FastAPI-fastkit 1.2.1__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_fastkit/__init__.py +1 -1
- fastapi_fastkit/backend/inspection/__init__.py +26 -0
- fastapi_fastkit/backend/inspection/checks.py +349 -0
- fastapi_fastkit/backend/inspection/consistency.py +125 -0
- fastapi_fastkit/backend/inspection/context.py +67 -0
- fastapi_fastkit/backend/inspection/core.py +347 -0
- fastapi_fastkit/backend/inspection/docker.py +185 -0
- fastapi_fastkit/backend/inspection/freshness.py +145 -0
- fastapi_fastkit/backend/inspection/fsutils.py +139 -0
- fastapi_fastkit/backend/inspection/lint.py +89 -0
- fastapi_fastkit/backend/inspection/report.py +58 -0
- fastapi_fastkit/backend/inspection/smoke.py +327 -0
- fastapi_fastkit/backend/inspection/strategies.py +336 -0
- fastapi_fastkit/backend/inspector.py +18 -1279
- fastapi_fastkit/backend/interactive/__init__.py +8 -0
- fastapi_fastkit/backend/interactive/config_builder.py +66 -65
- fastapi_fastkit/backend/interactive/prompts.py +205 -5
- fastapi_fastkit/backend/interactive/selectors.py +28 -2
- fastapi_fastkit/backend/main.py +956 -95
- fastapi_fastkit/backend/package_managers/base.py +172 -0
- fastapi_fastkit/backend/package_managers/factory.py +6 -5
- fastapi_fastkit/backend/package_managers/pdm_manager.py +32 -85
- fastapi_fastkit/backend/package_managers/pip_manager.py +31 -85
- fastapi_fastkit/backend/package_managers/poetry_manager.py +73 -113
- fastapi_fastkit/backend/package_managers/uv_manager.py +46 -115
- fastapi_fastkit/backend/project_builder/__init__.py +10 -0
- fastapi_fastkit/backend/project_builder/config_generator.py +813 -450
- fastapi_fastkit/backend/project_builder/config_schema.py +384 -0
- fastapi_fastkit/backend/project_builder/dependency_collector.py +105 -66
- fastapi_fastkit/backend/project_builder/preset_layout.py +429 -0
- fastapi_fastkit/backend/scaffolder.py +578 -0
- fastapi_fastkit/backend/transducer.py +54 -22
- fastapi_fastkit/cli.py +452 -284
- fastapi_fastkit/core/settings.py +248 -43
- fastapi_fastkit/fastapi_project_template/README.md +88 -23
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/README.md-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/pyproject.toml-tpl +25 -23
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/requirements.txt-tpl +39 -36
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/api.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/routes/items.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/core/config.py-tpl +5 -6
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/crud/items.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/main.py-tpl +9 -1
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/schemas/items.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/conftest.py-tpl +0 -1
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/.env-tpl +21 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/.gitignore-tpl +33 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/Dockerfile-tpl +18 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/README.md-tpl +210 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/alembic.ini-tpl +119 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/pyproject.toml-tpl +86 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/requirements.txt-tpl +23 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/scripts/format.sh-tpl +5 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/scripts/lint.sh-tpl +6 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/scripts/run-server.sh-tpl +8 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/scripts/test.sh-tpl +6 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/alembic/README-tpl +1 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/alembic/env.py-tpl +51 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/alembic/script.py.mako-tpl +26 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/alembic/versions/0001_initial_auth_schema.py-tpl +62 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/api/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/api/deps.py-tpl +91 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/api/health.py-tpl +11 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/api/router.py-tpl +17 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/core/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/core/config.py-tpl +64 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/core/security.py-tpl +100 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/db/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/db/session.py-tpl +36 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/models.py-tpl +25 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/repository.py-tpl +41 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/router.py-tpl +82 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/schemas.py-tpl +14 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/service.py-tpl +65 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/models.py-tpl +35 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/repository.py-tpl +33 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/router.py-tpl +71 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/schemas.py-tpl +29 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/service.py-tpl +55 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/main.py-tpl +69 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/template-config.yml-tpl +16 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/tests/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/tests/conftest.py-tpl +73 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/tests/test_auth.py-tpl +201 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/tests/test_health.py-tpl +15 -0
- fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/tests/test_users.py-tpl +77 -0
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/pyproject.toml-tpl +25 -23
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/requirements.txt-tpl +39 -36
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/api.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/routes/items.py-tpl +3 -3
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/core/config.py-tpl +5 -6
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/crud/items.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/helper/pagination.py-tpl +8 -8
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/main.py-tpl +9 -0
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/base.py-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/items.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/tests/conftest.py-tpl +0 -1
- fastapi_fastkit/fastapi_project_template/fastapi-default/pyproject.toml-tpl +22 -20
- fastapi_fastkit/fastapi_project_template/fastapi-default/requirements.txt-tpl +37 -34
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/api.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/routes/items.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/core/config.py-tpl +5 -6
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/crud/items.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/main.py-tpl +9 -1
- fastapi_fastkit/fastapi_project_template/fastapi-default/src/schemas/items.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-default/tests/conftest.py-tpl +2 -3
- fastapi_fastkit/fastapi_project_template/fastapi-default/tests/test_items.py-tpl +0 -2
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/Dockerfile-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/README.md-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/pyproject.toml-tpl +25 -23
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/requirements.txt-tpl +41 -36
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/api.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/routes/items.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/core/config.py-tpl +5 -6
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/crud/items.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/main.py-tpl +9 -1
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/schemas/items.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/conftest.py-tpl +2 -3
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/test_items.py-tpl +0 -2
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/.env-tpl +2 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/.gitignore-tpl +31 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/README.md-tpl +128 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/pyproject.toml-tpl +69 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/requirements.txt-tpl +37 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/format.sh-tpl +5 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/lint.sh-tpl +6 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/run-server.sh-tpl +8 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/test.sh-tpl +6 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/health.py-tpl +11 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/router.py-tpl +15 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/core/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/core/config.py-tpl +49 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/db/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/db/memory.py-tpl +49 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/__init__.py-tpl +9 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/models.py-tpl +17 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/repository.py-tpl +47 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/router.py-tpl +58 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/schemas.py-tpl +23 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/service.py-tpl +51 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/main.py-tpl +28 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/conftest.py-tpl +23 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/test_health.py-tpl +11 -0
- fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/test_items.py-tpl +77 -0
- fastapi_fastkit/fastapi_project_template/fastapi-empty/README.md-tpl +2 -2
- fastapi_fastkit/fastapi_project_template/fastapi-empty/pyproject.toml-tpl +18 -16
- fastapi_fastkit/fastapi_project_template/fastapi-empty/requirements.txt-tpl +36 -10
- fastapi_fastkit/fastapi_project_template/fastapi-empty/src/core/config.py-tpl +5 -6
- fastapi_fastkit/fastapi_project_template/fastapi-empty/src/main.py-tpl +3 -1
- fastapi_fastkit/fastapi_project_template/fastapi-empty/tests/conftest.py-tpl +0 -1
- fastapi_fastkit/fastapi_project_template/fastapi-empty/tests/test_main.py-tpl +0 -1
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/.env-tpl +15 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/.gitignore-tpl +31 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/Dockerfile-tpl +18 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/README.md-tpl +193 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/pyproject.toml-tpl +76 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/requirements.txt-tpl +15 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/scripts/chat.sh-tpl +10 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/scripts/format.sh-tpl +5 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/scripts/lint.sh-tpl +6 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/scripts/run-server.sh-tpl +8 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/scripts/test.sh-tpl +6 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/api/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/api/chat.py-tpl +62 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/api/deps.py-tpl +23 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/api/health.py-tpl +11 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/api/router.py-tpl +13 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/core/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/core/config.py-tpl +67 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/llm/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/llm/agent.py-tpl +136 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/llm/client.py-tpl +30 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/llm/tools.py-tpl +131 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/main.py-tpl +53 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/memory/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/memory/base.py-tpl +27 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/memory/in_memory.py-tpl +32 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/schemas/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/schemas/chat.py-tpl +46 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/template-config.yml-tpl +16 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/conftest.py-tpl +158 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/test_chat.py-tpl +160 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/test_health.py-tpl +12 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/test_memory.py-tpl +38 -0
- fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/test_tools.py-tpl +37 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/README.md-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/pyproject.toml-tpl +29 -26
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/requirements.txt-tpl +68 -39
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/api.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/routes/auth.py-tpl +4 -5
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/routes/items.py-tpl +3 -4
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/auth/dependencies.py-tpl +4 -5
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/core/config.py-tpl +4 -5
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/main.py-tpl +4 -1
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/{mcp → mcp_server}/router.py-tpl +3 -5
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/schemas/items.py-tpl +6 -7
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/conftest.py-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_auth.py-tpl +0 -1
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_items.py-tpl +0 -1
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_mcp.py-tpl +0 -1
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/Dockerfile-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/pyproject.toml-tpl +26 -24
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/requirements.txt-tpl +44 -41
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/env.py-tpl +1 -3
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/versions/bedcdc35b64a_first_alembic.py-tpl +5 -6
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/api.py-tpl +2 -0
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/routes/items.py-tpl +2 -4
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/config.py-tpl +1 -2
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/db.py-tpl +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/crud/items.py-tpl +4 -6
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/main.py-tpl +9 -0
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/schemas/items.py-tpl +4 -5
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/conftest.py-tpl +1 -5
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/test_items.py-tpl +0 -2
- fastapi_fastkit/fastapi_project_template/fastapi-single-module/.env-tpl +1 -0
- fastapi_fastkit/fastapi_project_template/fastapi-single-module/.gitignore-tpl +30 -0
- fastapi_fastkit/fastapi_project_template/fastapi-single-module/pyproject.toml-tpl +7 -5
- fastapi_fastkit/fastapi_project_template/fastapi-single-module/requirements.txt-tpl +26 -6
- fastapi_fastkit/fastapi_project_template/fastapi-single-module/src/main.py-tpl +3 -0
- fastapi_fastkit/fastapi_project_template/fastapi-single-module/tests/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-single-module/tests/conftest.py-tpl +0 -1
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/.env-tpl +7 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/.gitignore-tpl +35 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/Dockerfile-tpl +18 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/README.md-tpl +166 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/alembic.ini-tpl +44 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/pyproject.toml-tpl +82 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/requirements.txt-tpl +21 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/scripts/format.sh-tpl +5 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/scripts/lint.sh-tpl +6 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/scripts/migrate.sh-tpl +7 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/scripts/run-server.sh-tpl +8 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/scripts/test.sh-tpl +6 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/alembic/README-tpl +9 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/alembic/env.py-tpl +72 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/alembic/script.py.mako-tpl +27 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/alembic/versions/0001_create_items_table.py-tpl +38 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/api/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/api/health.py-tpl +11 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/api/router.py-tpl +15 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/core/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/core/config.py-tpl +62 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/crud/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/crud/base.py-tpl +62 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/crud/pagination.py-tpl +21 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/db/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/db/base.py-tpl +12 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/db/session.py-tpl +50 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/domains/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/domains/items/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/domains/items/crud.py-tpl +26 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/domains/items/models.py-tpl +70 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/domains/items/router.py-tpl +69 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/main.py-tpl +43 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/template-config.yml-tpl +16 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/tests/__init__.py-tpl +0 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/tests/conftest.py-tpl +60 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/tests/test_crud_base.py-tpl +43 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/tests/test_health.py-tpl +12 -0
- fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/tests/test_items.py-tpl +110 -0
- fastapi_fastkit/fastapi_project_template/modules/api/routes/new_route.py-tpl +2 -2
- fastapi_fastkit/fragments/README.md +90 -0
- fastapi_fastkit/fragments/auth/fastapi_users.py.j2 +8 -0
- fastapi_fastkit/fragments/auth/jwt.py.j2 +44 -0
- fastapi_fastkit/fragments/auth/oauth2.py.j2 +105 -0
- fastapi_fastkit/fragments/auth/session.py.j2 +67 -0
- fastapi_fastkit/fragments/db/mongodb.py.j2 +19 -0
- fastapi_fastkit/fragments/db/redis.py.j2 +26 -0
- fastapi_fastkit/fragments/db/sqlalchemy.py.j2 +45 -0
- fastapi_fastkit/fragments/docker/docker_compose.j2 +84 -0
- fastapi_fastkit/fragments/docker/dockerfile.j2 +19 -0
- fastapi_fastkit/fragments/features/cache.py.j2 +26 -0
- fastapi_fastkit/fragments/features/pagination.py.j2 +31 -0
- fastapi_fastkit/fragments/features/tasks_celery.py.j2 +50 -0
- fastapi_fastkit/fragments/features/tasks_dramatiq.py.j2 +59 -0
- fastapi_fastkit/fragments/features/websocket.py.j2 +66 -0
- fastapi_fastkit/fragments/header.j2 +4 -0
- fastapi_fastkit/fragments/logging/structured.py.j2 +88 -0
- fastapi_fastkit/fragments/main/imports/async_tasks.j2 +1 -0
- fastapi_fastkit/fragments/main/imports/caching.j2 +5 -0
- fastapi_fastkit/fragments/main/imports/fastapi_users.j2 +1 -0
- fastapi_fastkit/fragments/main/imports/jwt.j2 +1 -0
- fastapi_fastkit/fragments/main/imports/loguru.j2 +1 -0
- fastapi_fastkit/fragments/main/imports/mongodb.j2 +1 -0
- fastapi_fastkit/fragments/main/imports/oauth2.j2 +4 -0
- fastapi_fastkit/fragments/main/imports/opentelemetry.j2 +6 -0
- fastapi_fastkit/fragments/main/imports/pagination.j2 +3 -0
- fastapi_fastkit/fragments/main/imports/prometheus.j2 +1 -0
- fastapi_fastkit/fragments/main/imports/rate_limiting.j2 +3 -0
- fastapi_fastkit/fragments/main/imports/session_auth.j2 +4 -0
- fastapi_fastkit/fragments/main/imports/sqlalchemy.j2 +2 -0
- fastapi_fastkit/fragments/main/imports/structured_logging.j2 +1 -0
- fastapi_fastkit/fragments/main/imports/websocket.j2 +1 -0
- fastapi_fastkit/fragments/main/lifespan/caching.j2 +7 -0
- fastapi_fastkit/fragments/main/lifespan/mongodb.j2 +2 -0
- fastapi_fastkit/fragments/main/lifespan/prometheus.j2 +2 -0
- fastapi_fastkit/fragments/main/lifespan/sqlalchemy.j2 +2 -0
- fastapi_fastkit/fragments/main/setup/async_tasks.j2 +1 -0
- fastapi_fastkit/fragments/main/setup/caching.j2 +1 -0
- fastapi_fastkit/fragments/main/setup/cors.j2 +8 -0
- fastapi_fastkit/fragments/main/setup/oauth2.j2 +3 -0
- fastapi_fastkit/fragments/main/setup/opentelemetry.j2 +19 -0
- fastapi_fastkit/fragments/main/setup/pagination.j2 +2 -0
- fastapi_fastkit/fragments/main/setup/rate_limiting.j2 +3 -0
- fastapi_fastkit/fragments/main/setup/session_auth.j2 +6 -0
- fastapi_fastkit/fragments/main/setup/structured_logging.j2 +2 -0
- fastapi_fastkit/fragments/main/setup/websocket.j2 +1 -0
- fastapi_fastkit/fragments/main.py.j2 +57 -0
- fastapi_fastkit/fragments/migrations/alembic_ini.j2 +42 -0
- fastapi_fastkit/fragments/migrations/env.py.j2 +76 -0
- fastapi_fastkit/fragments/migrations/initial_revision.py.j2 +27 -0
- fastapi_fastkit/fragments/migrations/migrate_sh.j2 +34 -0
- fastapi_fastkit/fragments/migrations/script.py.mako.j2 +26 -0
- fastapi_fastkit/fragments/tasks/celery_worker.py.j2 +49 -0
- fastapi_fastkit/fragments/tasks/dramatiq_worker.py.j2 +41 -0
- fastapi_fastkit/fragments/test/factories.py.j2 +68 -0
- fastapi_fastkit/fragments/test/pytest_ini.j2 +16 -0
- fastapi_fastkit/fragments/test/test_factories.py.j2 +27 -0
- fastapi_fastkit/fragments/test/test_main.py.j2 +46 -0
- fastapi_fastkit/fragments/tooling/devcontainer.j2 +26 -0
- fastapi_fastkit/fragments/tooling/github_actions.j2 +37 -0
- fastapi_fastkit/fragments/tooling/makefile.j2 +40 -0
- fastapi_fastkit/fragments/tooling/pre_commit.j2 +36 -0
- fastapi_fastkit/fragments/tooling/ruff_toml.j2 +16 -0
- fastapi_fastkit/utils/config_file.py +263 -0
- fastapi_fastkit/utils/main.py +111 -6
- {fastapi_fastkit-1.2.1.dist-info → fastapi_fastkit-1.4.0.dist-info}/METADATA +33 -6
- fastapi_fastkit-1.4.0.dist-info/RECORD +484 -0
- {fastapi_fastkit-1.2.1.dist-info → fastapi_fastkit-1.4.0.dist-info}/WHEEL +1 -1
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.py-tpl +0 -25
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/setup.py-tpl +0 -27
- fastapi_fastkit/fastapi_project_template/fastapi-default/setup.py-tpl +0 -22
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/setup.py-tpl +0 -22
- fastapi_fastkit/fastapi_project_template/fastapi-empty/setup.py-tpl +0 -22
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/setup.py-tpl +0 -51
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/setup.py-tpl +0 -25
- fastapi_fastkit/fastapi_project_template/fastapi-single-module/setup.py-tpl +0 -17
- fastapi_fastkit-1.2.1.dist-info/RECORD +0 -256
- /fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/{mcp → mcp_server}/__init__.py-tpl +0 -0
- {fastapi_fastkit-1.2.1.dist-info → fastapi_fastkit-1.4.0.dist-info}/entry_points.txt +0 -0
- {fastapi_fastkit-1.2.1.dist-info → fastapi_fastkit-1.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# The TemplateInspector orchestrator.
|
|
3
|
+
#
|
|
4
|
+
# The inspector generates a real project from the template (copy + convert +
|
|
5
|
+
# metadata injection), runs every check against that generated project, and
|
|
6
|
+
# cleans the temporary directory up afterwards. Individual checks live in
|
|
7
|
+
# sibling modules; this class only sequences them and owns the lifecycle.
|
|
8
|
+
#
|
|
9
|
+
# @author bnbong
|
|
10
|
+
# --------------------------------------------------------------------------
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
import time
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from types import SimpleNamespace
|
|
16
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
17
|
+
|
|
18
|
+
from fastapi_fastkit.backend.scaffolder import ProjectScaffolder, ScaffoldOptions
|
|
19
|
+
from fastapi_fastkit.core.settings import settings
|
|
20
|
+
from fastapi_fastkit.utils.logging import debug_log
|
|
21
|
+
|
|
22
|
+
from . import checks, consistency, freshness, lint, smoke, strategies
|
|
23
|
+
from .context import InspectionContext, InspectionOptions
|
|
24
|
+
from .docker import DockerCompose
|
|
25
|
+
from .fsutils import force_cleanup_directory
|
|
26
|
+
from .report import build_report, print_report
|
|
27
|
+
|
|
28
|
+
#: Metadata used to generate the throwaway project that gets inspected.
|
|
29
|
+
DUMMY_METADATA = {
|
|
30
|
+
"project_name": "test-template",
|
|
31
|
+
"author": "Template Inspector",
|
|
32
|
+
"author_email": "inspector@fastapi-fastkit.dev",
|
|
33
|
+
"description": "Test project for template inspection",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TemplateInspector:
|
|
38
|
+
"""Validate a FastAPI template by generating and exercising a project.
|
|
39
|
+
|
|
40
|
+
Uses the context manager protocol so the temporary project directory is
|
|
41
|
+
always removed, even when a check raises.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
template_path: str,
|
|
47
|
+
temp_base_dir: Optional[str] = None,
|
|
48
|
+
options: Optional[InspectionOptions] = None,
|
|
49
|
+
):
|
|
50
|
+
template_name = Path(template_path).name
|
|
51
|
+
base_dir = temp_base_dir or os.path.dirname(os.path.dirname(__file__))
|
|
52
|
+
self.ctx = InspectionContext(
|
|
53
|
+
template_path=Path(template_path),
|
|
54
|
+
temp_dir=os.path.join(base_dir, f"temp_{template_name}"),
|
|
55
|
+
options=options or InspectionOptions(),
|
|
56
|
+
)
|
|
57
|
+
self._cleanup_needed = False
|
|
58
|
+
|
|
59
|
+
# -- state exposed for callers and tests -------------------------------
|
|
60
|
+
@property
|
|
61
|
+
def template_path(self) -> Path:
|
|
62
|
+
"""The on-disk template directory being inspected."""
|
|
63
|
+
return self.ctx.template_path
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def temp_dir(self) -> str:
|
|
67
|
+
"""The generated project directory used for the inspection."""
|
|
68
|
+
return self.ctx.temp_dir
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def errors(self) -> List[str]:
|
|
72
|
+
"""Fatal findings accumulated so far."""
|
|
73
|
+
return self.ctx.errors
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def warnings(self) -> List[str]:
|
|
77
|
+
"""Non-fatal findings accumulated so far."""
|
|
78
|
+
return self.ctx.warnings
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def template_config(self) -> Optional[Dict[str, Any]]:
|
|
82
|
+
"""Parsed ``template-config.yml`` contents, when the template ships one."""
|
|
83
|
+
return self.ctx.template_config
|
|
84
|
+
|
|
85
|
+
# -- lifecycle ---------------------------------------------------------
|
|
86
|
+
def __enter__(self) -> "TemplateInspector":
|
|
87
|
+
"""Create the temporary directory and generate a project from the template."""
|
|
88
|
+
try:
|
|
89
|
+
if os.path.exists(self.ctx.temp_dir):
|
|
90
|
+
debug_log(
|
|
91
|
+
f"Cleaning up existing temp directory: {self.ctx.temp_dir}", "info"
|
|
92
|
+
)
|
|
93
|
+
try:
|
|
94
|
+
shutil.rmtree(self.ctx.temp_dir)
|
|
95
|
+
except OSError as e:
|
|
96
|
+
debug_log(
|
|
97
|
+
f"Failed to cleanup existing temp directory: {e}", "warning"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
os.makedirs(self.ctx.temp_dir, exist_ok=True)
|
|
101
|
+
# Mark for cleanup immediately: a failure below must not leak the
|
|
102
|
+
# directory that was just created.
|
|
103
|
+
self._cleanup_needed = True
|
|
104
|
+
self._generate_project()
|
|
105
|
+
|
|
106
|
+
self.ctx.template_config = self._load_template_config()
|
|
107
|
+
debug_log(f"Created temporary directory at {self.ctx.temp_dir}", "debug")
|
|
108
|
+
return self
|
|
109
|
+
except Exception as e:
|
|
110
|
+
debug_log(f"Failed to setup template inspector: {e}", "error")
|
|
111
|
+
self._cleanup()
|
|
112
|
+
raise
|
|
113
|
+
|
|
114
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
115
|
+
"""Remove the generated project directory."""
|
|
116
|
+
self._cleanup()
|
|
117
|
+
|
|
118
|
+
def _cleanup(self) -> None:
|
|
119
|
+
"""Tear down Docker services and delete the temporary directory."""
|
|
120
|
+
if not (self._cleanup_needed and os.path.exists(self.ctx.temp_dir)):
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
if self._cleanup_docker_services():
|
|
125
|
+
# Give the daemon a moment to release bind-mounted files.
|
|
126
|
+
time.sleep(3)
|
|
127
|
+
force_cleanup_directory(self.ctx.temp_dir)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
debug_log(
|
|
130
|
+
f"Warning: Unexpected error during cleanup of "
|
|
131
|
+
f"{self.ctx.temp_dir}: {e}",
|
|
132
|
+
"warning",
|
|
133
|
+
)
|
|
134
|
+
try:
|
|
135
|
+
force_cleanup_directory(self.ctx.temp_dir)
|
|
136
|
+
except Exception:
|
|
137
|
+
pass
|
|
138
|
+
finally:
|
|
139
|
+
self._cleanup_needed = False
|
|
140
|
+
|
|
141
|
+
def _cleanup_docker_services(self) -> bool:
|
|
142
|
+
"""Stop any Docker Compose stack started for this template.
|
|
143
|
+
|
|
144
|
+
Returns ``True`` when a teardown was actually attempted - templates
|
|
145
|
+
without a compose file skip the (slow) docker invocations entirely.
|
|
146
|
+
"""
|
|
147
|
+
compose_files = [
|
|
148
|
+
name
|
|
149
|
+
for name in ("docker-compose.yml", "docker-compose.yaml", "compose.yml")
|
|
150
|
+
if os.path.exists(self.ctx.temp_path(name))
|
|
151
|
+
]
|
|
152
|
+
if not compose_files:
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
DockerCompose(self.ctx.temp_dir, compose_files[0]).cleanup()
|
|
157
|
+
return True
|
|
158
|
+
except Exception as e:
|
|
159
|
+
debug_log(f"Failed to cleanup Docker services: {e}", "warning")
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
def _generate_project(self) -> None:
|
|
163
|
+
"""Produce the project to inspect through the real ``startdemo`` path.
|
|
164
|
+
|
|
165
|
+
Running the actual ``ProjectScaffolder`` (rather than re-implementing
|
|
166
|
+
copy + inject here) is the point: whatever generation does for a user
|
|
167
|
+
- placeholder substitution, the ``[tool.fastapi-fastkit]`` metadata
|
|
168
|
+
block, the template-only files it filters out - is exactly what the
|
|
169
|
+
checks then see. Virtualenv creation and dependency installation are
|
|
170
|
+
switched off, so no package manager is ever invoked.
|
|
171
|
+
|
|
172
|
+
A generation failure is propagated: there is nothing meaningful to
|
|
173
|
+
inspect when the template cannot even be deployed, and ``__enter__``
|
|
174
|
+
already removes the half-written directory.
|
|
175
|
+
"""
|
|
176
|
+
template_path = self.ctx.template_path.resolve()
|
|
177
|
+
project_dir = Path(self.ctx.temp_dir).resolve()
|
|
178
|
+
|
|
179
|
+
scaffold_settings = SimpleNamespace(
|
|
180
|
+
FASTKIT_TEMPLATE_ROOT=str(template_path.parent),
|
|
181
|
+
USER_WORKSPACE=str(project_dir.parent),
|
|
182
|
+
PACKAGE_MANAGER_CONFIG=settings.PACKAGE_MANAGER_CONFIG,
|
|
183
|
+
)
|
|
184
|
+
options = ScaffoldOptions(
|
|
185
|
+
project_name=project_dir.name,
|
|
186
|
+
author=str(DUMMY_METADATA["author"]),
|
|
187
|
+
author_email=str(DUMMY_METADATA["author_email"]),
|
|
188
|
+
description=str(DUMMY_METADATA["description"]),
|
|
189
|
+
package_manager="uv",
|
|
190
|
+
template=template_path.name,
|
|
191
|
+
create_project_folder=True,
|
|
192
|
+
with_venv=False,
|
|
193
|
+
with_install=False,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
ProjectScaffolder(scaffold_settings, options).run()
|
|
197
|
+
|
|
198
|
+
def _load_template_config(self) -> Optional[Dict[str, Any]]:
|
|
199
|
+
"""Load ``template-config.yml`` from the template, if it ships one.
|
|
200
|
+
|
|
201
|
+
The file is read from the template directory rather than the generated
|
|
202
|
+
project: it is inspector-only metadata and is deliberately not copied
|
|
203
|
+
into a user's project by the transducer.
|
|
204
|
+
"""
|
|
205
|
+
config_file = str(self.ctx.template_path / "template-config.yml-tpl")
|
|
206
|
+
if not os.path.exists(config_file):
|
|
207
|
+
# Legacy layout: some templates shipped the config without the
|
|
208
|
+
# ``-tpl`` marker, in which case it still lands in the copy.
|
|
209
|
+
config_file = self.ctx.temp_path("template-config.yml")
|
|
210
|
+
if not os.path.exists(config_file):
|
|
211
|
+
debug_log(
|
|
212
|
+
"No template-config.yml found, using default testing strategy", "info"
|
|
213
|
+
)
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
import yaml
|
|
218
|
+
except ImportError: # pragma: no cover - PyYAML is an optional extra
|
|
219
|
+
debug_log(
|
|
220
|
+
"PyYAML is not installed; ignoring template-config.yml", "warning"
|
|
221
|
+
)
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
with open(config_file, "r", encoding="utf-8") as f:
|
|
226
|
+
config = yaml.safe_load(f)
|
|
227
|
+
except (yaml.YAMLError, OSError, UnicodeDecodeError) as e:
|
|
228
|
+
debug_log(f"Failed to load template configuration: {e}", "warning")
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
if not isinstance(config, dict):
|
|
232
|
+
debug_log("Invalid template configuration format", "warning")
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
debug_log(
|
|
236
|
+
f"Loaded template configuration: {config.get('name', 'Unknown')}", "info"
|
|
237
|
+
)
|
|
238
|
+
return config
|
|
239
|
+
|
|
240
|
+
# -- checks ------------------------------------------------------------
|
|
241
|
+
def inspect_template(self) -> bool:
|
|
242
|
+
"""Run every inspection step, stopping at the first failure.
|
|
243
|
+
|
|
244
|
+
:return: True if template is valid, False otherwise
|
|
245
|
+
"""
|
|
246
|
+
check_list: List[Tuple[str, Callable[[], bool]]] = [
|
|
247
|
+
("File Structure", self._check_file_structure),
|
|
248
|
+
("File Extensions", self._check_file_extensions),
|
|
249
|
+
("Test Suite Presence", self._check_tests_present),
|
|
250
|
+
("Junk Files", self._check_no_junk_files),
|
|
251
|
+
("Dependencies", self._check_dependencies),
|
|
252
|
+
("Configuration Consistency", self._check_configuration_consistency),
|
|
253
|
+
("FastAPI Implementation", self._check_fastapi_implementation),
|
|
254
|
+
("Placeholder Substitution", self._check_no_placeholder_residue),
|
|
255
|
+
("Template Tests", self._test_template),
|
|
256
|
+
("Compile Check", self._check_compileall),
|
|
257
|
+
("Type Check", self._check_mypy),
|
|
258
|
+
("Smoke Test", self._check_smoke_test),
|
|
259
|
+
("Dependency Freshness", self._check_dependency_freshness),
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
for check_name, check_func in check_list:
|
|
263
|
+
debug_log(f"Running check: {check_name}", "info")
|
|
264
|
+
if not check_func():
|
|
265
|
+
debug_log(f"Check failed: {check_name}", "error")
|
|
266
|
+
return False
|
|
267
|
+
debug_log(f"Check passed: {check_name}", "info")
|
|
268
|
+
|
|
269
|
+
return True
|
|
270
|
+
|
|
271
|
+
def _check_file_structure(self) -> bool:
|
|
272
|
+
return checks.check_file_structure(self.ctx)
|
|
273
|
+
|
|
274
|
+
def _check_file_extensions(self) -> bool:
|
|
275
|
+
return checks.check_file_extensions(self.ctx)
|
|
276
|
+
|
|
277
|
+
def _check_dependencies(self) -> bool:
|
|
278
|
+
return checks.check_dependencies(self.ctx)
|
|
279
|
+
|
|
280
|
+
def _check_fastapi_implementation(self) -> bool:
|
|
281
|
+
return checks.check_fastapi_implementation(self.ctx)
|
|
282
|
+
|
|
283
|
+
def _check_tests_present(self) -> bool:
|
|
284
|
+
return checks.check_tests_present(self.ctx)
|
|
285
|
+
|
|
286
|
+
def _check_no_junk_files(self) -> bool:
|
|
287
|
+
return checks.check_no_junk_files(self.ctx)
|
|
288
|
+
|
|
289
|
+
def _check_no_placeholder_residue(self) -> bool:
|
|
290
|
+
return checks.check_no_placeholder_residue(self.ctx)
|
|
291
|
+
|
|
292
|
+
def _check_configuration_consistency(self) -> bool:
|
|
293
|
+
return consistency.check_configuration_consistency(self.ctx)
|
|
294
|
+
|
|
295
|
+
def _check_compileall(self) -> bool:
|
|
296
|
+
return lint.check_compileall(self.ctx)
|
|
297
|
+
|
|
298
|
+
def _check_mypy(self) -> bool:
|
|
299
|
+
return lint.check_mypy(self.ctx)
|
|
300
|
+
|
|
301
|
+
def _check_smoke_test(self) -> bool:
|
|
302
|
+
return smoke.check_smoke_test(self.ctx)
|
|
303
|
+
|
|
304
|
+
def _check_dependency_freshness(self) -> bool:
|
|
305
|
+
return freshness.check_dependency_freshness(self.ctx)
|
|
306
|
+
|
|
307
|
+
def _test_template(self) -> bool:
|
|
308
|
+
return strategies.run_template_tests(self.ctx)
|
|
309
|
+
|
|
310
|
+
def get_report(self) -> Dict[str, Any]:
|
|
311
|
+
"""
|
|
312
|
+
Get inspection report with errors and warnings.
|
|
313
|
+
|
|
314
|
+
:return: Dictionary containing inspection results
|
|
315
|
+
"""
|
|
316
|
+
return build_report(self.ctx)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def inspect_fastapi_template(
|
|
320
|
+
template_path: str,
|
|
321
|
+
temp_base_dir: Optional[str] = None,
|
|
322
|
+
options: Optional[InspectionOptions] = None,
|
|
323
|
+
) -> Dict[str, Any]:
|
|
324
|
+
"""
|
|
325
|
+
Convenience function to inspect a FastAPI template.
|
|
326
|
+
|
|
327
|
+
:param template_path: Path to the template to inspect
|
|
328
|
+
:param temp_base_dir: Base directory for temporary files (defaults to backend directory)
|
|
329
|
+
:param options: Optional switches (offline mode, smoke test, mypy, ...)
|
|
330
|
+
:return: Inspection report dictionary
|
|
331
|
+
"""
|
|
332
|
+
template_name = Path(template_path).name
|
|
333
|
+
debug_log(
|
|
334
|
+
f"Starting template inspection for {template_name} at {template_path}", "info"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
with TemplateInspector(template_path, temp_base_dir, options) as inspector:
|
|
338
|
+
is_valid = inspector.inspect_template()
|
|
339
|
+
report = inspector.get_report()
|
|
340
|
+
print_report(inspector.ctx, is_valid)
|
|
341
|
+
|
|
342
|
+
debug_log(
|
|
343
|
+
f"Template inspection completed for {template_name}. Valid: {is_valid}, "
|
|
344
|
+
f"Errors: {len(report['errors'])}, Warnings: {len(report['warnings'])}",
|
|
345
|
+
"info",
|
|
346
|
+
)
|
|
347
|
+
return report
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# Docker Compose orchestration for templates that declare requires_docker.
|
|
3
|
+
#
|
|
4
|
+
# ``DockerCompose`` owns every ``docker``/``docker-compose`` invocation so the
|
|
5
|
+
# test strategies stay free of process plumbing.
|
|
6
|
+
#
|
|
7
|
+
# @author bnbong
|
|
8
|
+
# --------------------------------------------------------------------------
|
|
9
|
+
import json
|
|
10
|
+
import subprocess
|
|
11
|
+
import time
|
|
12
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
13
|
+
|
|
14
|
+
from fastapi_fastkit.utils.logging import debug_log
|
|
15
|
+
|
|
16
|
+
COMPOSE_COMMAND = "docker-compose"
|
|
17
|
+
SHORT_TIMEOUT = 10
|
|
18
|
+
STATUS_TIMEOUT = 30
|
|
19
|
+
CLEANUP_TIMEOUT = 60
|
|
20
|
+
TEST_TIMEOUT = 300
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DockerCompose:
|
|
24
|
+
"""Thin wrapper around the docker-compose CLI for one project directory."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, project_dir: str, compose_file: str = "docker-compose.yml"):
|
|
27
|
+
self.project_dir = project_dir
|
|
28
|
+
self.compose_file = compose_file
|
|
29
|
+
|
|
30
|
+
def _run(
|
|
31
|
+
self, args: Sequence[str], timeout: int
|
|
32
|
+
) -> Optional[subprocess.CompletedProcess[str]]:
|
|
33
|
+
"""Run a command in the project directory, returning ``None`` on failure."""
|
|
34
|
+
try:
|
|
35
|
+
return subprocess.run(
|
|
36
|
+
list(args),
|
|
37
|
+
cwd=self.project_dir,
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
timeout=timeout,
|
|
41
|
+
)
|
|
42
|
+
except (subprocess.TimeoutExpired, OSError) as e:
|
|
43
|
+
debug_log(f"Command {' '.join(args)} failed: {e}", "warning")
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
def _compose(
|
|
47
|
+
self, args: Sequence[str], timeout: int
|
|
48
|
+
) -> Optional[subprocess.CompletedProcess[str]]:
|
|
49
|
+
"""Run a docker-compose subcommand against the configured compose file."""
|
|
50
|
+
return self._run(
|
|
51
|
+
[COMPOSE_COMMAND, "-f", self.compose_file, *args], timeout=timeout
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def is_available() -> bool:
|
|
56
|
+
"""Check that both docker and docker-compose respond."""
|
|
57
|
+
for command in (["docker", "--version"], [COMPOSE_COMMAND, "--version"]):
|
|
58
|
+
try:
|
|
59
|
+
result = subprocess.run(
|
|
60
|
+
command, capture_output=True, text=True, timeout=SHORT_TIMEOUT
|
|
61
|
+
)
|
|
62
|
+
except (subprocess.TimeoutExpired, OSError):
|
|
63
|
+
return False
|
|
64
|
+
if result.returncode != 0:
|
|
65
|
+
return False
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
def _services(self, timeout: int) -> List[Dict[str, Any]]:
|
|
69
|
+
"""Return the parsed ``docker-compose ps --format json`` entries."""
|
|
70
|
+
result = self._compose(["ps", "--format", "json"], timeout=timeout)
|
|
71
|
+
if result is None or result.returncode != 0:
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
services: List[Dict[str, Any]] = []
|
|
75
|
+
for line in result.stdout.strip().split("\n"):
|
|
76
|
+
if not line.strip():
|
|
77
|
+
continue
|
|
78
|
+
try:
|
|
79
|
+
entry = json.loads(line)
|
|
80
|
+
except json.JSONDecodeError:
|
|
81
|
+
continue
|
|
82
|
+
if isinstance(entry, dict):
|
|
83
|
+
services.append(entry)
|
|
84
|
+
return services
|
|
85
|
+
|
|
86
|
+
def containers_running(self) -> bool:
|
|
87
|
+
"""Check whether every container of the project is already up."""
|
|
88
|
+
result = self._compose(["ps", "-q"], timeout=STATUS_TIMEOUT)
|
|
89
|
+
if result is None or result.returncode != 0:
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
container_ids = [line.strip() for line in result.stdout.strip().split("\n")]
|
|
93
|
+
container_ids = [cid for cid in container_ids if cid]
|
|
94
|
+
if not container_ids:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
for container_id in container_ids:
|
|
98
|
+
status = self._run(
|
|
99
|
+
["docker", "inspect", "-f", "{{.State.Running}}", container_id],
|
|
100
|
+
timeout=SHORT_TIMEOUT,
|
|
101
|
+
)
|
|
102
|
+
if status is None or status.returncode != 0:
|
|
103
|
+
return False
|
|
104
|
+
if status.stdout.strip() != "true":
|
|
105
|
+
return False
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
def up(self, timeout: int) -> subprocess.CompletedProcess[str]:
|
|
109
|
+
"""Build and start the compose services in the background."""
|
|
110
|
+
result = self._compose(["up", "-d", "--build"], timeout=timeout)
|
|
111
|
+
if result is None:
|
|
112
|
+
raise subprocess.TimeoutExpired(COMPOSE_COMMAND, timeout)
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
def wait_until_healthy(self, timeout: int) -> None:
|
|
116
|
+
"""Block until every service reports ``running`` or ``timeout`` elapses."""
|
|
117
|
+
debug_log("Waiting for services to be healthy...", "info")
|
|
118
|
+
deadline = time.time() + timeout
|
|
119
|
+
|
|
120
|
+
while time.time() < deadline:
|
|
121
|
+
services = self._services(timeout=SHORT_TIMEOUT)
|
|
122
|
+
if services and all(
|
|
123
|
+
service.get("State") == "running" for service in services
|
|
124
|
+
):
|
|
125
|
+
debug_log("All services are running", "info")
|
|
126
|
+
# Give the application a moment to finish booting.
|
|
127
|
+
time.sleep(10)
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
debug_log("Services not ready yet, waiting...", "info")
|
|
131
|
+
time.sleep(5)
|
|
132
|
+
|
|
133
|
+
debug_log(
|
|
134
|
+
f"Services did not become healthy within {timeout} seconds", "warning"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def verify_services_running(self) -> Optional[str]:
|
|
138
|
+
"""Return ``None`` when db and app are up, else a descriptive error."""
|
|
139
|
+
services = self._services(timeout=STATUS_TIMEOUT)
|
|
140
|
+
if not services:
|
|
141
|
+
return "Failed to check service status"
|
|
142
|
+
|
|
143
|
+
db_running = False
|
|
144
|
+
app_running = False
|
|
145
|
+
for service in services:
|
|
146
|
+
name = str(service.get("Name", ""))
|
|
147
|
+
state = str(service.get("State", ""))
|
|
148
|
+
debug_log(f"Service {name}: {state}", "info")
|
|
149
|
+
if "db" in name and state == "running":
|
|
150
|
+
db_running = True
|
|
151
|
+
elif "app" in name and state == "running":
|
|
152
|
+
app_running = True
|
|
153
|
+
|
|
154
|
+
if not db_running:
|
|
155
|
+
return "Database service is not running"
|
|
156
|
+
if not app_running:
|
|
157
|
+
logs = self._compose(
|
|
158
|
+
["logs", "--tail", "50", "app"], timeout=STATUS_TIMEOUT
|
|
159
|
+
)
|
|
160
|
+
if logs is not None and logs.returncode == 0:
|
|
161
|
+
return (
|
|
162
|
+
"Application service is not running. " f"Logs: {logs.stdout[-500:]}"
|
|
163
|
+
)
|
|
164
|
+
return "Application service is not running"
|
|
165
|
+
|
|
166
|
+
debug_log("All required services are running", "info")
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
def exec_tests(
|
|
170
|
+
self, use_test_script: bool
|
|
171
|
+
) -> Optional[subprocess.CompletedProcess[str]]:
|
|
172
|
+
"""Run the template's tests inside the app container."""
|
|
173
|
+
if use_test_script:
|
|
174
|
+
command = ["exec", "-T", "app", "bash", "scripts/test.sh"]
|
|
175
|
+
else:
|
|
176
|
+
command = ["exec", "-T", "app", "python", "-m", "pytest", "tests/", "-v"]
|
|
177
|
+
return self._compose(command, timeout=TEST_TIMEOUT)
|
|
178
|
+
|
|
179
|
+
def cleanup(self) -> None:
|
|
180
|
+
"""Tear down services and volumes, ignoring any failure."""
|
|
181
|
+
debug_log("Cleaning up Docker services", "info")
|
|
182
|
+
self._run(
|
|
183
|
+
[COMPOSE_COMMAND, "down", "-v", "--remove-orphans"], timeout=CLEANUP_TIMEOUT
|
|
184
|
+
)
|
|
185
|
+
self._run(["docker", "system", "prune", "-f"], timeout=STATUS_TIMEOUT)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# Dependency freshness reporting.
|
|
3
|
+
#
|
|
4
|
+
# Each pinned dependency is compared against the latest release published on
|
|
5
|
+
# PyPI. Findings are warnings only - a template lagging behind upstream is
|
|
6
|
+
# worth surfacing but never a reason to fail an inspection. Network failures
|
|
7
|
+
# are skipped silently, and ``--offline`` disables the lookups entirely.
|
|
8
|
+
#
|
|
9
|
+
# @author bnbong
|
|
10
|
+
# --------------------------------------------------------------------------
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
import tomllib
|
|
14
|
+
import urllib.error
|
|
15
|
+
import urllib.request
|
|
16
|
+
from typing import Dict, Optional, Tuple
|
|
17
|
+
|
|
18
|
+
from fastapi_fastkit.backend.package_managers.poetry_manager import (
|
|
19
|
+
_parse_pip_requirement,
|
|
20
|
+
)
|
|
21
|
+
from fastapi_fastkit.utils.logging import debug_log
|
|
22
|
+
|
|
23
|
+
from .context import InspectionContext
|
|
24
|
+
|
|
25
|
+
PYPI_JSON_URL = "https://pypi.org/pypi/{package}/json"
|
|
26
|
+
PYPI_TIMEOUT = 5
|
|
27
|
+
_VERSION_RE = re.compile(r"(\d+)\.(\d+)")
|
|
28
|
+
#: Only exact/lower-bound pins carry a meaningful "current version".
|
|
29
|
+
_PIN_OPERATORS = ("==", ">=", "~=")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _pinned_version(requirement: str) -> Tuple[str, str]:
|
|
33
|
+
"""Return ``(package name, pinned version)`` for a requirement string.
|
|
34
|
+
|
|
35
|
+
Version is empty when the requirement is unpinned or uses an operator
|
|
36
|
+
that does not identify a concrete current version (e.g. ``<2``).
|
|
37
|
+
"""
|
|
38
|
+
name, _extras, version_spec, _marker = _parse_pip_requirement(requirement)
|
|
39
|
+
if not name or not version_spec:
|
|
40
|
+
return name.lower() if name else "", ""
|
|
41
|
+
for operator in _PIN_OPERATORS:
|
|
42
|
+
if version_spec.startswith(operator):
|
|
43
|
+
return name.lower(), version_spec[len(operator) :].strip()
|
|
44
|
+
return name.lower(), ""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _parse_version(version: str) -> Optional[Tuple[int, int]]:
|
|
48
|
+
"""Extract a (major, minor) pair from a version string."""
|
|
49
|
+
match = _VERSION_RE.match(version.strip())
|
|
50
|
+
if not match:
|
|
51
|
+
return None
|
|
52
|
+
return int(match.group(1)), int(match.group(2))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def fetch_latest_version(package: str) -> Optional[str]:
|
|
56
|
+
"""Return the latest version of ``package`` on PyPI, or ``None`` on failure."""
|
|
57
|
+
url = PYPI_JSON_URL.format(package=package)
|
|
58
|
+
try:
|
|
59
|
+
with urllib.request.urlopen(url, timeout=PYPI_TIMEOUT) as response:
|
|
60
|
+
payload = json.loads(response.read().decode("utf-8"))
|
|
61
|
+
except (urllib.error.URLError, OSError, ValueError, TimeoutError) as e:
|
|
62
|
+
debug_log(f"PyPI lookup failed for {package}: {e}", "debug")
|
|
63
|
+
return None
|
|
64
|
+
version = payload.get("info", {}).get("version")
|
|
65
|
+
return str(version) if version else None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _collect_pins(ctx: InspectionContext) -> Dict[str, str]:
|
|
69
|
+
"""Map package name -> pinned version from the template's metadata files."""
|
|
70
|
+
pins: Dict[str, str] = {}
|
|
71
|
+
|
|
72
|
+
requirements_path = ctx.template_path / "requirements.txt-tpl"
|
|
73
|
+
if requirements_path.exists():
|
|
74
|
+
try:
|
|
75
|
+
with open(requirements_path, encoding="utf-8") as f:
|
|
76
|
+
lines = f.read().splitlines()
|
|
77
|
+
except (OSError, UnicodeDecodeError):
|
|
78
|
+
lines = []
|
|
79
|
+
for line in lines:
|
|
80
|
+
line = line.strip()
|
|
81
|
+
if not line or line.startswith("#"):
|
|
82
|
+
continue
|
|
83
|
+
name, version = _pinned_version(line)
|
|
84
|
+
if name and version:
|
|
85
|
+
pins.setdefault(name, version)
|
|
86
|
+
|
|
87
|
+
pyproject_path = ctx.template_path / "pyproject.toml-tpl"
|
|
88
|
+
if pyproject_path.exists():
|
|
89
|
+
try:
|
|
90
|
+
with open(pyproject_path, "rb") as f:
|
|
91
|
+
data = tomllib.load(f)
|
|
92
|
+
except (OSError, ValueError):
|
|
93
|
+
data = {}
|
|
94
|
+
for dep in data.get("project", {}).get("dependencies", []) or []:
|
|
95
|
+
if not isinstance(dep, str):
|
|
96
|
+
continue
|
|
97
|
+
name, version = _pinned_version(dep)
|
|
98
|
+
if name and version:
|
|
99
|
+
pins.setdefault(name, version)
|
|
100
|
+
|
|
101
|
+
return pins
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def check_dependency_freshness(ctx: InspectionContext) -> bool:
|
|
105
|
+
"""Warn about dependencies that lag behind their latest PyPI release.
|
|
106
|
+
|
|
107
|
+
Always returns ``True``: freshness is advisory.
|
|
108
|
+
"""
|
|
109
|
+
if ctx.options.offline:
|
|
110
|
+
debug_log("Offline mode - skipping dependency freshness check", "info")
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
pins = _collect_pins(ctx)
|
|
114
|
+
if not pins:
|
|
115
|
+
debug_log("No pinned dependencies found for freshness check", "info")
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
checked = 0
|
|
119
|
+
for package, pinned in sorted(pins.items()):
|
|
120
|
+
latest = fetch_latest_version(package)
|
|
121
|
+
if latest is None:
|
|
122
|
+
continue
|
|
123
|
+
checked += 1
|
|
124
|
+
current_parts = _parse_version(pinned)
|
|
125
|
+
latest_parts = _parse_version(latest)
|
|
126
|
+
if current_parts is None or latest_parts is None:
|
|
127
|
+
continue
|
|
128
|
+
if latest_parts[0] > current_parts[0]:
|
|
129
|
+
ctx.add_warning(
|
|
130
|
+
f"{package} is {latest_parts[0] - current_parts[0]} major "
|
|
131
|
+
f"version(s) behind: pinned {pinned}, latest {latest}"
|
|
132
|
+
)
|
|
133
|
+
elif latest_parts[0] == current_parts[0] and latest_parts[1] > current_parts[1]:
|
|
134
|
+
ctx.add_warning(
|
|
135
|
+
f"{package} is {latest_parts[1] - current_parts[1]} minor "
|
|
136
|
+
f"version(s) behind: pinned {pinned}, latest {latest}"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if checked == 0:
|
|
140
|
+
debug_log(
|
|
141
|
+
"Dependency freshness check skipped - no PyPI responses received", "info"
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
debug_log(f"Dependency freshness check completed ({checked} packages)", "info")
|
|
145
|
+
return True
|