solokit 0.1.1__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.
- solokit/__init__.py +10 -0
- solokit/__version__.py +3 -0
- solokit/cli.py +374 -0
- solokit/core/__init__.py +1 -0
- solokit/core/cache.py +102 -0
- solokit/core/command_runner.py +278 -0
- solokit/core/config.py +453 -0
- solokit/core/config_validator.py +204 -0
- solokit/core/constants.py +291 -0
- solokit/core/error_formatter.py +279 -0
- solokit/core/error_handlers.py +346 -0
- solokit/core/exceptions.py +1567 -0
- solokit/core/file_ops.py +309 -0
- solokit/core/logging_config.py +166 -0
- solokit/core/output.py +99 -0
- solokit/core/performance.py +57 -0
- solokit/core/protocols.py +141 -0
- solokit/core/types.py +312 -0
- solokit/deployment/__init__.py +1 -0
- solokit/deployment/executor.py +411 -0
- solokit/git/__init__.py +1 -0
- solokit/git/integration.py +619 -0
- solokit/init/__init__.py +41 -0
- solokit/init/claude_commands_installer.py +87 -0
- solokit/init/dependency_installer.py +313 -0
- solokit/init/docs_structure.py +90 -0
- solokit/init/env_generator.py +160 -0
- solokit/init/environment_validator.py +334 -0
- solokit/init/git_hooks_installer.py +71 -0
- solokit/init/git_setup.py +188 -0
- solokit/init/gitignore_updater.py +195 -0
- solokit/init/initial_commit.py +145 -0
- solokit/init/initial_scans.py +109 -0
- solokit/init/orchestrator.py +246 -0
- solokit/init/readme_generator.py +207 -0
- solokit/init/session_structure.py +239 -0
- solokit/init/template_installer.py +424 -0
- solokit/learning/__init__.py +1 -0
- solokit/learning/archiver.py +115 -0
- solokit/learning/categorizer.py +126 -0
- solokit/learning/curator.py +428 -0
- solokit/learning/extractor.py +352 -0
- solokit/learning/reporter.py +351 -0
- solokit/learning/repository.py +254 -0
- solokit/learning/similarity.py +342 -0
- solokit/learning/validator.py +144 -0
- solokit/project/__init__.py +1 -0
- solokit/project/init.py +1162 -0
- solokit/project/stack.py +436 -0
- solokit/project/sync_plugin.py +438 -0
- solokit/project/tree.py +375 -0
- solokit/quality/__init__.py +1 -0
- solokit/quality/api_validator.py +424 -0
- solokit/quality/checkers/__init__.py +25 -0
- solokit/quality/checkers/base.py +114 -0
- solokit/quality/checkers/context7.py +221 -0
- solokit/quality/checkers/custom.py +162 -0
- solokit/quality/checkers/deployment.py +323 -0
- solokit/quality/checkers/documentation.py +179 -0
- solokit/quality/checkers/formatting.py +161 -0
- solokit/quality/checkers/integration.py +394 -0
- solokit/quality/checkers/linting.py +159 -0
- solokit/quality/checkers/security.py +261 -0
- solokit/quality/checkers/spec_completeness.py +127 -0
- solokit/quality/checkers/tests.py +184 -0
- solokit/quality/env_validator.py +306 -0
- solokit/quality/gates.py +655 -0
- solokit/quality/reporters/__init__.py +10 -0
- solokit/quality/reporters/base.py +25 -0
- solokit/quality/reporters/console.py +98 -0
- solokit/quality/reporters/json_reporter.py +34 -0
- solokit/quality/results.py +98 -0
- solokit/session/__init__.py +1 -0
- solokit/session/briefing/__init__.py +245 -0
- solokit/session/briefing/documentation_loader.py +53 -0
- solokit/session/briefing/formatter.py +476 -0
- solokit/session/briefing/git_context.py +282 -0
- solokit/session/briefing/learning_loader.py +212 -0
- solokit/session/briefing/milestone_builder.py +78 -0
- solokit/session/briefing/orchestrator.py +137 -0
- solokit/session/briefing/stack_detector.py +51 -0
- solokit/session/briefing/tree_generator.py +52 -0
- solokit/session/briefing/work_item_loader.py +209 -0
- solokit/session/briefing.py +353 -0
- solokit/session/complete.py +1188 -0
- solokit/session/status.py +246 -0
- solokit/session/validate.py +452 -0
- solokit/templates/.claude/commands/end.md +109 -0
- solokit/templates/.claude/commands/init.md +159 -0
- solokit/templates/.claude/commands/learn-curate.md +88 -0
- solokit/templates/.claude/commands/learn-search.md +62 -0
- solokit/templates/.claude/commands/learn-show.md +69 -0
- solokit/templates/.claude/commands/learn.md +136 -0
- solokit/templates/.claude/commands/start.md +114 -0
- solokit/templates/.claude/commands/status.md +22 -0
- solokit/templates/.claude/commands/validate.md +27 -0
- solokit/templates/.claude/commands/work-delete.md +119 -0
- solokit/templates/.claude/commands/work-graph.md +139 -0
- solokit/templates/.claude/commands/work-list.md +26 -0
- solokit/templates/.claude/commands/work-new.md +114 -0
- solokit/templates/.claude/commands/work-next.md +25 -0
- solokit/templates/.claude/commands/work-show.md +24 -0
- solokit/templates/.claude/commands/work-update.md +141 -0
- solokit/templates/CHANGELOG.md +17 -0
- solokit/templates/WORK_ITEM_TYPES.md +141 -0
- solokit/templates/__init__.py +1 -0
- solokit/templates/bug_spec.md +217 -0
- solokit/templates/config.schema.json +150 -0
- solokit/templates/dashboard_refine/base/.gitignore +36 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/layout.tsx +22 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/page.tsx +68 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/users/page.tsx +77 -0
- solokit/templates/dashboard_refine/base/app/globals.css +60 -0
- solokit/templates/dashboard_refine/base/app/layout.tsx +23 -0
- solokit/templates/dashboard_refine/base/app/page.tsx +9 -0
- solokit/templates/dashboard_refine/base/components/client-refine-wrapper.tsx +21 -0
- solokit/templates/dashboard_refine/base/components/layout/header.tsx +44 -0
- solokit/templates/dashboard_refine/base/components/layout/sidebar.tsx +82 -0
- solokit/templates/dashboard_refine/base/components/ui/button.tsx +53 -0
- solokit/templates/dashboard_refine/base/components/ui/card.tsx +78 -0
- solokit/templates/dashboard_refine/base/components/ui/table.tsx +116 -0
- solokit/templates/dashboard_refine/base/components.json +16 -0
- solokit/templates/dashboard_refine/base/lib/refine.tsx +65 -0
- solokit/templates/dashboard_refine/base/lib/utils.ts +13 -0
- solokit/templates/dashboard_refine/base/next.config.ts +10 -0
- solokit/templates/dashboard_refine/base/package.json.template +40 -0
- solokit/templates/dashboard_refine/base/postcss.config.mjs +8 -0
- solokit/templates/dashboard_refine/base/providers/refine-provider.tsx +26 -0
- solokit/templates/dashboard_refine/base/tailwind.config.ts +57 -0
- solokit/templates/dashboard_refine/base/tsconfig.json +27 -0
- solokit/templates/dashboard_refine/docker/Dockerfile +57 -0
- solokit/templates/dashboard_refine/docker/docker-compose.prod.yml +31 -0
- solokit/templates/dashboard_refine/docker/docker-compose.yml +21 -0
- solokit/templates/dashboard_refine/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/dashboard_refine/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/dashboard_refine/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/dashboard_refine/tier-1-essential/package.json.tier1.template +57 -0
- solokit/templates/dashboard_refine/tier-1-essential/tests/setup.ts +26 -0
- solokit/templates/dashboard_refine/tier-1-essential/tests/unit/example.test.tsx +73 -0
- solokit/templates/dashboard_refine/tier-2-standard/package.json.tier2.template +62 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/eslint.config.mjs +22 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/package.json.tier3.template +79 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/stryker.conf.json +38 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/e2e/dashboard.spec.ts +88 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/e2e/user-management.spec.ts +102 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/integration/dashboard.test.tsx +90 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/type-coverage.json +16 -0
- solokit/templates/dashboard_refine/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/dashboard_refine/tier-4-production/k6/dashboard-load-test.js +70 -0
- solokit/templates/dashboard_refine/tier-4-production/next.config.ts +46 -0
- solokit/templates/dashboard_refine/tier-4-production/package.json.tier4.template +89 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/deployment_spec.md +500 -0
- solokit/templates/feature_spec.md +248 -0
- solokit/templates/fullstack_nextjs/base/.gitignore +36 -0
- solokit/templates/fullstack_nextjs/base/app/api/example/route.ts +65 -0
- solokit/templates/fullstack_nextjs/base/app/globals.css +27 -0
- solokit/templates/fullstack_nextjs/base/app/layout.tsx +20 -0
- solokit/templates/fullstack_nextjs/base/app/page.tsx +32 -0
- solokit/templates/fullstack_nextjs/base/components/example-component.tsx +20 -0
- solokit/templates/fullstack_nextjs/base/lib/prisma.ts +17 -0
- solokit/templates/fullstack_nextjs/base/lib/utils.ts +13 -0
- solokit/templates/fullstack_nextjs/base/lib/validations.ts +20 -0
- solokit/templates/fullstack_nextjs/base/next.config.ts +7 -0
- solokit/templates/fullstack_nextjs/base/package.json.template +32 -0
- solokit/templates/fullstack_nextjs/base/postcss.config.mjs +8 -0
- solokit/templates/fullstack_nextjs/base/prisma/schema.prisma +21 -0
- solokit/templates/fullstack_nextjs/base/tailwind.config.ts +19 -0
- solokit/templates/fullstack_nextjs/base/tsconfig.json +27 -0
- solokit/templates/fullstack_nextjs/docker/Dockerfile +60 -0
- solokit/templates/fullstack_nextjs/docker/docker-compose.prod.yml +57 -0
- solokit/templates/fullstack_nextjs/docker/docker-compose.yml +47 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/package.json.tier1.template +48 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/api/example.test.ts +88 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/setup.ts +22 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/unit/example.test.tsx +22 -0
- solokit/templates/fullstack_nextjs/tier-2-standard/package.json.tier2.template +52 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/eslint.config.mjs +39 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/package.json.tier3.template +68 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/stryker.conf.json +33 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/tests/e2e/flow.spec.ts +59 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/tests/integration/api.test.ts +165 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/type-coverage.json +12 -0
- solokit/templates/fullstack_nextjs/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/fullstack_nextjs/tier-4-production/k6/load-test.js +45 -0
- solokit/templates/fullstack_nextjs/tier-4-production/next.config.ts +46 -0
- solokit/templates/fullstack_nextjs/tier-4-production/package.json.tier4.template +77 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/git-hooks/prepare-commit-msg +24 -0
- solokit/templates/integration_test_spec.md +363 -0
- solokit/templates/learnings.json +15 -0
- solokit/templates/ml_ai_fastapi/base/.gitignore +104 -0
- solokit/templates/ml_ai_fastapi/base/alembic/env.py +96 -0
- solokit/templates/ml_ai_fastapi/base/alembic.ini +114 -0
- solokit/templates/ml_ai_fastapi/base/pyproject.toml.template +91 -0
- solokit/templates/ml_ai_fastapi/base/requirements.txt.template +28 -0
- solokit/templates/ml_ai_fastapi/base/src/__init__.py +5 -0
- solokit/templates/ml_ai_fastapi/base/src/api/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/api/dependencies.py +20 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/example.py +134 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/health.py +66 -0
- solokit/templates/ml_ai_fastapi/base/src/core/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/core/config.py +64 -0
- solokit/templates/ml_ai_fastapi/base/src/core/database.py +50 -0
- solokit/templates/ml_ai_fastapi/base/src/main.py +64 -0
- solokit/templates/ml_ai_fastapi/base/src/models/__init__.py +7 -0
- solokit/templates/ml_ai_fastapi/base/src/models/example.py +61 -0
- solokit/templates/ml_ai_fastapi/base/src/services/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/services/example.py +115 -0
- solokit/templates/ml_ai_fastapi/docker/Dockerfile +59 -0
- solokit/templates/ml_ai_fastapi/docker/docker-compose.prod.yml +112 -0
- solokit/templates/ml_ai_fastapi/docker/docker-compose.yml +77 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pyproject.toml.tier1.template +112 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pyrightconfig.json +41 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pytest.ini +69 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/requirements-dev.txt +17 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/ruff.toml +81 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/conftest.py +72 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/test_main.py +49 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/unit/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/unit/test_example.py +113 -0
- solokit/templates/ml_ai_fastapi/tier-2-standard/pyproject.toml.tier2.template +130 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/locustfile.py +99 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/mutmut_config.py +53 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/pyproject.toml.tier3.template +150 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/conftest.py +74 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/test_api.py +131 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/pyproject.toml.tier4.template +162 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/requirements-prod.txt +25 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/api/routes/metrics.py +19 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/logging.py +74 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/monitoring.py +68 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/sentry.py +66 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/logging.py +79 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/tracing.py +60 -0
- solokit/templates/refactor_spec.md +287 -0
- solokit/templates/saas_t3/base/.gitignore +36 -0
- solokit/templates/saas_t3/base/app/api/trpc/[trpc]/route.ts +33 -0
- solokit/templates/saas_t3/base/app/globals.css +27 -0
- solokit/templates/saas_t3/base/app/layout.tsx +23 -0
- solokit/templates/saas_t3/base/app/page.tsx +31 -0
- solokit/templates/saas_t3/base/lib/api.tsx +77 -0
- solokit/templates/saas_t3/base/lib/utils.ts +13 -0
- solokit/templates/saas_t3/base/next.config.ts +7 -0
- solokit/templates/saas_t3/base/package.json.template +38 -0
- solokit/templates/saas_t3/base/postcss.config.mjs +8 -0
- solokit/templates/saas_t3/base/prisma/schema.prisma +20 -0
- solokit/templates/saas_t3/base/server/api/root.ts +19 -0
- solokit/templates/saas_t3/base/server/api/routers/example.ts +28 -0
- solokit/templates/saas_t3/base/server/api/trpc.ts +52 -0
- solokit/templates/saas_t3/base/server/db.ts +17 -0
- solokit/templates/saas_t3/base/tailwind.config.ts +19 -0
- solokit/templates/saas_t3/base/tsconfig.json +27 -0
- solokit/templates/saas_t3/docker/Dockerfile +60 -0
- solokit/templates/saas_t3/docker/docker-compose.prod.yml +59 -0
- solokit/templates/saas_t3/docker/docker-compose.yml +49 -0
- solokit/templates/saas_t3/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/saas_t3/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/saas_t3/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/saas_t3/tier-1-essential/package.json.tier1.template +54 -0
- solokit/templates/saas_t3/tier-1-essential/tests/setup.ts +22 -0
- solokit/templates/saas_t3/tier-1-essential/tests/unit/example.test.tsx +24 -0
- solokit/templates/saas_t3/tier-2-standard/package.json.tier2.template +58 -0
- solokit/templates/saas_t3/tier-3-comprehensive/eslint.config.mjs +39 -0
- solokit/templates/saas_t3/tier-3-comprehensive/package.json.tier3.template +74 -0
- solokit/templates/saas_t3/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/saas_t3/tier-3-comprehensive/stryker.conf.json +34 -0
- solokit/templates/saas_t3/tier-3-comprehensive/tests/e2e/home.spec.ts +41 -0
- solokit/templates/saas_t3/tier-3-comprehensive/tests/integration/api.test.ts +44 -0
- solokit/templates/saas_t3/tier-3-comprehensive/type-coverage.json +12 -0
- solokit/templates/saas_t3/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/saas_t3/tier-4-production/k6/load-test.js +51 -0
- solokit/templates/saas_t3/tier-4-production/next.config.ts +46 -0
- solokit/templates/saas_t3/tier-4-production/package.json.tier4.template +83 -0
- solokit/templates/saas_t3/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/saas_t3/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/saas_t3/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/saas_t3/tier-4-production/vercel.json +37 -0
- solokit/templates/security_spec.md +287 -0
- solokit/templates/stack-versions.yaml +617 -0
- solokit/templates/status_update.json +6 -0
- solokit/templates/template-registry.json +257 -0
- solokit/templates/work_items.json +11 -0
- solokit/testing/__init__.py +1 -0
- solokit/testing/integration_runner.py +550 -0
- solokit/testing/performance.py +637 -0
- solokit/visualization/__init__.py +1 -0
- solokit/visualization/dependency_graph.py +788 -0
- solokit/work_items/__init__.py +1 -0
- solokit/work_items/creator.py +217 -0
- solokit/work_items/delete.py +264 -0
- solokit/work_items/get_dependencies.py +185 -0
- solokit/work_items/get_dependents.py +113 -0
- solokit/work_items/get_metadata.py +121 -0
- solokit/work_items/get_next_recommendations.py +133 -0
- solokit/work_items/manager.py +235 -0
- solokit/work_items/milestones.py +137 -0
- solokit/work_items/query.py +376 -0
- solokit/work_items/repository.py +267 -0
- solokit/work_items/scheduler.py +184 -0
- solokit/work_items/spec_parser.py +838 -0
- solokit/work_items/spec_validator.py +493 -0
- solokit/work_items/updater.py +157 -0
- solokit/work_items/validator.py +205 -0
- solokit-0.1.1.dist-info/METADATA +640 -0
- solokit-0.1.1.dist-info/RECORD +323 -0
- solokit-0.1.1.dist-info/WHEEL +5 -0
- solokit-0.1.1.dist-info/entry_points.txt +2 -0
- solokit-0.1.1.dist-info/licenses/LICENSE +21 -0
- solokit-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pytest fixtures for testing
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncGenerator
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from httpx import ASGITransport, AsyncClient # type: ignore[import-not-found]
|
|
10
|
+
from sqlalchemy.ext.asyncio import create_async_engine # type: ignore[import-not-found]
|
|
11
|
+
from sqlalchemy.orm import sessionmaker # type: ignore[import-not-found]
|
|
12
|
+
from sqlmodel import SQLModel # type: ignore[import-not-found]
|
|
13
|
+
from sqlmodel.ext.asyncio.session import AsyncSession # type: ignore[import-not-found]
|
|
14
|
+
|
|
15
|
+
from src.api.dependencies import get_db # type: ignore[import-not-found]
|
|
16
|
+
from src.main import app # type: ignore[import-not-found]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Test database URL (use in-memory SQLite for testing)
|
|
20
|
+
TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
async def db_engine() -> AsyncGenerator[Any, None]:
|
|
25
|
+
"""Create a test database engine."""
|
|
26
|
+
engine = create_async_engine(
|
|
27
|
+
TEST_DATABASE_URL,
|
|
28
|
+
echo=False,
|
|
29
|
+
future=True,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
async with engine.begin() as conn:
|
|
33
|
+
await conn.run_sync(SQLModel.metadata.create_all)
|
|
34
|
+
|
|
35
|
+
yield engine
|
|
36
|
+
|
|
37
|
+
async with engine.begin() as conn:
|
|
38
|
+
await conn.run_sync(SQLModel.metadata.drop_all)
|
|
39
|
+
|
|
40
|
+
await engine.dispose()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.fixture
|
|
44
|
+
async def db_session(db_engine: Any) -> AsyncGenerator[AsyncSession, None]:
|
|
45
|
+
"""Create a test database session."""
|
|
46
|
+
async_session_maker = sessionmaker(
|
|
47
|
+
db_engine,
|
|
48
|
+
class_=AsyncSession,
|
|
49
|
+
expire_on_commit=False,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
async with async_session_maker() as session:
|
|
53
|
+
yield session
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.fixture
|
|
57
|
+
async def client(db_session: AsyncSession) -> AsyncGenerator[AsyncClient, None]:
|
|
58
|
+
"""
|
|
59
|
+
Create a test client with database session override.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
async def override_get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
63
|
+
yield db_session
|
|
64
|
+
|
|
65
|
+
app.dependency_overrides[get_db] = override_get_db
|
|
66
|
+
|
|
67
|
+
async with AsyncClient(
|
|
68
|
+
transport=ASGITransport(app=app), base_url="http://test", timeout=30.0
|
|
69
|
+
) as ac: # nosec B113 - Test client timeout is set to 30 seconds
|
|
70
|
+
yield ac
|
|
71
|
+
|
|
72
|
+
app.dependency_overrides.clear()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for main FastAPI application
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from httpx import AsyncClient # type: ignore[import-not-found]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.unit
|
|
10
|
+
class TestMainApp:
|
|
11
|
+
"""Test cases for main application endpoints."""
|
|
12
|
+
|
|
13
|
+
async def test_root_endpoint(self, client: AsyncClient) -> None:
|
|
14
|
+
"""Test root endpoint returns correct information."""
|
|
15
|
+
response = await client.get("/")
|
|
16
|
+
|
|
17
|
+
assert response.status_code == 200
|
|
18
|
+
data = response.json()
|
|
19
|
+
assert "name" in data
|
|
20
|
+
assert "version" in data
|
|
21
|
+
assert "status" in data
|
|
22
|
+
assert data["status"] == "running"
|
|
23
|
+
|
|
24
|
+
async def test_health_check(self, client: AsyncClient) -> None:
|
|
25
|
+
"""Test health check endpoint."""
|
|
26
|
+
response = await client.get("/health")
|
|
27
|
+
|
|
28
|
+
assert response.status_code == 200
|
|
29
|
+
data = response.json()
|
|
30
|
+
assert data["status"] == "healthy"
|
|
31
|
+
assert "service" in data
|
|
32
|
+
assert "version" in data
|
|
33
|
+
|
|
34
|
+
async def test_liveness_check(self, client: AsyncClient) -> None:
|
|
35
|
+
"""Test liveness check endpoint."""
|
|
36
|
+
response = await client.get("/health/live")
|
|
37
|
+
|
|
38
|
+
assert response.status_code == 200
|
|
39
|
+
data = response.json()
|
|
40
|
+
assert data["status"] == "alive"
|
|
41
|
+
|
|
42
|
+
async def test_readiness_check(self, client: AsyncClient) -> None:
|
|
43
|
+
"""Test readiness check endpoint."""
|
|
44
|
+
response = await client.get("/health/ready")
|
|
45
|
+
|
|
46
|
+
assert response.status_code == 200
|
|
47
|
+
data = response.json()
|
|
48
|
+
assert "status" in data
|
|
49
|
+
assert "database" in data
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for example service
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from sqlmodel.ext.asyncio.session import AsyncSession # type: ignore[import-not-found]
|
|
7
|
+
|
|
8
|
+
from src.models.example import ItemCreate, ItemUpdate # type: ignore[import-not-found]
|
|
9
|
+
from src.services.example import ItemService # type: ignore[import-not-found]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.unit
|
|
13
|
+
class TestItemService:
|
|
14
|
+
"""Test cases for ItemService."""
|
|
15
|
+
|
|
16
|
+
async def test_create_item(self, db_session: AsyncSession) -> None:
|
|
17
|
+
"""Test creating an item."""
|
|
18
|
+
service = ItemService(db_session)
|
|
19
|
+
item_data = ItemCreate(
|
|
20
|
+
name="Test Item",
|
|
21
|
+
description="A test item",
|
|
22
|
+
price=9.99,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
item = await service.create_item(item_data)
|
|
26
|
+
|
|
27
|
+
assert item.id is not None
|
|
28
|
+
assert item.name == "Test Item"
|
|
29
|
+
assert item.description == "A test item"
|
|
30
|
+
assert item.price == 9.99
|
|
31
|
+
assert item.is_active is True
|
|
32
|
+
|
|
33
|
+
async def test_get_item(self, db_session: AsyncSession) -> None:
|
|
34
|
+
"""Test retrieving an item."""
|
|
35
|
+
service = ItemService(db_session)
|
|
36
|
+
|
|
37
|
+
# Create an item first
|
|
38
|
+
item_data = ItemCreate(name="Test Item", price=9.99)
|
|
39
|
+
created_item = await service.create_item(item_data)
|
|
40
|
+
|
|
41
|
+
# Retrieve the item
|
|
42
|
+
retrieved_item = await service.get_item(created_item.id)
|
|
43
|
+
|
|
44
|
+
assert retrieved_item is not None
|
|
45
|
+
assert retrieved_item.id == created_item.id
|
|
46
|
+
assert retrieved_item.name == created_item.name
|
|
47
|
+
|
|
48
|
+
async def test_get_nonexistent_item(self, db_session: AsyncSession) -> None:
|
|
49
|
+
"""Test retrieving a non-existent item."""
|
|
50
|
+
service = ItemService(db_session)
|
|
51
|
+
item = await service.get_item(999)
|
|
52
|
+
assert item is None
|
|
53
|
+
|
|
54
|
+
async def test_get_items_pagination(self, db_session: AsyncSession) -> None:
|
|
55
|
+
"""Test listing items with pagination."""
|
|
56
|
+
service = ItemService(db_session)
|
|
57
|
+
|
|
58
|
+
# Create multiple items (starting from 1 to avoid price=0 validation error)
|
|
59
|
+
for i in range(1, 6):
|
|
60
|
+
item_data = ItemCreate(name=f"Item {i}", price=float(i))
|
|
61
|
+
await service.create_item(item_data)
|
|
62
|
+
|
|
63
|
+
# Test pagination
|
|
64
|
+
items = await service.get_items(skip=0, limit=3)
|
|
65
|
+
assert len(items) == 3
|
|
66
|
+
|
|
67
|
+
items = await service.get_items(skip=3, limit=3)
|
|
68
|
+
assert len(items) == 2
|
|
69
|
+
|
|
70
|
+
async def test_update_item(self, db_session: AsyncSession) -> None:
|
|
71
|
+
"""Test updating an item."""
|
|
72
|
+
service = ItemService(db_session)
|
|
73
|
+
|
|
74
|
+
# Create an item
|
|
75
|
+
item_data = ItemCreate(name="Original Name", price=10.0)
|
|
76
|
+
created_item = await service.create_item(item_data)
|
|
77
|
+
|
|
78
|
+
# Update the item
|
|
79
|
+
update_data = ItemUpdate(name="Updated Name", price=15.0)
|
|
80
|
+
updated_item = await service.update_item(created_item.id, update_data)
|
|
81
|
+
|
|
82
|
+
assert updated_item is not None
|
|
83
|
+
assert updated_item.name == "Updated Name"
|
|
84
|
+
assert updated_item.price == 15.0
|
|
85
|
+
|
|
86
|
+
async def test_update_nonexistent_item(self, db_session: AsyncSession) -> None:
|
|
87
|
+
"""Test updating a non-existent item."""
|
|
88
|
+
service = ItemService(db_session)
|
|
89
|
+
update_data = ItemUpdate(name="Updated Name")
|
|
90
|
+
result = await service.update_item(999, update_data)
|
|
91
|
+
assert result is None
|
|
92
|
+
|
|
93
|
+
async def test_delete_item(self, db_session: AsyncSession) -> None:
|
|
94
|
+
"""Test deleting an item."""
|
|
95
|
+
service = ItemService(db_session)
|
|
96
|
+
|
|
97
|
+
# Create an item
|
|
98
|
+
item_data = ItemCreate(name="To Delete", price=10.0)
|
|
99
|
+
created_item = await service.create_item(item_data)
|
|
100
|
+
|
|
101
|
+
# Delete the item
|
|
102
|
+
success = await service.delete_item(created_item.id)
|
|
103
|
+
assert success is True
|
|
104
|
+
|
|
105
|
+
# Verify deletion
|
|
106
|
+
deleted_item = await service.get_item(created_item.id)
|
|
107
|
+
assert deleted_item is None
|
|
108
|
+
|
|
109
|
+
async def test_delete_nonexistent_item(self, db_session: AsyncSession) -> None:
|
|
110
|
+
"""Test deleting a non-existent item."""
|
|
111
|
+
service = ItemService(db_session)
|
|
112
|
+
success = await service.delete_item(999)
|
|
113
|
+
assert success is False
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "{project_name}"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "{project_description}"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"fastapi==0.115.6",
|
|
8
|
+
"uvicorn[standard]==0.34.0",
|
|
9
|
+
"pydantic==2.12.4",
|
|
10
|
+
"pydantic-core==2.41.5",
|
|
11
|
+
"pydantic-settings==2.11.0",
|
|
12
|
+
"sqlmodel==0.0.25",
|
|
13
|
+
"sqlalchemy==2.0.37",
|
|
14
|
+
"psycopg2-binary==2.9.10",
|
|
15
|
+
"alembic==1.14.0",
|
|
16
|
+
"python-dotenv==1.2.1",
|
|
17
|
+
"asyncpg==0.30.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest==8.3.4",
|
|
23
|
+
"pytest-cov==6.0.0",
|
|
24
|
+
"pytest-asyncio==0.25.2",
|
|
25
|
+
"httpx==0.28.1",
|
|
26
|
+
"aiosqlite==0.20.0",
|
|
27
|
+
"ruff==0.9.2",
|
|
28
|
+
"pyright==1.1.396",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
security = [
|
|
32
|
+
"detect-secrets==1.5.0",
|
|
33
|
+
"pip-audit==2.7.3",
|
|
34
|
+
"bandit[toml]==1.8.0",
|
|
35
|
+
"pre-commit==4.0.1",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["setuptools>=61.0"]
|
|
40
|
+
build-backend = "setuptools.build_meta"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools]
|
|
43
|
+
packages = ["src"]
|
|
44
|
+
|
|
45
|
+
# Ruff configuration
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
target-version = "py311"
|
|
49
|
+
|
|
50
|
+
select = [
|
|
51
|
+
"E", # pycodestyle errors
|
|
52
|
+
"W", # pycodestyle warnings
|
|
53
|
+
"F", # pyflakes
|
|
54
|
+
"I", # isort
|
|
55
|
+
"N", # pep8-naming
|
|
56
|
+
"UP", # pyupgrade
|
|
57
|
+
"B", # flake8-bugbear
|
|
58
|
+
"S", # bandit security checks
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
ignore = [
|
|
62
|
+
"E501", # line too long (handled by formatter)
|
|
63
|
+
"S101", # assert used (we use asserts in tests)
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
exclude = [
|
|
67
|
+
".git",
|
|
68
|
+
".venv",
|
|
69
|
+
"__pycache__",
|
|
70
|
+
"alembic/versions",
|
|
71
|
+
".pytest_cache",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[tool.ruff.format]
|
|
75
|
+
quote-style = "double"
|
|
76
|
+
indent-style = "space"
|
|
77
|
+
|
|
78
|
+
[tool.ruff.isort]
|
|
79
|
+
known-first-party = ["src"]
|
|
80
|
+
|
|
81
|
+
[tool.ruff.per-file-ignores]
|
|
82
|
+
"tests/*" = ["S101"]
|
|
83
|
+
|
|
84
|
+
# Pytest configuration
|
|
85
|
+
[tool.pytest.ini_options]
|
|
86
|
+
asyncio_mode = "auto"
|
|
87
|
+
testpaths = ["tests"]
|
|
88
|
+
python_files = ["test_*.py", "*_test.py"]
|
|
89
|
+
python_classes = ["Test*"]
|
|
90
|
+
python_functions = ["test_*"]
|
|
91
|
+
addopts = [
|
|
92
|
+
"-v",
|
|
93
|
+
"--strict-markers",
|
|
94
|
+
"--tb=short",
|
|
95
|
+
"--cov=src",
|
|
96
|
+
"--cov-report=term-missing",
|
|
97
|
+
"--cov-fail-under=80",
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
# Coverage configuration
|
|
101
|
+
[tool.coverage.run]
|
|
102
|
+
source = ["src"]
|
|
103
|
+
omit = [
|
|
104
|
+
"tests/*",
|
|
105
|
+
"alembic/*",
|
|
106
|
+
"*/__init__.py",
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
[tool.coverage.report]
|
|
110
|
+
precision = 2
|
|
111
|
+
show_missing = true
|
|
112
|
+
skip_covered = false
|
|
113
|
+
exclude_lines = [
|
|
114
|
+
"pragma: no cover",
|
|
115
|
+
"def __repr__",
|
|
116
|
+
"raise AssertionError",
|
|
117
|
+
"raise NotImplementedError",
|
|
118
|
+
"if __name__ == .__main__.:",
|
|
119
|
+
"if TYPE_CHECKING:",
|
|
120
|
+
"@abstractmethod",
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
# Bandit security configuration
|
|
124
|
+
[tool.bandit]
|
|
125
|
+
exclude_dirs = ["tests", ".venv", "venv", "alembic/versions"]
|
|
126
|
+
skips = ["B101"]
|
|
127
|
+
|
|
128
|
+
# Scripts
|
|
129
|
+
[project.scripts]
|
|
130
|
+
dev = "uvicorn src.main:app --reload --host 0.0.0.0 --port 8000"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Locust load testing configuration for FastAPI application
|
|
3
|
+
https://docs.locust.io/
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from locust import HttpUser, between, task # type: ignore[import-not-found]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FastAPIUser(HttpUser):
|
|
10
|
+
"""
|
|
11
|
+
Simulated user for load testing the FastAPI application.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Wait time between tasks (in seconds)
|
|
15
|
+
wait_time = between(1, 3)
|
|
16
|
+
|
|
17
|
+
# Base host will be set via command line: locust --host=http://localhost:8000
|
|
18
|
+
|
|
19
|
+
@task(3)
|
|
20
|
+
def get_root(self) -> None:
|
|
21
|
+
"""Test the root endpoint (higher weight = 3)."""
|
|
22
|
+
self.client.get("/")
|
|
23
|
+
|
|
24
|
+
@task(5)
|
|
25
|
+
def health_check(self) -> None:
|
|
26
|
+
"""Test the health check endpoint (higher weight = 5)."""
|
|
27
|
+
self.client.get("/health")
|
|
28
|
+
|
|
29
|
+
@task(2)
|
|
30
|
+
def list_items(self) -> None:
|
|
31
|
+
"""Test listing items."""
|
|
32
|
+
self.client.get("/api/v1/items")
|
|
33
|
+
|
|
34
|
+
@task(1)
|
|
35
|
+
def create_item(self) -> None:
|
|
36
|
+
"""Test creating an item."""
|
|
37
|
+
self.client.post(
|
|
38
|
+
"/api/v1/items",
|
|
39
|
+
json={
|
|
40
|
+
"name": "Load Test Item",
|
|
41
|
+
"description": "Created during load testing",
|
|
42
|
+
"price": 99.99,
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@task(1)
|
|
47
|
+
def get_item(self) -> None:
|
|
48
|
+
"""Test getting a specific item."""
|
|
49
|
+
# Note: This assumes item with ID 1 exists
|
|
50
|
+
# In production, you'd create items first
|
|
51
|
+
with self.client.get("/api/v1/items/1", catch_response=True) as response:
|
|
52
|
+
if response.status_code == 404:
|
|
53
|
+
response.success() # Mark as success even if not found
|
|
54
|
+
|
|
55
|
+
def on_start(self) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Called when a simulated user starts.
|
|
58
|
+
Use this to set up test data or authenticate.
|
|
59
|
+
"""
|
|
60
|
+
# Example: Create test items
|
|
61
|
+
for i in range(3):
|
|
62
|
+
self.client.post(
|
|
63
|
+
"/api/v1/items",
|
|
64
|
+
json={
|
|
65
|
+
"name": f"Test Item {i}",
|
|
66
|
+
"description": f"Description {i}",
|
|
67
|
+
"price": 10.0 * (i + 1),
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def on_stop(self) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Called when a simulated user stops.
|
|
74
|
+
Use this to clean up test data.
|
|
75
|
+
"""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class AdminUser(HttpUser):
|
|
80
|
+
"""
|
|
81
|
+
Simulated admin user with different behavior patterns.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
wait_time = between(2, 5)
|
|
85
|
+
|
|
86
|
+
@task(1)
|
|
87
|
+
def check_readiness(self) -> None:
|
|
88
|
+
"""Test the readiness check endpoint."""
|
|
89
|
+
self.client.get("/health/ready")
|
|
90
|
+
|
|
91
|
+
@task(1)
|
|
92
|
+
def check_liveness(self) -> None:
|
|
93
|
+
"""Test the liveness check endpoint."""
|
|
94
|
+
self.client.get("/health/live")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Run with:
|
|
98
|
+
# locust --host=http://localhost:8000
|
|
99
|
+
# locust --host=http://localhost:8000 --users 100 --spawn-rate 10 --run-time 1m
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mutation testing configuration for mutmut
|
|
3
|
+
https://mutmut.readthedocs.io/
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def pre_mutation(context: Any) -> None:
|
|
10
|
+
"""
|
|
11
|
+
Called before each mutation is tested.
|
|
12
|
+
Can be used to skip certain mutations.
|
|
13
|
+
"""
|
|
14
|
+
# Skip mutations in test files
|
|
15
|
+
if "test_" in context.filename:
|
|
16
|
+
context.skip = True
|
|
17
|
+
|
|
18
|
+
# Skip mutations in migration files
|
|
19
|
+
if "alembic/versions" in context.filename:
|
|
20
|
+
context.skip = True
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def post_mutation(context: Any) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Called after each mutation is tested.
|
|
26
|
+
Can be used for custom reporting.
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Paths to mutate
|
|
32
|
+
paths_to_mutate = "src/"
|
|
33
|
+
|
|
34
|
+
# Paths to exclude from mutation
|
|
35
|
+
paths_to_exclude = [
|
|
36
|
+
"tests/",
|
|
37
|
+
"alembic/",
|
|
38
|
+
"__pycache__/",
|
|
39
|
+
".venv/",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# Test command
|
|
43
|
+
tests_dir = "tests/"
|
|
44
|
+
test_command = "pytest -x --tb=short"
|
|
45
|
+
|
|
46
|
+
# Runner configuration
|
|
47
|
+
runner = "python"
|
|
48
|
+
|
|
49
|
+
# Coverage threshold (percentage)
|
|
50
|
+
coverage_threshold = 80
|
|
51
|
+
|
|
52
|
+
# Mutation operators to use
|
|
53
|
+
dict_synonyms = ["dict", "OrderedDict", "defaultdict"]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "{project_name}"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "{project_description}"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"fastapi==0.115.6",
|
|
8
|
+
"uvicorn[standard]==0.34.0",
|
|
9
|
+
"pydantic==2.12.4",
|
|
10
|
+
"pydantic-core==2.41.5",
|
|
11
|
+
"pydantic-settings==2.11.0",
|
|
12
|
+
"sqlmodel==0.0.25",
|
|
13
|
+
"sqlalchemy==2.0.37",
|
|
14
|
+
"psycopg2-binary==2.9.10",
|
|
15
|
+
"alembic==1.14.0",
|
|
16
|
+
"python-dotenv==1.2.1",
|
|
17
|
+
"asyncpg==0.30.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest==8.3.4",
|
|
23
|
+
"pytest-cov==6.0.0",
|
|
24
|
+
"pytest-asyncio==0.25.2",
|
|
25
|
+
"httpx==0.28.1",
|
|
26
|
+
"aiosqlite==0.20.0",
|
|
27
|
+
"ruff==0.9.2",
|
|
28
|
+
"pyright==1.1.396",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
security = [
|
|
32
|
+
"detect-secrets==1.5.0",
|
|
33
|
+
"pip-audit==2.7.3",
|
|
34
|
+
"bandit[toml]==1.8.0",
|
|
35
|
+
"pre-commit==4.0.1",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
quality = [
|
|
39
|
+
"radon==6.0.1",
|
|
40
|
+
"vulture==2.14",
|
|
41
|
+
"mutmut==3.3.1",
|
|
42
|
+
"locust==2.42.2",
|
|
43
|
+
"semgrep==1.142.1",
|
|
44
|
+
"coverage[toml]==7.11.1",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
requires = ["setuptools>=61.0"]
|
|
49
|
+
build-backend = "setuptools.build_meta"
|
|
50
|
+
|
|
51
|
+
[tool.setuptools]
|
|
52
|
+
packages = ["src"]
|
|
53
|
+
|
|
54
|
+
# Ruff configuration
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
line-length = 100
|
|
57
|
+
target-version = "py311"
|
|
58
|
+
|
|
59
|
+
select = [
|
|
60
|
+
"E", # pycodestyle errors
|
|
61
|
+
"W", # pycodestyle warnings
|
|
62
|
+
"F", # pyflakes
|
|
63
|
+
"I", # isort
|
|
64
|
+
"N", # pep8-naming
|
|
65
|
+
"UP", # pyupgrade
|
|
66
|
+
"B", # flake8-bugbear
|
|
67
|
+
"S", # bandit security checks
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
ignore = [
|
|
71
|
+
"E501", # line too long (handled by formatter)
|
|
72
|
+
"S101", # assert used (we use asserts in tests)
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
exclude = [
|
|
76
|
+
".git",
|
|
77
|
+
".venv",
|
|
78
|
+
"__pycache__",
|
|
79
|
+
"alembic/versions",
|
|
80
|
+
".pytest_cache",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[tool.ruff.format]
|
|
84
|
+
quote-style = "double"
|
|
85
|
+
indent-style = "space"
|
|
86
|
+
|
|
87
|
+
[tool.ruff.isort]
|
|
88
|
+
known-first-party = ["src"]
|
|
89
|
+
|
|
90
|
+
[tool.ruff.per-file-ignores]
|
|
91
|
+
"tests/*" = ["S101"]
|
|
92
|
+
|
|
93
|
+
# Pytest configuration
|
|
94
|
+
[tool.pytest.ini_options]
|
|
95
|
+
asyncio_mode = "auto"
|
|
96
|
+
testpaths = ["tests"]
|
|
97
|
+
python_files = ["test_*.py", "*_test.py"]
|
|
98
|
+
python_classes = ["Test*"]
|
|
99
|
+
python_functions = ["test_*"]
|
|
100
|
+
markers = [
|
|
101
|
+
"unit: Unit tests",
|
|
102
|
+
"integration: Integration tests",
|
|
103
|
+
"slow: Slow running tests",
|
|
104
|
+
"api: API tests",
|
|
105
|
+
"db: Database tests",
|
|
106
|
+
]
|
|
107
|
+
addopts = [
|
|
108
|
+
"-v",
|
|
109
|
+
"--strict-markers",
|
|
110
|
+
"--tb=short",
|
|
111
|
+
"--cov=src",
|
|
112
|
+
"--cov-report=term-missing",
|
|
113
|
+
"--cov-report=html",
|
|
114
|
+
"--cov-report=xml",
|
|
115
|
+
"--cov-fail-under=80",
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
# Coverage configuration
|
|
119
|
+
[tool.coverage.run]
|
|
120
|
+
source = ["src"]
|
|
121
|
+
omit = [
|
|
122
|
+
"tests/*",
|
|
123
|
+
"alembic/*",
|
|
124
|
+
"*/__init__.py",
|
|
125
|
+
]
|
|
126
|
+
branch = true
|
|
127
|
+
|
|
128
|
+
[tool.coverage.report]
|
|
129
|
+
precision = 2
|
|
130
|
+
show_missing = true
|
|
131
|
+
skip_covered = false
|
|
132
|
+
fail_under = 80
|
|
133
|
+
exclude_lines = [
|
|
134
|
+
"pragma: no cover",
|
|
135
|
+
"def __repr__",
|
|
136
|
+
"raise AssertionError",
|
|
137
|
+
"raise NotImplementedError",
|
|
138
|
+
"if __name__ == .__main__.:",
|
|
139
|
+
"if TYPE_CHECKING:",
|
|
140
|
+
"@abstractmethod",
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
# Bandit security configuration
|
|
144
|
+
[tool.bandit]
|
|
145
|
+
exclude_dirs = ["tests", ".venv", "venv", "alembic/versions"]
|
|
146
|
+
skips = ["B101"]
|
|
147
|
+
|
|
148
|
+
# Scripts
|
|
149
|
+
[project.scripts]
|
|
150
|
+
dev = "uvicorn src.main:app --reload --host 0.0.0.0 --port 8000"
|