csrd-utils 0.3.82__tar.gz → 0.3.85__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.
Files changed (162) hide show
  1. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/PKG-INFO +3 -1
  2. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/pyproject.toml +3 -1
  3. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/__main__.py +192 -0
  4. csrd_utils-0.3.85/src/csrd_utils/v2/__init__.py +1 -0
  5. csrd_utils-0.3.85/src/csrd_utils/v2/compose/__init__.py +180 -0
  6. csrd_utils-0.3.85/src/csrd_utils/v2/compose/git.py +17 -0
  7. csrd_utils-0.3.85/src/csrd_utils/v2/compose/infra.py +178 -0
  8. csrd_utils-0.3.85/src/csrd_utils/v2/compose/loader.py +38 -0
  9. csrd_utils-0.3.85/src/csrd_utils/v2/compose/presets.py +58 -0
  10. csrd_utils-0.3.85/src/csrd_utils/v2/compose/renderer.py +226 -0
  11. csrd_utils-0.3.85/src/csrd_utils/v2/compose/spec.py +15 -0
  12. csrd_utils-0.3.85/src/csrd_utils/v2/compose/yaml_editor.py +134 -0
  13. csrd_utils-0.3.85/src/csrd_utils/v2/generate/AGENTS.md +180 -0
  14. csrd_utils-0.3.85/src/csrd_utils/v2/generate/__init__.py +5 -0
  15. csrd_utils-0.3.85/src/csrd_utils/v2/generate/handlers.py +694 -0
  16. csrd_utils-0.3.85/src/csrd_utils/v2/generate/helpers.py +569 -0
  17. csrd_utils-0.3.85/src/csrd_utils/v2/generate/menu.py +170 -0
  18. csrd_utils-0.3.85/src/csrd_utils/v2/models/__init__.py +39 -0
  19. csrd_utils-0.3.85/src/csrd_utils/v2/models/base.py +10 -0
  20. csrd_utils-0.3.85/src/csrd_utils/v2/models/spec.py +103 -0
  21. csrd_utils-0.3.85/src/csrd_utils/v2/models/types.py +12 -0
  22. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/.gitignore +0 -0
  23. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/README.md +0 -0
  24. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/__init__.py +0 -0
  25. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/audit.py +0 -0
  26. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/augmentor.py +0 -0
  27. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/cluster.py +0 -0
  28. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/doctor.py +0 -0
  29. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
  30. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
  31. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/caching/manifest.yaml +0 -0
  32. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/caching/requirements.fragment +0 -0
  33. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
  34. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
  35. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
  36. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
  37. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/logging/manifest.yaml +0 -0
  38. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/logging/requirements.fragment +0 -0
  39. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
  40. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
  41. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
  42. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
  43. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
  44. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
  45. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
  46. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
  47. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
  48. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
  49. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
  50. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
  51. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
  52. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
  53. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
  54. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
  55. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
  56. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
  57. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/workers/manifest.yaml +0 -0
  58. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/workers/requirements.fragment +0 -0
  59. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
  60. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
  61. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
  62. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
  63. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
  64. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/fragments.py +0 -0
  65. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/generator.py +0 -0
  66. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/resources.py +0 -0
  67. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
  68. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +0 -0
  69. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +0 -0
  70. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.cookiecutterignore +0 -0
  71. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
  72. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  73. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  74. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  75. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/cache.py +0 -0
  76. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
  77. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/upstreams.py +0 -0
  78. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  79. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/auth_service.py +0 -0
  80. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/delegates.py +0 -0
  81. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
  82. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/worker_broker.py +0 -0
  83. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/docker-compose.yml +0 -0
  84. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/__init__.py +0 -0
  85. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/logging.py +0 -0
  86. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
  87. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
  88. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
  89. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  90. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/__init__.py +0 -0
  91. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/__init__.py +0 -0
  92. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/test_logging_middleware.py +0 -0
  93. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/conftest.py +0 -0
  94. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/test_items.py +0 -0
  95. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/__init__.py +0 -0
  96. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_cache.py +0 -0
  97. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_database_settings.py +0 -0
  98. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_delegates.py +0 -0
  99. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_settings_contracts.py +0 -0
  100. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_worker_broker.py +0 -0
  101. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  102. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
  103. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
  104. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
  105. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/__init__.py +0 -0
  106. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/tasks.py +0 -0
  107. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/cookiecutter.json +0 -0
  108. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
  109. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  110. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  111. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  112. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
  113. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/delegates/auth_delegate.py +0 -0
  114. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/delegates/identity_delegate.py +0 -0
  115. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  116. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/auth_passthrough.py +0 -0
  117. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/auth_service.py +0 -0
  118. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
  119. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/models/__init__.py +0 -0
  120. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/models/auth_service.py +0 -0
  121. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/models/items.py +0 -0
  122. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
  123. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
  124. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
  125. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  126. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  127. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
  128. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/auth_service_view.py +0 -0
  129. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
  130. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
  131. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/cookiecutter.json +0 -0
  132. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
  133. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  134. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  135. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  136. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  137. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/models/__init__.py +0 -0
  138. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/models/auth.py +0 -0
  139. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
  140. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/repositories/token_repository.py +0 -0
  141. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
  142. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  143. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  144. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
  145. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/unversioned/auth_view.py +0 -0
  146. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
  147. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/cookiecutter.json +0 -0
  148. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
  149. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  150. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  151. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  152. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  153. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/models/__init__.py +0 -0
  154. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/models/users.py +0 -0
  155. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
  156. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/repositories/user_repository.py +0 -0
  157. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
  158. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  159. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  160. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
  161. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
  162. {csrd_utils-0.3.82 → csrd_utils-0.3.85}/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.82
3
+ Version: 0.3.85
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.82"
3
+ version = "0.3.85"
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 @@
1
+ """Namespace package for next-gen csrd compose and generate flows."""
@@ -0,0 +1,180 @@
1
+ """Compose package namespace."""
2
+
3
+ from pathlib import Path
4
+
5
+ from .loader import (
6
+ SPEC_FILENAME,
7
+ default_spec,
8
+ load_spec,
9
+ save_spec,
10
+ spec_file_path,
11
+ )
12
+ from .presets import (
13
+ PRESET_REGISTRY,
14
+ PresetDefinition,
15
+ list_presets,
16
+ members_for_category,
17
+ )
18
+ from .renderer import render_workspace
19
+ from .spec import (
20
+ INFRA_ALL_TYPES,
21
+ INFRA_CATEGORIES,
22
+ INFRA_DATABASES,
23
+ ComposeSpec,
24
+ InfraNode,
25
+ ServiceNode,
26
+ )
27
+ from .yaml_editor import (
28
+ deep_merge,
29
+ dumps_yaml,
30
+ ensure_list_item,
31
+ load_yaml,
32
+ save_yaml,
33
+ set_key,
34
+ )
35
+
36
+
37
+ def apply(output_dir: Path, *, git_init: bool = False) -> Path:
38
+ """Apply baseline empty-workspace rendering into `output_dir`."""
39
+
40
+ return render_workspace(output_dir, git_init=git_init)
41
+
42
+
43
+ def validate(output_dir: Path) -> ComposeSpec:
44
+ """Validate and return the canonical compose spec for a workspace."""
45
+
46
+ return load_spec(spec_file_path(output_dir))
47
+
48
+
49
+ def add_service(output_dir: Path, service: ServiceNode) -> ComposeSpec:
50
+ """Append a service to the workspace spec and persist to disk.
51
+
52
+ Returns the updated spec.
53
+ """
54
+
55
+ spec_path = spec_file_path(output_dir)
56
+ spec = load_spec(spec_path)
57
+
58
+ existing_names = {s.name for s in spec.services}
59
+ if service.name in existing_names:
60
+ raise ValueError(f"Service '{service.name}' already exists in workspace spec")
61
+
62
+ spec.services.append(service)
63
+ save_spec(spec, spec_path)
64
+ return spec
65
+
66
+
67
+ def add_infra(output_dir: Path, infra: InfraNode) -> ComposeSpec:
68
+ # ...existing docstring and body...
69
+ """Add or replace an infrastructure entry in the workspace spec.
70
+
71
+ Database types are mutually exclusive — adding one replaces any existing
72
+ database entry. Other infra types (redis, rabbitmq) are singletons that
73
+ cannot be duplicated.
74
+
75
+ Returns the updated spec.
76
+ """
77
+
78
+ spec_path = spec_file_path(output_dir)
79
+ spec = load_spec(spec_path)
80
+
81
+ existing_types = {i.type for i in spec.infra}
82
+
83
+ if infra.type in INFRA_DATABASES:
84
+ # Remove any existing database entry (mutually exclusive)
85
+ spec.infra = [i for i in spec.infra if i.type not in INFRA_DATABASES]
86
+ elif infra.type in existing_types:
87
+ raise ValueError(f"Infra '{infra.type}' already exists in workspace spec")
88
+
89
+ spec.infra.append(infra)
90
+ save_spec(spec, spec_path)
91
+ return spec
92
+
93
+
94
+ def remove_infra(output_dir: Path, infra_type: str) -> ComposeSpec:
95
+ """Remove an infrastructure entry from the workspace spec.
96
+
97
+ Raises ``ValueError`` if *infra_type* is not currently configured.
98
+
99
+ Returns the updated spec.
100
+ """
101
+
102
+ spec_path = spec_file_path(output_dir)
103
+ spec = load_spec(spec_path)
104
+
105
+ existing_types = {i.type for i in spec.infra}
106
+ if infra_type not in existing_types:
107
+ raise ValueError(f"Infra '{infra_type}' is not configured in workspace spec")
108
+
109
+ spec.infra = [i for i in spec.infra if i.type != infra_type]
110
+ save_spec(spec, spec_path)
111
+ return spec
112
+
113
+
114
+ def available_infra(workspace_dir: Path) -> list[str]:
115
+ """Return infra types not yet present in the workspace spec.
116
+
117
+ Database types are mutually exclusive — if any database is already
118
+ configured, all database options are excluded.
119
+ """
120
+
121
+ spec_path = spec_file_path(workspace_dir)
122
+ if not spec_path.is_file():
123
+ return sorted(INFRA_ALL_TYPES)
124
+
125
+ spec = load_spec(spec_path)
126
+ existing = {i.type for i in spec.infra}
127
+
128
+ available: list[str] = []
129
+ has_db = bool(existing & INFRA_DATABASES)
130
+ for infra_type in sorted(INFRA_ALL_TYPES):
131
+ if infra_type in existing:
132
+ continue
133
+ if has_db and infra_type in INFRA_DATABASES:
134
+ continue
135
+ available.append(infra_type)
136
+ return available
137
+
138
+
139
+ def configured_infra(workspace_dir: Path) -> list[str]:
140
+ """Return infra types currently configured in the workspace spec."""
141
+
142
+ spec_path = spec_file_path(workspace_dir)
143
+ if not spec_path.is_file():
144
+ return []
145
+
146
+ spec = load_spec(spec_path)
147
+ return sorted(i.type for i in spec.infra)
148
+
149
+
150
+ __all__ = [
151
+ "INFRA_ALL_TYPES",
152
+ "INFRA_CATEGORIES",
153
+ "INFRA_DATABASES",
154
+ "PRESET_REGISTRY",
155
+ "SPEC_FILENAME",
156
+ "ComposeSpec",
157
+ "InfraNode",
158
+ "PresetDefinition",
159
+ "ServiceNode",
160
+ "add_infra",
161
+ "add_service",
162
+ "apply",
163
+ "available_infra",
164
+ "configured_infra",
165
+ "deep_merge",
166
+ "default_spec",
167
+ "dumps_yaml",
168
+ "ensure_list_item",
169
+ "list_presets",
170
+ "load_spec",
171
+ "load_yaml",
172
+ "members_for_category",
173
+ "remove_infra",
174
+ "render_workspace",
175
+ "save_spec",
176
+ "save_yaml",
177
+ "set_key",
178
+ "spec_file_path",
179
+ "validate",
180
+ ]
@@ -0,0 +1,17 @@
1
+ """Git integration helpers for compose workspaces."""
2
+
3
+ import subprocess
4
+ from pathlib import Path
5
+
6
+
7
+ def maybe_git_init(output_dir: Path) -> None:
8
+ """Run ``git init`` inside *output_dir* if the directory is not already a git repo."""
9
+
10
+ if (output_dir / ".git").exists():
11
+ return
12
+ subprocess.run(
13
+ ["git", "init"],
14
+ cwd=output_dir,
15
+ check=True,
16
+ capture_output=True,
17
+ )
@@ -0,0 +1,178 @@
1
+ """Infrastructure container definitions for docker-compose rendering.
2
+
3
+ Each definition is a pure data function — no I/O, no side effects.
4
+ The renderer calls these to build the ``services`` and ``volumes``
5
+ sections of ``docker-compose.yml``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from ..models import InfraRenderer
13
+
14
+ # ---------------------------------------------------------------------------
15
+ # Healthcheck presets
16
+ # ---------------------------------------------------------------------------
17
+
18
+
19
+ def _healthcheck(
20
+ test: list[str], interval: str = "5s", timeout: str = "3s", retries: int = 3
21
+ ) -> dict[str, Any]:
22
+ return {
23
+ "test": test,
24
+ "interval": interval,
25
+ "timeout": timeout,
26
+ "retries": retries,
27
+ "start_period": "10s",
28
+ }
29
+
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Container definitions
33
+ # ---------------------------------------------------------------------------
34
+
35
+
36
+ def postgres_service() -> tuple[dict[str, Any], dict[str, None]]:
37
+ """Return (service_dict, volumes_dict) for a Postgres container."""
38
+
39
+ service: dict[str, Any] = {
40
+ "image": "postgres:16-alpine",
41
+ "restart": "unless-stopped",
42
+ "environment": {
43
+ "POSTGRES_DB": "${DB_NAME:-service_db}",
44
+ "POSTGRES_USER": "${DB_USER:-service_user}",
45
+ "POSTGRES_PASSWORD": "${DB_PASSWORD:-change_me}",
46
+ },
47
+ "ports": ["5432:5432"],
48
+ "volumes": ["postgres-data:/var/lib/postgresql/data"],
49
+ "healthcheck": _healthcheck(
50
+ ["CMD-SHELL", "pg_isready -U ${DB_USER:-service_user} -d ${DB_NAME:-service_db}"],
51
+ ),
52
+ }
53
+ volumes: dict[str, None] = {"postgres-data": None}
54
+ return service, volumes
55
+
56
+
57
+ def mariadb_service() -> tuple[dict[str, Any], dict[str, None]]:
58
+ """Return (service_dict, volumes_dict) for a MariaDB container."""
59
+
60
+ service: dict[str, Any] = {
61
+ "image": "mariadb:11",
62
+ "restart": "unless-stopped",
63
+ "environment": {
64
+ "MYSQL_ROOT_PASSWORD": "${MYSQL_ROOT_PASSWORD:-change_me_root}",
65
+ "MYSQL_DATABASE": "${DB_NAME:-service_db}",
66
+ "MYSQL_USER": "${DB_USER:-service_user}",
67
+ "MYSQL_PASSWORD": "${DB_PASSWORD:-change_me}",
68
+ },
69
+ "ports": ["3306:3306"],
70
+ "volumes": ["mariadb-data:/var/lib/mysql"],
71
+ "healthcheck": _healthcheck(
72
+ ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"],
73
+ ),
74
+ }
75
+ volumes: dict[str, None] = {"mariadb-data": None}
76
+ return service, volumes
77
+
78
+
79
+ def redis_service() -> tuple[dict[str, Any], dict[str, None]]:
80
+ """Return (service_dict, volumes_dict) for a Redis container."""
81
+
82
+ service: dict[str, Any] = {
83
+ "image": "redis:7-alpine",
84
+ "restart": "unless-stopped",
85
+ "ports": ["6379:6379"],
86
+ "healthcheck": _healthcheck(
87
+ ["CMD", "redis-cli", "ping"],
88
+ ),
89
+ }
90
+ return service, {}
91
+
92
+
93
+ def rabbitmq_service() -> tuple[dict[str, Any], dict[str, None]]:
94
+ """Return (service_dict, volumes_dict) for a RabbitMQ container."""
95
+
96
+ service: dict[str, Any] = {
97
+ "image": "rabbitmq:3-management-alpine",
98
+ "restart": "unless-stopped",
99
+ "environment": {
100
+ "RABBITMQ_DEFAULT_USER": "${RABBITMQ_USER:-service_rabbit}",
101
+ "RABBITMQ_DEFAULT_PASS": "${RABBITMQ_PASSWORD:-change_me}",
102
+ },
103
+ "ports": ["5672:5672", "15672:15672"],
104
+ "healthcheck": _healthcheck(
105
+ ["CMD", "rabbitmq-diagnostics", "-q", "ping"],
106
+ ),
107
+ }
108
+ return service, {}
109
+
110
+
111
+ # ---------------------------------------------------------------------------
112
+ # Registry
113
+ # ---------------------------------------------------------------------------
114
+
115
+ INFRA_RENDERERS: dict[str, InfraRenderer] = {
116
+ "postgres": postgres_service,
117
+ "mariadb": mariadb_service,
118
+ "redis": redis_service,
119
+ "rabbitmq": rabbitmq_service,
120
+ }
121
+ """Map infra type name → factory that returns ``(service_dict, volumes_dict)``."""
122
+
123
+
124
+ def render_infra(infra_type: str) -> tuple[str, dict[str, Any], dict[str, None]]:
125
+ """Render an infra type into its compose service name, service dict, and volumes.
126
+
127
+ Raises ``KeyError`` if *infra_type* has no renderer (e.g. ``sqlite``
128
+ which needs no container).
129
+ """
130
+
131
+ factory = INFRA_RENDERERS[infra_type]
132
+ service_def, volumes = factory()
133
+ return infra_type, service_def, volumes
134
+
135
+
136
+ # ---------------------------------------------------------------------------
137
+ # Service environment wiring
138
+ # ---------------------------------------------------------------------------
139
+
140
+
141
+ def service_env_block(db_type: str) -> dict[str, str]:
142
+ """Return the environment variables a service needs to connect to *db_type*.
143
+
144
+ For ``postgres``/``mariadb`` the vars point at the shared infra container.
145
+ For ``sqlite`` the var points at a local file path (volume-mounted).
146
+ """
147
+
148
+ if db_type == "postgres":
149
+ return {
150
+ "DB_HOST": "postgres",
151
+ "DB_PORT": "5432",
152
+ "DB_NAME": "${DB_NAME:-service_db}",
153
+ "DB_USER": "${DB_USER:-service_user}",
154
+ "DB_PASSWORD": "${DB_PASSWORD:-change_me}",
155
+ }
156
+ if db_type == "mariadb":
157
+ return {
158
+ "DB_HOST": "mariadb",
159
+ "DB_PORT": "3306",
160
+ "DB_NAME": "${DB_NAME:-service_db}",
161
+ "DB_USER": "${DB_USER:-service_user}",
162
+ "DB_PASSWORD": "${DB_PASSWORD:-change_me}",
163
+ }
164
+ if db_type == "sqlite":
165
+ # DB_PATH is set per-service in the renderer; this is the base template.
166
+ return {}
167
+ return {}
168
+
169
+
170
+ def service_depends_on(db_type: str) -> dict[str, Any]:
171
+ """Return ``depends_on`` entries for a service wired to *db_type*.
172
+
173
+ ``sqlite`` returns empty (no container dependency).
174
+ """
175
+
176
+ if db_type in {"postgres", "mariadb"}:
177
+ return {db_type: {"condition": "service_healthy"}}
178
+ return {}