csrd-utils 0.3.82__tar.gz → 0.3.86__tar.gz
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.
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/PKG-INFO +3 -1
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/pyproject.toml +3 -1
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/__main__.py +192 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/AGENTS.md +349 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/__init__.py +1 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/compose/__init__.py +93 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/compose/git.py +17 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/compose/infra.py +257 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/compose/loader.py +38 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/compose/operations.py +126 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/compose/presets.py +58 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/compose/renderer.py +279 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/compose/scaffolder.py +89 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/compose/yaml_editor.py +134 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/generate/__init__.py +5 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/generate/handlers.py +705 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/generate/helpers.py +647 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/generate/menu.py +170 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/models/__init__.py +38 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/models/base.py +10 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/models/spec.py +101 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/models/types.py +6 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/templates/service/cookiecutter.json +7 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/Dockerfile +12 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/README.md +21 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/__init__.py +28 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/requirements.txt +4 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/settings.py +12 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/tests/__init__.py +0 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/tests/conftest.py +12 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/tests/test_health.py +7 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/views/__init__.py +9 -0
- csrd_utils-0.3.86/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/views/health_view.py +8 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/.gitignore +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/README.md +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/audit.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/augmentor.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/cluster.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/doctor.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/caching/manifest.yaml +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/caching/requirements.fragment +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/logging/manifest.yaml +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/logging/requirements.fragment +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/workers/manifest.yaml +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/workers/requirements.fragment +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/fragments.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/generator.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/resources.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.cookiecutterignore +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/cache.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/upstreams.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/auth_service.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/delegates.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/worker_broker.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/docker-compose.yml +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/logging.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/test_logging_middleware.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/conftest.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/test_items.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_cache.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_database_settings.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_delegates.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_settings_contracts.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_worker_broker.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/tasks.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/cookiecutter.json +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/delegates/auth_delegate.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/delegates/identity_delegate.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/auth_passthrough.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/auth_service.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/models/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/models/auth_service.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/models/items.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/auth_service_view.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/cookiecutter.json +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/models/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/models/auth.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/repositories/token_repository.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/unversioned/auth_view.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/cookiecutter.json +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/models/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/models/users.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/repositories/user_repository.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
- {csrd_utils-0.3.82 → csrd_utils-0.3.86}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/views/unversioned/users_view.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csrd-utils
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.86
|
|
4
4
|
Summary: CLI utilities for csrd service generation and feature augmentation
|
|
5
5
|
Project-URL: Repository, https://github.com/csrd-api/fastapi-common
|
|
6
6
|
Project-URL: Documentation, https://github.com/csrd-api/fastapi-common/tree/main/packages/utils
|
|
@@ -8,7 +8,9 @@ Project-URL: Changelog, https://github.com/csrd-api/fastapi-common/blob/main/CHA
|
|
|
8
8
|
License: MIT
|
|
9
9
|
Requires-Python: >=3.12
|
|
10
10
|
Requires-Dist: cookiecutter<3,>=2.6
|
|
11
|
+
Requires-Dist: pydantic<3,>=2.6
|
|
11
12
|
Requires-Dist: pyyaml<7,>=6
|
|
13
|
+
Requires-Dist: ruamel-yaml<1,>=0.18
|
|
12
14
|
Description-Content-Type: text/markdown
|
|
13
15
|
|
|
14
16
|
# csrd-utils
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "csrd-utils"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.86"
|
|
4
4
|
description = "CLI utilities for csrd service generation and feature augmentation"
|
|
5
5
|
license = { text = "MIT" }
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"cookiecutter>=2.6,<3",
|
|
9
9
|
"PyYAML>=6,<7",
|
|
10
|
+
"pydantic>=2.6,<3",
|
|
11
|
+
"ruamel.yaml>=0.18,<1",
|
|
10
12
|
]
|
|
11
13
|
readme = "README.md"
|
|
12
14
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import argparse
|
|
4
4
|
import json
|
|
5
5
|
import shutil
|
|
6
|
+
import sys
|
|
6
7
|
from contextlib import suppress
|
|
7
8
|
from importlib.metadata import version
|
|
8
9
|
from pathlib import Path
|
|
@@ -24,6 +25,9 @@ from .cluster import (
|
|
|
24
25
|
from .doctor import run_doctor
|
|
25
26
|
from .generator import generate_platform_service, generate_service
|
|
26
27
|
from .resources import features_path
|
|
28
|
+
from .v2.compose import apply as v2_compose_apply
|
|
29
|
+
from .v2.compose import validate as v2_compose_validate
|
|
30
|
+
from .v2.generate import run_generate_menu as run_v2_generate_menu
|
|
27
31
|
|
|
28
32
|
|
|
29
33
|
class GenerateServiceArgs(TypedDict):
|
|
@@ -57,6 +61,7 @@ class GenerateWorkspaceArgs(TypedDict):
|
|
|
57
61
|
output_dir: Path
|
|
58
62
|
overwrite_if_exists: bool
|
|
59
63
|
generate: str
|
|
64
|
+
git_init: bool
|
|
60
65
|
|
|
61
66
|
|
|
62
67
|
class ExtractWorkspaceServiceArgs(TypedDict):
|
|
@@ -416,12 +421,14 @@ def _collect_new_workspace_config(args: argparse.Namespace) -> GenerateWorkspace
|
|
|
416
421
|
generate = args.generate or _prompt_choice(
|
|
417
422
|
"Generate in workspace now", ["none", "service", "cluster"], "service"
|
|
418
423
|
)
|
|
424
|
+
git_init = _prompt_bool("Initialize git repository", True)
|
|
419
425
|
return GenerateWorkspaceArgs(
|
|
420
426
|
{
|
|
421
427
|
"name": name,
|
|
422
428
|
"output_dir": output_dir,
|
|
423
429
|
"overwrite_if_exists": args.force,
|
|
424
430
|
"generate": generate,
|
|
431
|
+
"git_init": git_init,
|
|
425
432
|
}
|
|
426
433
|
)
|
|
427
434
|
|
|
@@ -438,6 +445,10 @@ def _create_workspace(config: GenerateWorkspaceArgs) -> Path:
|
|
|
438
445
|
)
|
|
439
446
|
|
|
440
447
|
_write_workspace_marker(workspace_root)
|
|
448
|
+
|
|
449
|
+
if config.get("git_init", False):
|
|
450
|
+
_git_init(workspace_root)
|
|
451
|
+
|
|
441
452
|
return workspace_root
|
|
442
453
|
|
|
443
454
|
|
|
@@ -577,6 +588,20 @@ def _write_workspace_marker(path: Path) -> None:
|
|
|
577
588
|
(path / WORKSPACE_MARKER).write_text("workspace: csrd\n", encoding="utf-8")
|
|
578
589
|
|
|
579
590
|
|
|
591
|
+
def _git_init(path: Path) -> None:
|
|
592
|
+
"""Run ``git init`` inside *path* if not already a git repository."""
|
|
593
|
+
import subprocess
|
|
594
|
+
|
|
595
|
+
if (path / ".git").exists():
|
|
596
|
+
return
|
|
597
|
+
subprocess.run(
|
|
598
|
+
["git", "init"],
|
|
599
|
+
cwd=path,
|
|
600
|
+
check=True,
|
|
601
|
+
capture_output=True,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
|
|
580
605
|
def _is_workspace(path: Path) -> bool:
|
|
581
606
|
return (path / WORKSPACE_MARKER).is_file()
|
|
582
607
|
|
|
@@ -1199,6 +1224,76 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1199
1224
|
completion_sub = completion.add_subparsers(dest="completion_shell", required=True)
|
|
1200
1225
|
completion_sub.add_parser("bash", help="Print bash completion script")
|
|
1201
1226
|
|
|
1227
|
+
v2 = sub.add_parser("v2", help="Preview next-gen generator and compose workflow")
|
|
1228
|
+
v2_sub = v2.add_subparsers(dest="v2_command")
|
|
1229
|
+
|
|
1230
|
+
v2_generate = v2_sub.add_parser("generate", help="Open interactive v2 generator menu")
|
|
1231
|
+
v2_generate.add_argument(
|
|
1232
|
+
"target",
|
|
1233
|
+
nargs="?",
|
|
1234
|
+
choices=["workspace", "preset", "add-service", "add-infra", "remove-infra", "empty"],
|
|
1235
|
+
help="Direct generator target (workspace, preset, add-service, add-infra, remove-infra)",
|
|
1236
|
+
)
|
|
1237
|
+
v2_generate.add_argument(
|
|
1238
|
+
"--name",
|
|
1239
|
+
dest="generate_name",
|
|
1240
|
+
help="Workspace or service name",
|
|
1241
|
+
)
|
|
1242
|
+
v2_generate.add_argument(
|
|
1243
|
+
"--git-init",
|
|
1244
|
+
action="store_true",
|
|
1245
|
+
default=False,
|
|
1246
|
+
help="Initialize a git repository in the generated workspace",
|
|
1247
|
+
)
|
|
1248
|
+
v2_generate.add_argument(
|
|
1249
|
+
"--features",
|
|
1250
|
+
dest="features",
|
|
1251
|
+
nargs="*",
|
|
1252
|
+
choices=["database", "caching", "messaging"],
|
|
1253
|
+
default=None,
|
|
1254
|
+
help="Service capabilities to wire (for add-service)",
|
|
1255
|
+
)
|
|
1256
|
+
v2_generate.add_argument(
|
|
1257
|
+
"--port",
|
|
1258
|
+
dest="port",
|
|
1259
|
+
type=int,
|
|
1260
|
+
default=None,
|
|
1261
|
+
help="Service port (for add-service)",
|
|
1262
|
+
)
|
|
1263
|
+
v2_generate.add_argument(
|
|
1264
|
+
"--infra-type",
|
|
1265
|
+
dest="infra_type",
|
|
1266
|
+
choices=["postgres", "mariadb", "sqlite", "redis", "rabbitmq"],
|
|
1267
|
+
default=None,
|
|
1268
|
+
help="Infrastructure type (for add-infra)",
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1271
|
+
v2_compose = v2_sub.add_parser("compose", help="Compose v2 operations")
|
|
1272
|
+
v2_compose_sub = v2_compose.add_subparsers(dest="v2_compose_command")
|
|
1273
|
+
|
|
1274
|
+
v2_compose_validate = v2_compose_sub.add_parser("validate", help="Validate csrd-compose.yaml")
|
|
1275
|
+
v2_compose_validate.add_argument(
|
|
1276
|
+
"--output", type=Path, default=Path.cwd(), help="Workspace root"
|
|
1277
|
+
)
|
|
1278
|
+
v2_compose_validate.add_argument(
|
|
1279
|
+
"--json", action="store_true", help="Print validated compose spec as JSON"
|
|
1280
|
+
)
|
|
1281
|
+
|
|
1282
|
+
v2_compose_plan = v2_compose_sub.add_parser("plan", help="Preview resolved compose baseline")
|
|
1283
|
+
v2_compose_plan.add_argument("--output", type=Path, default=Path.cwd(), help="Workspace root")
|
|
1284
|
+
v2_compose_plan.add_argument("--json", action="store_true", help="Print plan payload as JSON")
|
|
1285
|
+
|
|
1286
|
+
v2_compose_apply = v2_compose_sub.add_parser(
|
|
1287
|
+
"apply", help="Render baseline workspace artifacts"
|
|
1288
|
+
)
|
|
1289
|
+
v2_compose_apply.add_argument("--output", type=Path, default=Path.cwd(), help="Workspace root")
|
|
1290
|
+
v2_compose_apply.add_argument(
|
|
1291
|
+
"--git-init",
|
|
1292
|
+
action="store_true",
|
|
1293
|
+
default=False,
|
|
1294
|
+
help="Initialize a git repository in the workspace",
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1202
1297
|
return parser
|
|
1203
1298
|
|
|
1204
1299
|
|
|
@@ -1323,9 +1418,106 @@ def _run_audit(service: Path | None, as_json: bool) -> int:
|
|
|
1323
1418
|
|
|
1324
1419
|
|
|
1325
1420
|
def main() -> int:
|
|
1421
|
+
# Shorthand: `csrd v2 compose <path>` => `csrd v2 compose apply --output <path>`
|
|
1422
|
+
raw_argv = sys.argv[1:]
|
|
1423
|
+
if len(raw_argv) >= 3 and raw_argv[0] == "v2" and raw_argv[1] == "compose":
|
|
1424
|
+
third = raw_argv[2]
|
|
1425
|
+
if third not in {"validate", "plan", "apply", "-h", "--help"} and not third.startswith("-"):
|
|
1426
|
+
shorthand_dir = Path(third).resolve()
|
|
1427
|
+
has_compose_spec = (shorthand_dir / "csrd-compose.yaml").is_file() or (
|
|
1428
|
+
shorthand_dir / "csrd-compose.yml"
|
|
1429
|
+
).is_file()
|
|
1430
|
+
if not has_compose_spec:
|
|
1431
|
+
print(f"ERROR: Compose shorthand requires csrd-compose.yaml in: {shorthand_dir}")
|
|
1432
|
+
print(
|
|
1433
|
+
"Hint: create a spec first (csrd v2 generate empty --name <name>) "
|
|
1434
|
+
"or use explicit apply: csrd v2 compose apply --output <path>"
|
|
1435
|
+
)
|
|
1436
|
+
return 1
|
|
1437
|
+
sys.argv = [
|
|
1438
|
+
sys.argv[0],
|
|
1439
|
+
"v2",
|
|
1440
|
+
"compose",
|
|
1441
|
+
"apply",
|
|
1442
|
+
"--output",
|
|
1443
|
+
third,
|
|
1444
|
+
*raw_argv[3:],
|
|
1445
|
+
]
|
|
1446
|
+
|
|
1326
1447
|
parser = _build_parser()
|
|
1327
1448
|
args = parser.parse_args()
|
|
1328
1449
|
|
|
1450
|
+
if args.command == "v2":
|
|
1451
|
+
if getattr(args, "v2_command", None) == "generate":
|
|
1452
|
+
return run_v2_generate_menu(
|
|
1453
|
+
target=getattr(args, "target", None),
|
|
1454
|
+
name=getattr(args, "generate_name", None),
|
|
1455
|
+
git_init=getattr(args, "git_init", False),
|
|
1456
|
+
features=getattr(args, "features", None),
|
|
1457
|
+
port=getattr(args, "port", None),
|
|
1458
|
+
infra_type=getattr(args, "infra_type", None),
|
|
1459
|
+
)
|
|
1460
|
+
|
|
1461
|
+
if getattr(args, "v2_command", None) != "compose":
|
|
1462
|
+
parser.print_help()
|
|
1463
|
+
return 0
|
|
1464
|
+
|
|
1465
|
+
compose_command = getattr(args, "v2_compose_command", None)
|
|
1466
|
+
if compose_command is None:
|
|
1467
|
+
parser.print_help()
|
|
1468
|
+
return 0
|
|
1469
|
+
|
|
1470
|
+
output_dir = Path(args.output).resolve()
|
|
1471
|
+
try:
|
|
1472
|
+
if compose_command == "apply":
|
|
1473
|
+
generated_root = v2_compose_apply(
|
|
1474
|
+
output_dir, git_init=getattr(args, "git_init", False)
|
|
1475
|
+
)
|
|
1476
|
+
print(f"Rendered compose workspace at: {generated_root}")
|
|
1477
|
+
return 0
|
|
1478
|
+
|
|
1479
|
+
if compose_command == "validate":
|
|
1480
|
+
spec = v2_compose_validate(output_dir)
|
|
1481
|
+
if args.json:
|
|
1482
|
+
print(json.dumps(spec.model_dump(mode="python"), indent=2, sort_keys=True))
|
|
1483
|
+
else:
|
|
1484
|
+
print(f"Compose spec valid: {output_dir / 'csrd-compose.yaml'}")
|
|
1485
|
+
return 0
|
|
1486
|
+
|
|
1487
|
+
if compose_command == "plan":
|
|
1488
|
+
spec = v2_compose_validate(output_dir)
|
|
1489
|
+
payload = {
|
|
1490
|
+
"workspace": spec.workspace.name,
|
|
1491
|
+
"version": spec.version,
|
|
1492
|
+
"services": len(spec.services),
|
|
1493
|
+
"infra": len(spec.infra),
|
|
1494
|
+
"presets": [preset.name for preset in spec.presets],
|
|
1495
|
+
"overrides_keys": sorted(spec.overrides.keys()),
|
|
1496
|
+
}
|
|
1497
|
+
if args.json:
|
|
1498
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
1499
|
+
else:
|
|
1500
|
+
print("Compose v2 plan")
|
|
1501
|
+
print(f"- workspace: {payload['workspace']}")
|
|
1502
|
+
print(f"- version: {payload['version']}")
|
|
1503
|
+
print(f"- services: {payload['services']}")
|
|
1504
|
+
print(f"- infra: {payload['infra']}")
|
|
1505
|
+
presets = payload["presets"]
|
|
1506
|
+
overrides_keys = payload["overrides_keys"]
|
|
1507
|
+
assert isinstance(presets, list)
|
|
1508
|
+
assert isinstance(overrides_keys, list)
|
|
1509
|
+
print(f"- presets: {', '.join(presets) if presets else '<none>'}")
|
|
1510
|
+
print(
|
|
1511
|
+
f"- overrides: {', '.join(overrides_keys) if overrides_keys else '<none>'}"
|
|
1512
|
+
)
|
|
1513
|
+
return 0
|
|
1514
|
+
except ValueError as exc:
|
|
1515
|
+
print(f"ERROR: {exc}")
|
|
1516
|
+
return 1
|
|
1517
|
+
|
|
1518
|
+
parser.print_help()
|
|
1519
|
+
return 0
|
|
1520
|
+
|
|
1329
1521
|
if args.command == "new" and args.new_command == "service":
|
|
1330
1522
|
try:
|
|
1331
1523
|
service_config = _collect_new_service_config(args)
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Menu System & Compose Design Rules
|
|
2
|
+
|
|
3
|
+
This file captures the design principles of the `v2/generate` menu system
|
|
4
|
+
and the `v2/compose` rendering pipeline.
|
|
5
|
+
Consult it for every change that adds, removes, or modifies menus, menu items,
|
|
6
|
+
prompts, interactive selection flows, or compose/renderer behavior.
|
|
7
|
+
|
|
8
|
+
## Scope
|
|
9
|
+
|
|
10
|
+
- Applies to `packages/utils/src/csrd_utils/v2/**`.
|
|
11
|
+
- Also follow `packages/utils/AGENTS.md` and the top-level `AGENTS.md`.
|
|
12
|
+
|
|
13
|
+
## Drop-in replacement intent
|
|
14
|
+
|
|
15
|
+
The `v2` module is intended to **fully replace** the bare `csrd` command.
|
|
16
|
+
Once promoted, the `v2/` directory becomes the top-level implementation and
|
|
17
|
+
the `v2` namespace disappears.
|
|
18
|
+
|
|
19
|
+
**Naming rule**: Do not use "v2" in any module name, class name, function name,
|
|
20
|
+
variable name, constant, test name, CLI label, or user-facing string inside this
|
|
21
|
+
package. The only place "v2" appears is the directory name itself, which will be
|
|
22
|
+
removed at promotion time. Names should read as if `v2/` does not exist —
|
|
23
|
+
e.g. `compose`, `generate`, `ComposeSpec`, `render_workspace`, not
|
|
24
|
+
`v2_compose`, `V2ComposeSpec`, `render_v2_workspace`.
|
|
25
|
+
|
|
26
|
+
## Architecture overview
|
|
27
|
+
|
|
28
|
+
### Generate layer (interactive / CLI)
|
|
29
|
+
|
|
30
|
+
The menu system is split into three layers:
|
|
31
|
+
|
|
32
|
+
| Layer | File | Responsibility |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| **Primitives** | `helpers.py` | `MenuItem` dataclass, rendering, TTY/non-TTY selection, prompt helpers (`prompt_text`, `prompt_yes_no`, `prompt_single_select`, `prompt_multi_select`). |
|
|
35
|
+
| **Orchestration** | `menu.py` | Declarative item lists (`IN_WORKSPACE_ITEMS`, `_build_no_workspace_items`), context predicates, direct-mode dispatch, and the public `run_generate_menu` entry-point. |
|
|
36
|
+
| **Handlers** | `handlers.py` | Business-logic callbacks invoked by menu items. Each returns an `int` exit code. Shared finalization via `_finalize_workspace`. |
|
|
37
|
+
|
|
38
|
+
Changes must respect this separation. Do not put rendering logic in handlers
|
|
39
|
+
or business logic in helpers.
|
|
40
|
+
|
|
41
|
+
### Compose layer (deterministic rendering)
|
|
42
|
+
|
|
43
|
+
The compose pipeline is split into focused modules:
|
|
44
|
+
|
|
45
|
+
| Module | Responsibility |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `models/spec.py` | Pydantic models: `ComposeSpec`, `ServiceNode`, `InfraNode`, `PresetDefinition`, constants (`INFRA_DATABASES`, `INFRA_CATEGORIES`, etc.) |
|
|
48
|
+
| `compose/infra.py` | `InfraDescriptor` registry — single source of truth for all infra container defs, service env wiring, depends_on, and env example lines |
|
|
49
|
+
| `compose/operations.py` | Workspace mutations: `add_service`, `add_infra`, `remove_infra`, `available_infra`, `configured_infra`, `apply`, `validate` |
|
|
50
|
+
| `compose/renderer.py` | Pure `spec → file content` transforms: docker-compose.yml, .env.example, README, .gitignore, pyproject.toml, workspace marker |
|
|
51
|
+
| `compose/scaffolder.py` | Cookiecutter template → workspace filesystem layout (idempotent) |
|
|
52
|
+
| `compose/loader.py` | Load/save `csrd-compose.yaml` |
|
|
53
|
+
| `compose/presets.py` | Preset definitions and category helpers |
|
|
54
|
+
| `compose/yaml_editor.py` | YAML serialization utilities |
|
|
55
|
+
| `compose/__init__.py` | Re-exports only — no business logic |
|
|
56
|
+
|
|
57
|
+
## Non-negotiable design rules
|
|
58
|
+
|
|
59
|
+
### 1. Deterministic rendering
|
|
60
|
+
|
|
61
|
+
Every render function must be a **pure transform from spec to output**.
|
|
62
|
+
Same `ComposeSpec` → same files, byte-for-byte.
|
|
63
|
+
|
|
64
|
+
- **No randomness**: no UUIDs, timestamps, random ports, or non-deterministic ordering.
|
|
65
|
+
- **No environment reads**: renderers must not read env vars, `$HOME`, `os.getcwd()`, or any external state.
|
|
66
|
+
- **Stable iteration**: `spec.infra` and `spec.services` are ordered lists (not sets). Compose services appear in insertion order.
|
|
67
|
+
- **Stable YAML**: `dumps_yaml` uses `default_flow_style=False` for consistent formatting.
|
|
68
|
+
- **Test proof**: `test_render_workspace_is_idempotent` asserts two consecutive renders produce byte-identical output.
|
|
69
|
+
|
|
70
|
+
### 2. Declarative spec as single source of truth
|
|
71
|
+
|
|
72
|
+
`csrd-compose.yaml` (`ComposeSpec`) is the **only** source of truth for a workspace.
|
|
73
|
+
|
|
74
|
+
- Renderers never inspect generated files to decide what to generate.
|
|
75
|
+
- Data flow is one-directional: `user input → operations (mutate spec) → renderer (spec → files)`.
|
|
76
|
+
- No feedback loops: the renderer does not read `docker-compose.yml` or scaffolded source to inform its output.
|
|
77
|
+
|
|
78
|
+
### 3. Data-driven infra registry (DRY / Open-Closed)
|
|
79
|
+
|
|
80
|
+
All infra knowledge lives in `INFRA_REGISTRY` (`infra.py`) as frozen `InfraDescriptor` instances.
|
|
81
|
+
|
|
82
|
+
- **Adding a new infra type** requires only adding a new `InfraDescriptor` entry to the registry.
|
|
83
|
+
- Do **not** scatter infra-specific if/elif branches across renderer, env example, or service wiring code.
|
|
84
|
+
- The renderer queries the registry generically via `desc.service_env`, `desc.depends_on`, `desc.env_example_lines`.
|
|
85
|
+
- Exception: sqlite is special-cased in `_wire_feature` because it needs per-service volumes with no shared container. This is documented inline.
|
|
86
|
+
|
|
87
|
+
### 4. Single Responsibility (SRP)
|
|
88
|
+
|
|
89
|
+
- `compose/__init__.py` — re-exports only, zero business logic.
|
|
90
|
+
- `operations.py` — spec mutations (add/remove on disk).
|
|
91
|
+
- `renderer.py` — spec → file content (no I/O decisions, no spec mutations).
|
|
92
|
+
- `scaffolder.py` — cookiecutter execution and filesystem rearrangement.
|
|
93
|
+
- `infra.py` — infra data and lookup helpers.
|
|
94
|
+
- `handlers.py` — interactive/direct CLI handlers. Shared finalization via `_finalize_workspace`.
|
|
95
|
+
|
|
96
|
+
Do not mix responsibilities across these modules.
|
|
97
|
+
|
|
98
|
+
### 5. DRY: No duplicate definitions
|
|
99
|
+
|
|
100
|
+
- **Infra wiring** (service env, depends_on, env example) is defined once in `InfraDescriptor`, not repeated in renderer, env builder, or handlers.
|
|
101
|
+
- **Workspace finalization** (create dir → add infra → add services → render) is in `_finalize_workspace`, shared by both `create_workspace` and `create_workspace_from_preset`.
|
|
102
|
+
- **DB detection** is in `infra.detect_configured_db`, not duplicated in renderer and handlers.
|
|
103
|
+
- **Model types** are defined in `models/spec.py`, re-exported through `models/__init__.py` and `compose/__init__.py` — no shim modules or alias files.
|
|
104
|
+
|
|
105
|
+
### 6. Idempotent scaffolding
|
|
106
|
+
|
|
107
|
+
`scaffold_service` skips silently if `src/<service-name>/` already exists.
|
|
108
|
+
User edits to scaffolded files are never overwritten on re-render.
|
|
109
|
+
|
|
110
|
+
- `.env` is created only if missing (user customizations preserved).
|
|
111
|
+
- All other workspace files (compose, env example, pyproject, README, gitignore) are overwritten on every render (they are derived from spec).
|
|
112
|
+
|
|
113
|
+
### 7. Workspace filesystem contract
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
<ws-root>/
|
|
117
|
+
csrd-compose.yaml ← spec (source of truth)
|
|
118
|
+
docker-compose.yml ← rendered from spec
|
|
119
|
+
.env.example ← rendered from spec
|
|
120
|
+
.env ← created once, never overwritten
|
|
121
|
+
pyproject.toml ← rendered from spec (pytest config)
|
|
122
|
+
README.md ← rendered from spec
|
|
123
|
+
.gitignore ← rendered from spec
|
|
124
|
+
.csrd-workspace ← marker file
|
|
125
|
+
Dockerfile.<service-name> ← one per service, at root
|
|
126
|
+
src/<service-name>/ ← service source (kebab-case dir)
|
|
127
|
+
tests/unit/<service_snake>/ ← unit tests (snake_case dir)
|
|
128
|
+
tests/acceptance/<service_snake>/ ← acceptance stubs (snake_case dir)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- Service source dirs use **kebab-case** (matching `service.name`).
|
|
132
|
+
- Test dirs use **snake_case** (valid Python package names).
|
|
133
|
+
- Dockerfiles use kebab-case service name for the suffix, snake_case for the Python module reference in CMD.
|
|
134
|
+
|
|
135
|
+
### 8. Consistent model paradigm
|
|
136
|
+
|
|
137
|
+
All models use **Pydantic `BaseModel`** (from `models/base.py`).
|
|
138
|
+
Do not use `@dataclass` for spec or preset models.
|
|
139
|
+
Use `model_config = ConfigDict(frozen=True)` for immutable models (e.g. `PresetDefinition`).
|
|
140
|
+
|
|
141
|
+
Exception: `InfraDescriptor` in `infra.py` uses `@dataclass(frozen=True)` because it is a
|
|
142
|
+
compose-internal data structure, not a serializable spec model.
|
|
143
|
+
|
|
144
|
+
### 9. Exception semantics
|
|
145
|
+
|
|
146
|
+
- `FileNotFoundError` — missing spec file (`loader.py`).
|
|
147
|
+
- `ValueError` — invalid spec content, duplicate service/infra, unconfigured infra removal.
|
|
148
|
+
- `KeyError` — unknown infra type in registry lookup.
|
|
149
|
+
|
|
150
|
+
Do not use generic `Exception` or mismatched exception types.
|
|
151
|
+
|
|
152
|
+
### 10. Declarative item definitions
|
|
153
|
+
|
|
154
|
+
Menu items are declared as static `list[MenuItem]` constants (or built by a
|
|
155
|
+
simple factory when runtime data like `default_name` is needed).
|
|
156
|
+
|
|
157
|
+
- **Do not** build menus with imperative if/else chains.
|
|
158
|
+
- Use the `visible` predicate on `MenuItem` to hide items based on context.
|
|
159
|
+
The `filter_visible()` helper evaluates these at menu-build time.
|
|
160
|
+
|
|
161
|
+
### 11. Dual-mode: interactive + direct dispatch
|
|
162
|
+
|
|
163
|
+
Every interactive flow must have a non-interactive (direct) counterpart
|
|
164
|
+
invokable via CLI flags (`target`, `--name`, `--database`, etc.).
|
|
165
|
+
|
|
166
|
+
- `run_generate_menu` routes direct targets first, then falls through to the
|
|
167
|
+
interactive menu. Keep this order: direct dispatch → interactive.
|
|
168
|
+
- Direct handlers are named `*_direct`, interactive ones `*_interactive`.
|
|
169
|
+
|
|
170
|
+
### 12. TTY / non-TTY graceful degradation
|
|
171
|
+
|
|
172
|
+
All interactive primitives must work in both TTY and non-TTY environments:
|
|
173
|
+
|
|
174
|
+
- **TTY path**: arrow-key navigation via `cbreak_mode()`, `>` cursor marker.
|
|
175
|
+
- **Non-TTY path**: numbered list with text input fallback.
|
|
176
|
+
- Gate on `sys.stdin.isatty() and sys.stdout.isatty()` — never assume a terminal.
|
|
177
|
+
- `cbreak_mode()` yields `None` when `termios` is unavailable; always handle that.
|
|
178
|
+
- Escape key detection uses `select.select()` with a 50 ms timeout via
|
|
179
|
+
`_read_escape_seq()` to distinguish standalone Escape from arrow-key
|
|
180
|
+
prefixes (`\x1b[A`/`\x1b[B`).
|
|
181
|
+
|
|
182
|
+
### 13. Cancel, interrupt, and back-navigation
|
|
183
|
+
|
|
184
|
+
**Ctrl-C (KeyboardInterrupt)**:
|
|
185
|
+
- Must **propagate** from all prompt primitives — they must NOT swallow it.
|
|
186
|
+
- Only the outermost handler or `run_menu` catches it.
|
|
187
|
+
- Handlers print `"Cancelled."` (wrapped in `contextlib.suppress`) and
|
|
188
|
+
return exit code `1`.
|
|
189
|
+
- Swallowing at the primitive level causes fall-through — this is a bug.
|
|
190
|
+
|
|
191
|
+
**Escape / back (GoBack)**:
|
|
192
|
+
- `GoBack` is a lightweight exception raised by `prompt_single_select` and
|
|
193
|
+
`prompt_multi_select` when the user presses Escape (TTY) or types `0` /
|
|
194
|
+
`back` (non-TTY).
|
|
195
|
+
- In **nested flows** (e.g. `_prompt_infra_categories`), catch `GoBack`
|
|
196
|
+
from sub-selects and loop back to the parent prompt.
|
|
197
|
+
- In **top-level handlers**, catch `GoBack` and skip the step (empty selection).
|
|
198
|
+
- In **`run_menu`**, Escape = cancel (same as selecting the cancel item).
|
|
199
|
+
- Visual hints: `"Esc back"` (TTY), `"0) back"` (non-TTY).
|
|
200
|
+
|
|
201
|
+
**EOF (EOFError)**:
|
|
202
|
+
- Return quietly with the default/empty value from primitives.
|
|
203
|
+
- `run_menu` returns `0`.
|
|
204
|
+
|
|
205
|
+
### 14. Exit code contract
|
|
206
|
+
|
|
207
|
+
All handlers and menu runners return an `int` exit code:
|
|
208
|
+
|
|
209
|
+
| Code | Meaning |
|
|
210
|
+
|---|---|
|
|
211
|
+
| `0` | Success or clean cancel |
|
|
212
|
+
| `1` | Error or user interrupt |
|
|
213
|
+
|
|
214
|
+
Do not raise exceptions to signal user-facing outcomes; print and return a code.
|
|
215
|
+
|
|
216
|
+
### 15. Prompt conventions
|
|
217
|
+
|
|
218
|
+
- `prompt_text(question, default=...)` — returns default on empty input or EOF.
|
|
219
|
+
In TTY mode uses `_tty_line_input` (cbreak-mode character-by-character
|
|
220
|
+
reading) so that Escape raises `GoBack` and Ctrl-C raises
|
|
221
|
+
`KeyboardInterrupt`. Non-TTY falls back to `input()`.
|
|
222
|
+
- `prompt_yes_no(question, default=True)` — same TTY/non-TTY split. First
|
|
223
|
+
prompt defaults yes; "add another?" prompts default no.
|
|
224
|
+
- `prompt_single_select` — auto-selects when only one option exists. Escape
|
|
225
|
+
raises `GoBack`.
|
|
226
|
+
- `prompt_multi_select` — spacebar toggles, Enter confirms; non-TTY accepts
|
|
227
|
+
comma-separated numbers, `all`, or `back`/`0`. Escape raises `GoBack`.
|
|
228
|
+
|
|
229
|
+
All four prompts support Escape for back-navigation. Do not invent new prompt
|
|
230
|
+
primitives without adding them to `helpers.py` first.
|
|
231
|
+
|
|
232
|
+
### 16. Composable wizard flows
|
|
233
|
+
|
|
234
|
+
Multi-step interactive flows **must** use `run_wizard()` from `helpers.py`
|
|
235
|
+
with a list of `WizardStep` callables. Each step is a function that receives
|
|
236
|
+
a shared `state: dict[str, object]` and either returns normally (advance) or
|
|
237
|
+
raises `GoBack` (go back). The wizard handles all back-navigation and
|
|
238
|
+
interrupt logic automatically.
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
# ✅ Correct — composable steps, no control flow boilerplate
|
|
242
|
+
steps: list[WizardStep] = [
|
|
243
|
+
_step_workspace_name,
|
|
244
|
+
_step_infra_categories,
|
|
245
|
+
_step_services,
|
|
246
|
+
_step_git_init,
|
|
247
|
+
]
|
|
248
|
+
state = run_wizard(steps)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
# ❌ Prohibited — hand-coded if/elif step dispatch
|
|
253
|
+
while step <= 3:
|
|
254
|
+
if step == 0:
|
|
255
|
+
...
|
|
256
|
+
elif step == 1:
|
|
257
|
+
...
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Rules:
|
|
261
|
+
- **Never** use raw `while step <= N` / `if step == 0` dispatch in handlers.
|
|
262
|
+
- Each step must be an independent `_step_*` function in `handlers.py`.
|
|
263
|
+
- Steps that appear in more than one wizard flow must be shared (e.g.
|
|
264
|
+
`_step_workspace_name`, `_step_git_init`).
|
|
265
|
+
- Steps communicate exclusively through the shared state dict.
|
|
266
|
+
- `GoBack` at step 0 = cancel (exit code 0).
|
|
267
|
+
- `KeyboardInterrupt` at any step = abort (exit code 1).
|
|
268
|
+
|
|
269
|
+
Back-navigation semantics:
|
|
270
|
+
```
|
|
271
|
+
Step 0: name ← Escape here = cancel (exit code 0)
|
|
272
|
+
Step 1: infra ← Escape here = back to step 0
|
|
273
|
+
Step 2: services ← Escape here = back to step 1
|
|
274
|
+
Step 3: git init ← Escape here = back to step 2
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
### 17. Naming & normalization
|
|
279
|
+
|
|
280
|
+
- Service names: `normalize_service_name()` → kebab-case, appends `-service`.
|
|
281
|
+
- Workspace names: `resolve_workspace_name()` → collapse whitespace, reject
|
|
282
|
+
path separators.
|
|
283
|
+
- Always print a `"Normalized to: ..."` message when the name changes.
|
|
284
|
+
|
|
285
|
+
### 18. Category-based infra selection
|
|
286
|
+
|
|
287
|
+
Infrastructure is organized by category (`database`, `messaging`, `caching`)
|
|
288
|
+
defined in `models/spec.py` via `INFRA_CATEGORIES`.
|
|
289
|
+
|
|
290
|
+
- Step 1: multi-select categories.
|
|
291
|
+
- Step 2: for each category, single-select the specific type (auto-select if
|
|
292
|
+
only one member).
|
|
293
|
+
- Already-configured types are excluded from the available set.
|
|
294
|
+
|
|
295
|
+
Do not bypass the category system with flat infra-type lists.
|
|
296
|
+
|
|
297
|
+
### 19. Context awareness
|
|
298
|
+
|
|
299
|
+
Menus adapt based on workspace presence:
|
|
300
|
+
|
|
301
|
+
- **Inside workspace** (`csrd-compose.yaml` exists): show `add service`,
|
|
302
|
+
`add infra` (if slots remain), `remove infra` (if any configured), `cancel`.
|
|
303
|
+
- **Outside workspace**: show `custom`, `preset`, `cancel`.
|
|
304
|
+
|
|
305
|
+
Context predicates (`_has_workspace`, `_has_available_infra`,
|
|
306
|
+
`_has_configured_infra`) must be pure filesystem checks — no global state,
|
|
307
|
+
no caching.
|
|
308
|
+
|
|
309
|
+
Alias uniqueness: each alias must be unique within a menu list. Do not reuse
|
|
310
|
+
a shortcut alias across items in the same list (e.g. `"c"` for both `custom`
|
|
311
|
+
and `cancel`).
|
|
312
|
+
|
|
313
|
+
### 20. No runtime auto-discovery
|
|
314
|
+
|
|
315
|
+
Menu items, infra types, and preset definitions are statically registered.
|
|
316
|
+
Do not add plugin loaders, dynamic module scanning, or entry-point discovery.
|
|
317
|
+
This aligns with the top-level `AGENTS.md` cookiecutter guardrail.
|
|
318
|
+
|
|
319
|
+
## Audit checklist
|
|
320
|
+
|
|
321
|
+
When asked to "audit the menus or compose system", verify each of the following:
|
|
322
|
+
|
|
323
|
+
### Menu audit
|
|
324
|
+
1. Every `MenuItem` has a `label`, at least two `aliases` (full name + shortcut), and a handler.
|
|
325
|
+
2. Every menu list ends with a `cancel` item.
|
|
326
|
+
3. `filter_visible()` is called before passing items to `run_menu()`.
|
|
327
|
+
4. All interactive flows have a `*_direct` counterpart wired in `run_generate_menu`.
|
|
328
|
+
5. TTY and non-TTY paths are both exercised (check for `isatty` gates).
|
|
329
|
+
6. `KeyboardInterrupt` and `EOFError` are caught in every prompt/menu call.
|
|
330
|
+
7. Exit codes follow the `0`/`1` contract.
|
|
331
|
+
8. Infra selection uses the category flow, not a flat list.
|
|
332
|
+
9. Names are normalized and the user is informed of changes.
|
|
333
|
+
10. No new prompt primitives exist outside `helpers.py`.
|
|
334
|
+
11. The three-layer separation (helpers → menu → handlers) is preserved.
|
|
335
|
+
12. Tests in `tests/test_cli.py` and `tests/test_v2_generate*.py` cover new/changed items.
|
|
336
|
+
13. All multi-step interactive flows use `run_wizard()` with a list of `WizardStep` callables — no raw `while step <= N` / `if step == 0` dispatch exists in handlers.
|
|
337
|
+
14. No step logic is duplicated across wizard flows — shared steps (`_step_workspace_name`, `_step_git_init`, etc.) are extracted to `_step_*` functions.
|
|
338
|
+
|
|
339
|
+
### Compose / renderer audit
|
|
340
|
+
15. All render functions are pure `spec → string` transforms with no side effects, no env reads, no filesystem inspection.
|
|
341
|
+
16. `test_render_workspace_is_idempotent` passes — two consecutive renders produce identical output.
|
|
342
|
+
17. Infra wiring uses `INFRA_REGISTRY` descriptors — no hardcoded if/elif branches (except the documented sqlite special case).
|
|
343
|
+
18. `.env` is created-if-missing, all other files are overwritten from spec.
|
|
344
|
+
19. `compose/__init__.py` contains only re-exports — no business logic.
|
|
345
|
+
20. `operations.py` functions load → mutate → save spec — no rendering.
|
|
346
|
+
21. Workspace finalization uses `_finalize_workspace` — no duplicated create/add/render sequences in handlers.
|
|
347
|
+
22. `detect_configured_db` is defined once in `infra.py` — not duplicated.
|
|
348
|
+
23. Test dirs use snake_case, source dirs use kebab-case, Dockerfiles use kebab-case suffix.
|
|
349
|
+
24. No shim modules or alias re-export files exist (pre-1.0 refactor policy).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Namespace package for next-gen csrd compose and generate flows."""
|