From a1cce2e401050f39d7f8fcbab5fd823a219fda3c Mon Sep 17 00:00:00 2001 From: dcousin24 Date: Wed, 25 Jun 2025 11:05:15 -0300 Subject: [PATCH 1/6] Pyright migration --- .devcontainer/devcontainer.json | 7 ++- .dockerignore | 2 +- .gitignore | 2 +- .pre-commit-config.yaml | 11 ++--- README.md | 2 +- poetry.lock | 80 ++++++++++----------------------- pyproject.toml | 62 ++++++++++++------------- scripts/format.sh | 4 +- src/admin.py | 5 ++- src/alembic/env.py | 4 +- src/api/dependencies.py | 7 ++- src/core/config.py | 22 ++++++++- src/helpers/shell.py | 21 +++++++-- src/tests/base.py | 7 ++- src/tests/test_user.py | 1 - 15 files changed, 123 insertions(+), 114 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4dd5a2d..db024b2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -28,7 +28,8 @@ "extensions": [ "charliermarsh.ruff", "ms-azuretools.vscode-docker", - "ms-python.mypy-type-checker", + "ms-python.python", + "ms-python.vscode-pylance", "tamasfe.even-better-toml" ], "settings": { @@ -48,7 +49,9 @@ "python.testing.pytestArgs": [ "src/tests" ], - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "python.analysis.typeCheckingMode": "standard", + "python.analysis.autoImportCompletions": true } }, "black-formatter.args": [ diff --git a/.dockerignore b/.dockerignore index 1889b90..f1e12e7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,7 +14,7 @@ Dockerfile docker-compose.yaml # python related -.mypy_cache +.pyright_cache .pytest_cache *.pyc *.pyo diff --git a/.gitignore b/.gitignore index b073d48..66119ea 100755 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ var/ .venv logs/* .ptpython-history -.mypy_cache +.pyright_cache .pytest_cache .coverage *.coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0dcb817..dca8249 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,13 +20,8 @@ repos: # Run the formatter. - id: ruff-format - - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.8.0' + - repo: https://github.com/RobertCraigie/pyright-python + rev: v1.1.394 hooks: - - id: mypy + - id: pyright exclude: ^src/alembic/|^scripts/|^\\.devcontainer/|^\\.github/ - additional_dependencies: [ - pydantic==2.10.6, - SQLAlchemy==2.0.39, - types-mock==5.2.0.20250306 - ] diff --git a/README.md b/README.md index 4461ff2..5d6b45f 100755 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ We use Alembic as database migration tool. You can run migration commands direct Linters, formatters, etc. - **ruff**: Linter and formatter -- **mypy**: Static type checker +- **pyright**: Static type checker ### pre-commit diff --git a/poetry.lock b/poetry.lock index 8ae1cd4..a078ab4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "alembic" @@ -1268,66 +1268,13 @@ build = ["blurb", "twine", "wheel"] docs = ["sphinx"] test = ["pytest", "pytest-cov"] -[[package]] -name = "mypy" -version = "1.15.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, - {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"}, - {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"}, - {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"}, - {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"}, - {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"}, - {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"}, - {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"}, - {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"}, - {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"}, - {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"}, - {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"}, - {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}, - {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"}, -] - -[package.dependencies] -mypy_extensions = ">=1.0.0" -typing_extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" -groups = ["dev", "types"] +groups = ["types"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1951,6 +1898,27 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyright" +version = "1.1.402" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "pyright-1.1.402-py3-none-any.whl", hash = "sha256:2c721f11869baac1884e846232800fe021c33f1b4acb3929cff321f7ea4e2982"}, + {file = "pyright-1.1.402.tar.gz", hash = "sha256:85a33c2d40cd4439c66aa946fd4ce71ab2f3f5b8c22ce36a623f59ac22937683"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" +typing-extensions = ">=4.1" + +[package.extras] +all = ["nodejs-wheel-binaries", "twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] +nodejs = ["nodejs-wheel-binaries"] + [[package]] name = "pytest" version = "8.3.5" @@ -2715,4 +2683,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.13.0,<4.0.0" -content-hash = "4295a745b43c0f89ad0b7ff0c6676c6eae423c1abcaceabdbd5dca763c545d49" +content-hash = "d04c67b17361f8117b73a8f67ccace363f742a29014e850318ee61baf05681e5" diff --git a/pyproject.toml b/pyproject.toml index 6cfde0d..a47e274 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,7 @@ uvicorn = "^0.34.0" coverage = "^7.7.1" flower = "^2.0.1" mock = "^5.2.0" -mypy = "^1.15.0" -mypy-extensions = "^1.0.0" +pyright = "^1.1.394" pytest = "^8.3.5" pytest-asyncio = "0.26.0" pre-commit = "^4.2.0" @@ -87,34 +86,37 @@ known-third-party = ["fastapi", "sqlalchemy", "pydantic"] force-single-line = false combine-as-imports = true -[tool.mypy] -plugins = ["pydantic.mypy", "sqlalchemy.ext.mypy.plugin"] -ignore_missing_imports = true -disallow_untyped_defs = true -warn_unused_ignores = false -no_implicit_optional = true -implicit_reexport = true -explicit_package_bases = true -namespace_packages = true -follow_imports = "silent" -warn_redundant_casts = true -check_untyped_defs = true -no_implicit_reexport = true -disable_error_code = ["name-defined", "call-arg", "attr-defined"] - -[[tool.mypy.overrides]] -module = "starlette_context.plugins" -implicit_reexport = true - -[[tool.mypy.overrides]] -module = "app.middlewares.logging_middleware" -warn_unused_ignores = false - -[tool.pydantic-mypy] -init_forbid_extra = true -init_typed = true -warn_required_dynamic_aliases = false -warn_untyped_fields = true +[tool.pyright] +include = ["src"] +exclude = ["src/alembic/versions", "**/__pycache__", "scripts"] +reportMissingImports = true +reportMissingTypeStubs = false +pythonVersion = "3.12" +typeCheckingMode = "standard" +useLibraryCodeForTypes = true +strictListInference = true +strictDictionaryInference = true +strictSetInference = true +analyzeUnannotatedFunctions = true +strictParameterNoneValue = true +enableTypeIgnoreComments = true +reportGeneralTypeIssues = true +reportOptionalSubscript = true +reportOptionalMemberAccess = true +reportOptionalCall = true +reportOptionalIterable = true +reportOptionalContextManager = true +reportOptionalOperand = true +reportTypedDictNotRequiredAccess = false +reportPrivateUsage = false +reportUnknownArgumentType = false +reportUnknownLambdaType = false +reportUnknownMemberType = false +reportUnknownParameterType = false +reportUnknownVariableType = false +reportUnnecessaryIsInstance = false +reportUnnecessaryCast = false +reportUnnecessaryComparison = false [tool.pytest.ini_options] asyncio_mode = "auto" diff --git a/scripts/format.sh b/scripts/format.sh index bb090f5..dd3e6d9 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -1,7 +1,7 @@ #!/bin/bash -printf "\nRunning mypy...\n" -poetry run python -m mypy src +printf "\nRunning pyright...\n" +pyright src printf "\nRunning ruff check...\n" ruff check --fix diff --git a/src/admin.py b/src/admin.py index 1efcdc7..08af093 100755 --- a/src/admin.py +++ b/src/admin.py @@ -36,6 +36,8 @@ async def authenticate(self, request: Request) -> RedirectResponse | bool: token = request.session.get(AdminAuth.cookie_name) if not token: return failed_auth_response + + session = None try: async_session = async_session_generator() async with async_session() as session: @@ -44,7 +46,8 @@ async def authenticate(self, request: Request) -> RedirectResponse | bool: except Exception: return failed_auth_response finally: - await session.close() + if session is not None: + await session.close() if not user.is_superuser: return failed_auth_response return True diff --git a/src/alembic/env.py b/src/alembic/env.py index 041b4a7..4114c9e 100755 --- a/src/alembic/env.py +++ b/src/alembic/env.py @@ -1,11 +1,11 @@ import asyncio from logging.config import fileConfig -from alembic import context -from sqlalchemy import engine_from_config, pool +from sqlalchemy import pool from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import async_engine_from_config +from alembic import context from src import models # noqa F401 from src.core.config import settings from src.core.database import SQLBase diff --git a/src/api/dependencies.py b/src/api/dependencies.py index 7289ab8..246bed3 100755 --- a/src/api/dependencies.py +++ b/src/api/dependencies.py @@ -8,15 +8,18 @@ async def db_session() -> AsyncIterator[AsyncSession]: + session = None try: async_session = async_session_generator() async with async_session() as session: yield session except Exception: - await session.rollback() + if session is not None: + await session.rollback() raise finally: - await session.close() + if session is not None: + await session.close() async def get_user(request: Request, session: AsyncSession = Depends(db_session)) -> User: diff --git a/src/core/config.py b/src/core/config.py index abd9f9e..335b041 100755 --- a/src/core/config.py +++ b/src/core/config.py @@ -27,6 +27,10 @@ class LogSettings(BaseSettings): structured_log: bool | Literal["auto"] = "auto" cache_loggers: bool = True + def __hash__(self) -> int: + """Make LogSettings hashable so it can be used as a dict key""" + return hash((self.log_level, self.structured_log, self.cache_loggers)) + @property def enable_structured_log(self) -> bool: return not sys.stdout.isatty() if self.structured_log == "auto" else self.structured_log @@ -42,7 +46,7 @@ def parse_log_level(cls, data: Any) -> Any: class Settings(BaseSettings): # Makes the settings immutable and hashable (can be used as dicts key) - model_config = SettingsConfigDict(frozen=True) + model_config = SettingsConfigDict(frozen=True, env_file=".env", env_file_encoding="utf-8") env: str = "dev" project_name: str @@ -72,4 +76,18 @@ class Settings(BaseSettings): otel_exporter_otlp_endpoint: str -settings = Settings() +# Settings will be instantiated from environment variables +# Make sure to set these environment variables: +# PROJECT_NAME=python-template +# ASYNC_DATABASE_URL=postgresql+asyncpg://user:password@localhost/db +# DATABASE_POOL_PRE_PING=true +# DATABASE_POOL_SIZE=10 +# DATABASE_POOL_RECYCLE=3600 +# DATABASE_MAX_OVERFLOW=20 +# SERVER_URL=http://localhost:8000 +# ACCESS_TOKEN_EXPIRE_MINUTES=60.0 +# JWT_SIGNING_KEY=your-secret-key-here +# CELERY_BROKER_URL=redis://localhost:6379/0 +# CELERY_RESULT_BACKEND=redis://localhost:6379/0 +# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 +settings = Settings() # type: ignore[reportCallIssue] diff --git a/src/helpers/shell.py b/src/helpers/shell.py index 8eae9d6..e4cccbf 100644 --- a/src/helpers/shell.py +++ b/src/helpers/shell.py @@ -1,3 +1,4 @@ +import asyncio import inspect from typing import Dict, Type @@ -5,7 +6,7 @@ from src import models from src.core.config import settings -from src.core.database import SessionLocal, SQLBase +from src.core.database import SQLBase, async_session_generator def _get_models() -> Dict[str, Type[SQLBase]]: @@ -16,12 +17,26 @@ def _get_models() -> Dict[str, Type[SQLBase]]: return models_dict -def _start_shell() -> None: +async def _start_shell_async() -> None: + """Async shell function that properly handles the async session""" models = _get_models() - with SessionLocal() as session: + async_session = async_session_generator() + async with async_session() as session: locals = {"session": session, "settings": settings, **models} embed(locals=locals, history_filename=".ptpython-history") +def _start_shell() -> None: + """Start the shell - wrapper to handle async context""" + try: + # Try to use existing event loop if available + asyncio.get_running_loop() + # If we're already in an async context, we can't use asyncio.run + asyncio.create_task(_start_shell_async()) + except RuntimeError: + # No event loop running, start new one + asyncio.run(_start_shell_async()) + + if __name__ == "__main__": _start_shell() diff --git a/src/tests/base.py b/src/tests/base.py index b43601d..b3c7c6c 100755 --- a/src/tests/base.py +++ b/src/tests/base.py @@ -25,15 +25,18 @@ def async_session_generator() -> async_sessionmaker[AsyncSession]: async def override_get_db() -> AsyncIterator[AsyncSession]: + session = None try: async_session = async_session_generator() async with async_session() as session: yield session except Exception: - await session.rollback() + if session is not None: + await session.rollback() raise finally: - await session.close() + if session is not None: + await session.close() app.dependency_overrides[db_session] = override_get_db diff --git a/src/tests/test_user.py b/src/tests/test_user.py index ee6499f..fbe5c31 100755 --- a/src/tests/test_user.py +++ b/src/tests/test_user.py @@ -4,7 +4,6 @@ from httpx import AsyncClient, Response from jose import jwt -from mock import patch from src.core.config import settings from src.core.security import AuthManager, PasswordManager From e18e0ccc6c52260ee28771225fedc28e9544be3f Mon Sep 17 00:00:00 2001 From: dcousin24 Date: Wed, 25 Jun 2025 11:06:58 -0300 Subject: [PATCH 2/6] pyright execution --- scripts/format.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/format.sh b/scripts/format.sh index dd3e6d9..ceb5e5e 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -1,7 +1,7 @@ #!/bin/bash printf "\nRunning pyright...\n" -pyright src +poetry run pyright src printf "\nRunning ruff check...\n" ruff check --fix From 136dad2441cd6432402a85f6bf2b1cd357688f1d Mon Sep 17 00:00:00 2001 From: dcousin24 Date: Wed, 25 Jun 2025 11:08:52 -0300 Subject: [PATCH 3/6] settings --- src/core/config.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/core/config.py b/src/core/config.py index 335b041..f42fa5b 100755 --- a/src/core/config.py +++ b/src/core/config.py @@ -76,18 +76,4 @@ class Settings(BaseSettings): otel_exporter_otlp_endpoint: str -# Settings will be instantiated from environment variables -# Make sure to set these environment variables: -# PROJECT_NAME=python-template -# ASYNC_DATABASE_URL=postgresql+asyncpg://user:password@localhost/db -# DATABASE_POOL_PRE_PING=true -# DATABASE_POOL_SIZE=10 -# DATABASE_POOL_RECYCLE=3600 -# DATABASE_MAX_OVERFLOW=20 -# SERVER_URL=http://localhost:8000 -# ACCESS_TOKEN_EXPIRE_MINUTES=60.0 -# JWT_SIGNING_KEY=your-secret-key-here -# CELERY_BROKER_URL=redis://localhost:6379/0 -# CELERY_RESULT_BACKEND=redis://localhost:6379/0 -# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 settings = Settings() # type: ignore[reportCallIssue] From 8dce9058831f5f9d740fa146367ca96f43472e8d Mon Sep 17 00:00:00 2001 From: dcousin24 Date: Mon, 7 Jul 2025 10:20:21 -0300 Subject: [PATCH 4/6] test: fix Celery task type annotations --- .pre-commit-config.yaml | 28 +++++++++++++++++++++++++--- src/api/v1/routers/task.py | 4 ++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dca8249..66f697f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ default_stages: [pre-commit, pre-push] files: ^src/|^alembic/versions/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -12,7 +12,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.5.5 + rev: v0.11.3 hooks: # Run the linter. - id: ruff @@ -21,7 +21,29 @@ repos: - id: ruff-format - repo: https://github.com/RobertCraigie/pyright-python - rev: v1.1.394 + rev: v1.1.402 hooks: - id: pyright exclude: ^src/alembic/|^scripts/|^\\.devcontainer/|^\\.github/ + additional_dependencies: [ + fastapi==0.115.12, + pydantic==2.10.6, + pydantic-settings==2.8.1, + sqlalchemy==2.0.39, + asyncpg==0.30.0, + structlog==25.3.0, + sqladmin==0.20.1, + fastapi-pagination==0.12.34, + opentelemetry-api==1.31.1, + opentelemetry-sdk==1.31.1, + opentelemetry-instrumentation-fastapi==0.52b1, + opentelemetry-instrumentation-sqlalchemy==0.52b1, + opentelemetry-exporter-otlp==1.31.1, + celery==5.4.0, + python-jose==3.4.0, + passlib==1.7.4, + uvicorn==0.34.0, + httpx==0.28.1, + pytest==8.3.5, + ptpython==3.0.29 + ] diff --git a/src/api/v1/routers/task.py b/src/api/v1/routers/task.py index b1cfa47..fde242d 100644 --- a/src/api/v1/routers/task.py +++ b/src/api/v1/routers/task.py @@ -14,11 +14,11 @@ # you have to implement the task logic in the src/task_queue/task.py file @router.post("/add", response_model=Task) def add_task(payload: TaskCreate = Body(...)) -> Any: - task = add.delay(payload.delay, payload.x, payload.y) + task = add.delay(payload.delay, payload.x, payload.y) # type: ignore[reportFunctionMemberAccess] return {"task_id": task.id} @router.get("/task/{task_id}", response_model=TaskResult) def get_task_result(task_id: str = Path(...)) -> Any: - task: AsyncResult = add.AsyncResult(task_id) + task: AsyncResult = add.AsyncResult(task_id) # type: ignore[reportFunctionMemberAccess] return {"task_id": task.id, "task_status": task.status, "task_result": task.result} From 1eece79b40bc3aa4d61061de4d37d18b7ab15a37 Mon Sep 17 00:00:00 2001 From: dcousin24 Date: Mon, 7 Jul 2025 10:21:42 -0300 Subject: [PATCH 5/6] Pre commit fix --- poetry.lock | 2 +- pyproject.toml | 2 +- scripts/format.sh | 4 ++-- src/admin.py | 6 ++++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index a078ab4..f3a232d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2683,4 +2683,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.13.0,<4.0.0" -content-hash = "d04c67b17361f8117b73a8f67ccace363f742a29014e850318ee61baf05681e5" +content-hash = "95d3e7a201a5c5d26847a5b2cafd4b4aa6bff858e1a50988e7a68f4965ad6ebc" diff --git a/pyproject.toml b/pyproject.toml index a47e274..6ce16ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ uvicorn = "^0.34.0" coverage = "^7.7.1" flower = "^2.0.1" mock = "^5.2.0" -pyright = "^1.1.394" +pyright = "^1.1.402" pytest = "^8.3.5" pytest-asyncio = "0.26.0" pre-commit = "^4.2.0" diff --git a/scripts/format.sh b/scripts/format.sh index ceb5e5e..449e5e3 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -4,7 +4,7 @@ printf "\nRunning pyright...\n" poetry run pyright src printf "\nRunning ruff check...\n" -ruff check --fix +poetry run ruff check --fix printf "\nRunning ruff format...\n" -ruff format +poetry run ruff format diff --git a/src/admin.py b/src/admin.py index 08af093..75f9466 100755 --- a/src/admin.py +++ b/src/admin.py @@ -53,7 +53,8 @@ async def authenticate(self, request: Request) -> RedirectResponse | bool: return True -class UserAdmin(ModelView, model=User): +class UserAdmin(ModelView): + model = User column_list = [ User.email, User.created_at, @@ -62,7 +63,8 @@ class UserAdmin(ModelView, model=User): column_searchable_list = [User.id, User.email] -class ItemAdmin(ModelView, model=Item): +class ItemAdmin(ModelView): + model = Item column_list = [ Item.name, Item.description, From 75f862dc8e0e7d058b7920e59a0fd5a4a4f36605 Mon Sep 17 00:00:00 2001 From: dcousin24 Date: Mon, 7 Jul 2025 12:25:26 -0300 Subject: [PATCH 6/6] App loading fix --- src/admin.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/admin.py b/src/admin.py index 75f9466..f60294e 100755 --- a/src/admin.py +++ b/src/admin.py @@ -53,8 +53,7 @@ async def authenticate(self, request: Request) -> RedirectResponse | bool: return True -class UserAdmin(ModelView): - model = User +class UserAdmin(ModelView, model=User): # type: ignore[misc] column_list = [ User.email, User.created_at, @@ -63,8 +62,7 @@ class UserAdmin(ModelView): column_searchable_list = [User.id, User.email] -class ItemAdmin(ModelView): - model = Item +class ItemAdmin(ModelView, model=Item): # type: ignore[misc] column_list = [ Item.name, Item.description,