diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index c83cc50e60..56739c3a8a 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..a59544e120 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.9", "ruff<1.0.0,>=0.2.2", "prek>=0.2.24,<1.0.0", "coverage<8.0.0,>=7.4.3", diff --git a/backend/scripts/lint.sh b/backend/scripts/lint.sh index b3b2b4ecc7..d8748c98f0 100644 --- a/backend/scripts/lint.sh +++ b/backend/scripts/lint.sh @@ -4,5 +4,6 @@ set -e set -x mypy app +ty app ruff check app ruff format app --check diff --git a/uv.lock b/uv.lock index 48635f8fe8..7ab33ff0c8 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.9" }, ] [[package]] @@ -2175,6 +2177,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.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/20/2ba8fd9493c89c41dfe9dbb73bc70a28b28028463bc0d2897ba8be36230a/ty-0.0.21.tar.gz", hash = "sha256:a4c2ba5d67d64df8fcdefd8b280ac1149d24a73dbda82fa953a0dff9d21400ed", size = 5297967, upload-time = "2026-03-06T01:57:13.809Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/70/edf38bb37517531681d1c37f5df64744e5ad02673c02eb48447eae4bea08/ty-0.0.21-py3-none-linux_armv6l.whl", hash = "sha256:7bdf2f572378de78e1f388d24691c89db51b7caf07cf90f2bfcc1d6b18b70a76", size = 10299222, upload-time = "2026-03-06T01:57:16.64Z" }, + { url = "https://files.pythonhosted.org/packages/72/62/0047b0bd19afeefbc7286f20a5f78a2aa39f92b4d89853f0d7185ab89edc/ty-0.0.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7e9613994610431ab8625025bd2880dbcb77c5c9fabdd21134cda12d840a529d", size = 10130513, upload-time = "2026-03-06T01:57:29.93Z" }, + { url = "https://files.pythonhosted.org/packages/a2/20/0b93a9e91aaed23155780258cdfdb4726ef68b6985378ac069bc427291a0/ty-0.0.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:56d3b198b64dd0a19b2b66e257deaed2ecea568e722ae5352f3c6fb62027f89d", size = 9605425, upload-time = "2026-03-06T01:57:27.115Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fd/9945e2fa2996a1287b1e1d7ce050e97e1f420233b271e770934bfa0880a0/ty-0.0.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d23d2c34f7a77d974bb08f0860ef700addc8a683d81a0319f71c08f87506cfd0", size = 10108298, upload-time = "2026-03-06T01:57:35.429Z" }, + { url = "https://files.pythonhosted.org/packages/52/e7/4ec52fcb15f3200826c9f048472c062549a05b0d1ef0b51f32d527b513c4/ty-0.0.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56b01fd2519637a4ca88344f61c96225f540c98ff18bca321d4eaa7bb0f7aa2f", size = 10121556, upload-time = "2026-03-06T01:57:03.242Z" }, + { url = "https://files.pythonhosted.org/packages/ee/c0/ad457be2a8abea0f25549598bd098554540ced66229488daa0d558dad3c8/ty-0.0.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9de7e11c63c6afc40f3e9ba716374add171aee7fabc70b5146a510705c6d41b", size = 10603264, upload-time = "2026-03-06T01:56:52.134Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5b/2ecc7a2175243a4bcb72f5298ae41feabbb93b764bb0dc45722f3752c2c2/ty-0.0.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62f7f5b235c4f7876db305c36997aea07b7af29b1a068f373d0e2547e25f32ff", size = 11196428, upload-time = "2026-03-06T01:57:32.94Z" }, + { url = "https://files.pythonhosted.org/packages/37/f5/aff507d6a901f328ef96a298032b0c11aaaf950a146ed7dd3b5bf2cd3acf/ty-0.0.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee8399f7c453a425291e6688efe430cfae7ab0ac4ffd50eba9f872bf878b54f6", size = 10866355, upload-time = "2026-03-06T01:56:57.831Z" }, + { url = "https://files.pythonhosted.org/packages/be/30/822bbcb92d55b65989aa7ed06d9585f28ade9c9447369194ed4b0fb3b5b9/ty-0.0.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210e7568c9f886c4d01308d751949ee714ad7ad9d7d928d2ba90d329dd880367", size = 10738177, upload-time = "2026-03-06T01:57:11.256Z" }, + { url = "https://files.pythonhosted.org/packages/57/cc/46e7991b6469e93ac2c7e533a028983e402485580150ac864c56352a3a82/ty-0.0.21-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:53508e345b11569f78b21ba8e2b4e61df38a9754947fb3cd9f2ef574367338fb", size = 10079158, upload-time = "2026-03-06T01:57:00.516Z" }, + { url = "https://files.pythonhosted.org/packages/15/c2/0bbdadfbd008240f8f1a87dc877433cb3884436097926107ccf06e618199/ty-0.0.21-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:553e43571f4a35604c36cfd07d8b61a5eb7a714e3c67f8c4ff2cf674fefbaef9", size = 10150535, upload-time = "2026-03-06T01:57:08.815Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b5/2dbdb7b57b5362200ef0a39738ebd31331726328336def0143ac097ee59d/ty-0.0.21-py3-none-musllinux_1_2_i686.whl", hash = "sha256:666f6822e3b9200abfa7e95eb0ddd576460adb8d66b550c0ad2c70abc84a2048", size = 10319803, upload-time = "2026-03-06T01:57:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/72/84/70e52c0b7abc7c2086f9876ef454a73b161d3125315536d8d7e911c94ca4/ty-0.0.21-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a0854d008347ce4a5fb351af132f660a390ab2a1163444d075251d43e6f74b9b", size = 10826239, upload-time = "2026-03-06T01:57:21.727Z" }, + { url = "https://files.pythonhosted.org/packages/a1/8a/1f72480fd013bbc6cd1929002abbbcde9a0b08ead6a15154de9d7f7fa37e/ty-0.0.21-py3-none-win32.whl", hash = "sha256:bef3ab4c7b966bcc276a8ac6c11b63ba222d21355b48d471ea782c4104eee4e0", size = 9693196, upload-time = "2026-03-06T01:57:24.126Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f8/1104808b875c26c640e536945753a78562d606bef4e241d9dbf3d92477f6/ty-0.0.21-py3-none-win_amd64.whl", hash = "sha256:a709d576e5bea84b745d43058d8b9cd4f27f74a0b24acb4b0cbb7d3d41e0d050", size = 10668660, upload-time = "2026-03-06T01:56:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b8/25e0adc404bbf986977657b25318991f93097b49f8aea640d93c0b0db68e/ty-0.0.21-py3-none-win_arm64.whl", hash = "sha256:f72047996598ac20553fb7e21ba5741e3c82dee4e9eadf10d954551a5fe09391", size = 10104161, upload-time = "2026-03-06T01:57:06.072Z" }, +] + [[package]] name = "typer" version = "0.21.1"