diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index d4c6247269..0a8a942184 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -91,7 +91,7 @@ jobs: - name: Install dependencies run: bun ci - name: Download blob reports from GitHub Actions Artifacts - uses: actions/download-artifact@v8 + uses: actions/download-artifact@v7 with: path: frontend/all-blob-reports pattern: blob-report-* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f19448ef5..39771c3fab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,6 +47,13 @@ repos: language: unsupported pass_filenames: false + - id: local-ty + name: ty check + entry: uv run ty check backend/app + require_serial: true + language: unsupported + pass_filenames: false + - id: generate-frontend-sdk name: Generate Frontend SDK entry: bash ./scripts/generate-client.sh diff --git a/backend/app/alembic/env.py b/backend/app/alembic/env.py index 5e2c22f844..fb993cf48c 100755 --- a/backend/app/alembic/env.py +++ b/backend/app/alembic/env.py @@ -63,6 +63,7 @@ def run_migrations_online(): """ configuration = config.get_section(config.config_ini_section) + assert configuration is not None configuration["sqlalchemy.url"] = get_url() connectable = engine_from_config( configuration, diff --git a/backend/app/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py b/backend/app/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py index 10e47a1456..a5246906df 100644 --- a/backend/app/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py +++ b/backend/app/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py @@ -29,7 +29,7 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'item', type_='foreignkey') + op.drop_constraint('item_owner_id_fkey', 'item', type_='foreignkey') op.create_foreign_key('item_owner_id_fkey', 'item', 'user', ['owner_id'], ['id']) op.alter_column('item', 'owner_id', existing_type=sa.UUID(), diff --git a/backend/app/api/routes/items.py b/backend/app/api/routes/items.py index f1929e5836..f0eb30e4ce 100644 --- a/backend/app/api/routes/items.py +++ b/backend/app/api/routes/items.py @@ -41,7 +41,8 @@ def read_items( ) items = session.exec(statement).all() - return ItemsPublic(data=items, count=count) + items_public = [ItemPublic.model_validate(item) for item in items] + return ItemsPublic(data=items_public, count=count) @router.get("/{id}", response_model=ItemPublic) diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index 35f64b626e..1748f58484 100644 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -47,7 +47,8 @@ def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any: ) users = session.exec(statement).all() - return UsersPublic(data=users, count=count) + users_public = [UserPublic.model_validate(user) for user in users] + return UsersPublic(data=users_public, count=count) @router.post( diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 650b9f7910..e9036cbe56 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -41,7 +41,7 @@ class Settings(BaseSettings): list[AnyUrl] | str, BeforeValidator(parse_cors) ] = [] - @computed_field # type: ignore[prop-decorator] + @computed_field # type: ignore[prop-decorator] # ty: ignore[unused-ignore-comment] @property def all_cors_origins(self) -> list[str]: return [str(origin).rstrip("/") for origin in self.BACKEND_CORS_ORIGINS] + [ @@ -56,7 +56,7 @@ def all_cors_origins(self) -> list[str]: POSTGRES_PASSWORD: str = "" POSTGRES_DB: str = "" - @computed_field # type: ignore[prop-decorator] + @computed_field # type: ignore[prop-decorator] # ty: ignore[unused-ignore-comment] @property def SQLALCHEMY_DATABASE_URI(self) -> PostgresDsn: return PostgresDsn.build( @@ -85,7 +85,7 @@ def _set_default_emails_from(self) -> Self: EMAIL_RESET_TOKEN_EXPIRE_HOURS: int = 48 - @computed_field # type: ignore[prop-decorator] + @computed_field # type: ignore[prop-decorator] # ty: ignore[unused-ignore-comment] @property def emails_enabled(self) -> bool: return bool(self.SMTP_HOST and self.EMAILS_FROM_EMAIL) diff --git a/backend/app/main.py b/backend/app/main.py index 9a95801e74..2d9c7444df 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -23,7 +23,7 @@ def custom_generate_unique_id(route: APIRoute) -> str: # Set all CORS enabled origins if settings.all_cors_origins: app.add_middleware( - CORSMiddleware, + CORSMiddleware, # ty: ignore allow_origins=settings.all_cors_origins, allow_credentials=True, allow_methods=["*"], diff --git a/backend/app/models.py b/backend/app/models.py index b5132e0e2c..1c65e2f6ac 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -31,7 +31,7 @@ class UserRegister(SQLModel): # Properties to receive via API on update, all are optional class UserUpdate(UserBase): - email: EmailStr | None = Field(default=None, max_length=255) # type: ignore + email: EmailStr | None = Field(default=None, max_length=255) # type: ignore # ty: ignore[unused-ignore-comment] password: str | None = Field(default=None, min_length=8, max_length=128) @@ -80,7 +80,7 @@ class ItemCreate(ItemBase): # Properties to receive on item update class ItemUpdate(ItemBase): - title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore + title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore # ty: ignore[unused-ignore-comment] # Database model, database table inferred from class name diff --git a/backend/app/utils.py b/backend/app/utils.py index ac029f6342..03a0c16891 100644 --- a/backend/app/utils.py +++ b/backend/app/utils.py @@ -4,7 +4,7 @@ from pathlib import Path from typing import Any -import emails # type: ignore +import emails # type: ignore # ty: ignore[unused-ignore-comment] import jwt from jinja2 import Template from jwt.exceptions import InvalidTokenError diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 66b4d66683..89a5bdd74e 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -25,6 +25,7 @@ dependencies = [ dev = [ "pytest<8.0.0,>=7.4.3", "mypy<2.0.0,>=1.8.0", + "ty>=0.0.25", "ruff<1.0.0,>=0.2.2", "prek>=0.2.24,<1.0.0", "coverage<8.0.0,>=7.4.3", @@ -75,3 +76,6 @@ sort = "-Cover" [tool.coverage.html] show_contexts = true + +[tool.ty.terminal] +error-on-warning = true diff --git a/backend/scripts/lint.sh b/backend/scripts/lint.sh index b3b2b4ecc7..ccc981e490 100644 --- a/backend/scripts/lint.sh +++ b/backend/scripts/lint.sh @@ -4,5 +4,6 @@ set -e set -x mypy app +ty check app ruff check app ruff format app --check diff --git a/uv.lock b/uv.lock index aef1e5bb8d..312035345c 100644 --- a/uv.lock +++ b/uv.lock @@ -87,6 +87,7 @@ dev = [ { name = "prek" }, { name = "pytest" }, { name = "ruff" }, + { name = "ty" }, ] [package.metadata] @@ -115,6 +116,7 @@ dev = [ { name = "prek", specifier = ">=0.2.24,<1.0.0" }, { name = "pytest", specifier = ">=7.4.3,<8.0.0" }, { name = "ruff", specifier = ">=0.2.2,<1.0.0" }, + { name = "ty", specifier = ">=0.0.25" }, ] [[package]] @@ -2169,6 +2171,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, ] +[[package]] +name = "ty" +version = "0.0.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/94/4879b81f8681117ccaf31544579304f6dc2ddcc0c67f872afb35869643a2/ty-0.0.26.tar.gz", hash = "sha256:0496b62405d62de7b954d6d677dc1cc5d3046197215d7a0a7fef37745d7b6d29", size = 5393643, upload-time = "2026-03-26T16:27:11.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/24/99fe33ecd7e16d23c53b0d4244778c6d1b6eb1663b091236dcba22882d67/ty-0.0.26-py3-none-linux_armv6l.whl", hash = "sha256:35beaa56cf59725fd59ab35d8445bbd40b97fe76db39b052b1fcb31f9bf8adf7", size = 10521856, upload-time = "2026-03-26T16:27:06.335Z" }, + { url = "https://files.pythonhosted.org/packages/55/97/1b5e939e2ff69b9bb279ab680bfa8f677d886309a1ac8d9588fd6ce58146/ty-0.0.26-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:487a0be58ab0eb02e31ba71eb6953812a0f88e50633469b0c0ce3fb795fe0fa1", size = 10320958, upload-time = "2026-03-26T16:27:13.849Z" }, + { url = "https://files.pythonhosted.org/packages/71/25/37081461e13d38a190e5646948d7bc42084f7bd1c6b44f12550be3923e7e/ty-0.0.26-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a01b7de5693379646d423b68f119719a1338a20017ba48a93eefaff1ee56f97b", size = 9799905, upload-time = "2026-03-26T16:26:55.805Z" }, + { url = "https://files.pythonhosted.org/packages/a1/1c/295d8f55a7b0e037dfc3a5ec4bdda3ab3cbca6f492f725bf269f96a4d841/ty-0.0.26-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:628c3ee869d113dd2bd249925662fd39d9d0305a6cb38f640ddaa7436b74a1ef", size = 10317507, upload-time = "2026-03-26T16:27:31.887Z" }, + { url = "https://files.pythonhosted.org/packages/1d/62/48b3875c5d2f48fe017468d4bbdde1164c76a8184374f1d5e6162cf7d9b8/ty-0.0.26-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63d04f35f5370cbc91c0b9675dc83e0c53678125a7b629c9c95769e86f123e65", size = 10319821, upload-time = "2026-03-26T16:27:29.647Z" }, + { url = "https://files.pythonhosted.org/packages/ff/28/cfb2d495046d5bf42d532325cea7412fa1189912d549dbfae417a24fd794/ty-0.0.26-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a53c4e6f6a91927f8b90e584a4b12bcde05b0c1870ddff8d17462168ad7947a", size = 10831757, upload-time = "2026-03-26T16:27:37.441Z" }, + { url = "https://files.pythonhosted.org/packages/26/bf/dbc3e42f448a2d862651de070b4108028c543ca18cab096b38d7de449915/ty-0.0.26-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:caf2ced0e58d898d5e3ba5cb843e0ebd377c8a461464748586049afbd9321f51", size = 11369556, upload-time = "2026-03-26T16:26:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/92/4c/6d2f8f34bc6d502ab778c9345a4a936a72ae113de11329c1764bb1f204f6/ty-0.0.26-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:384807bbcb7d7ce9b97ee5aaa6417a8ae03ccfb426c52b08018ca62cf60f5430", size = 11085679, upload-time = "2026-03-26T16:27:21.746Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f4/f3f61c203bc980dd9bba0ba7ed3c6e81ddfd36b286330f9487c2c7d041aa/ty-0.0.26-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2c766a94d79b4f82995d41229702caf2d76e5c440ec7e543d05c70e98bf8ab", size = 10900581, upload-time = "2026-03-26T16:27:24.39Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fd/3ca1b4e4bdd129829e9ce78677e0f8e0f1038a7702dccecfa52f037c6046/ty-0.0.26-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f41ac45a0f8e3e8e181508d863a0a62156341db0f624ffd004b97ee550a9de80", size = 10294401, upload-time = "2026-03-26T16:27:03.999Z" }, + { url = "https://files.pythonhosted.org/packages/de/20/4ee3d8c3f90e008843795c765cb8bb245f188c23e5e5cc612c7697406fba/ty-0.0.26-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:73eb8327a34d529438dfe4db46796946c4e825167cbee434dc148569892e435f", size = 10351469, upload-time = "2026-03-26T16:27:19.003Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b1/9fb154ade65906d4148f0b999c4a8257c2a34253cb72e15d84c1f04a064e/ty-0.0.26-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4bb53a79259516535a1b55f613ba1619e9c666854946474ca8418c35a5c4fd60", size = 10529488, upload-time = "2026-03-26T16:27:01.378Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b02b03b1862e27b64143db65946d68b138160a5b6bfea193bee0b8bbc34/ty-0.0.26-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2f0e75edc1aeb1b4b84af516c7891f631254a4ca3dcd15e848fa1e061e1fe9da", size = 10999015, upload-time = "2026-03-26T16:27:34.636Z" }, + { url = "https://files.pythonhosted.org/packages/21/16/0a56b8667296e2989b9d48095472d98ebf57a0006c71f2a101bbc62a142d/ty-0.0.26-py3-none-win32.whl", hash = "sha256:943c998c5523ed6b519c899c0c39b26b4c751a9759e460fb964765a44cde226f", size = 9912378, upload-time = "2026-03-26T16:27:08.999Z" }, + { url = "https://files.pythonhosted.org/packages/60/c2/fef0d4bba9cd89a82d725b3b1a66efb1b36629ecf0fb1d8e916cb75b8829/ty-0.0.26-py3-none-win_amd64.whl", hash = "sha256:19c856d343efeb1ecad8ee220848f5d2c424daf7b2feda357763ad3036e2172f", size = 10863737, upload-time = "2026-03-26T16:27:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/4d/05/888ebcb3c4d3b6b72d5d3241fddd299142caa3c516e6d26a9cd887dfed3b/ty-0.0.26-py3-none-win_arm64.whl", hash = "sha256:2cde58ccffa046db1223dc28f3e7d4f2c7da8267e97cc5cd186af6fe85f1758a", size = 10285408, upload-time = "2026-03-26T16:27:16.432Z" }, +] + [[package]] name = "typer" version = "0.21.1"