quadkit-cli 0.0.4__tar.gz → 0.0.42__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 (211) hide show
  1. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/.gitignore +32 -1
  2. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/PKG-INFO +4 -4
  3. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/README.md +3 -3
  4. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/ARCHITECTURE.md +2 -2
  5. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/COMMANDS.md +2 -2
  6. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/PROJECT_LAYOUT.md +23 -9
  7. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/pyproject.toml +1 -1
  8. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/config.py +11 -2
  9. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/db_commands_data.py +119 -37
  10. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/new.py +2 -2
  11. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/core.py +1 -1
  12. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/generators/provider.py +2 -2
  13. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/layout.py +45 -2
  14. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/environment.py +52 -2
  15. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/presets.py +2 -2
  16. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/template.py +5 -3
  17. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/module_files.py +18 -1
  18. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/specs.py +6 -4
  19. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_config.py +52 -9
  20. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_template.py +8 -1
  21. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_db.py +50 -0
  22. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_gen_provider.py +2 -2
  23. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_generator_registry.py +1 -1
  24. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_layout.py +2 -0
  25. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_package_names.py +4 -0
  26. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_project_layout.py +1 -1
  27. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/LICENSE +0 -0
  28. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/NOTICE +0 -0
  29. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/CONFIGURATION.md +0 -0
  30. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/GUIDE.md +0 -0
  31. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/HOWTOS.md +0 -0
  32. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/PUBLIC_PACKAGE_CLI_MATRIX.md +0 -0
  33. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/QUICKSTART.md +0 -0
  34. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/TROUBLESHOOTING.md +0 -0
  35. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/example.yaml +0 -0
  36. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/__init__.py +0 -0
  37. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/assembly/__init__.py +0 -0
  38. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/assembly/assembler.py +0 -0
  39. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/__init__.py +0 -0
  40. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/add.py +0 -0
  41. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/config_utils.py +0 -0
  42. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/contrib.py +0 -0
  43. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/db.py +0 -0
  44. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/db_bootstrap.py +0 -0
  45. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/db_commands_migrations.py +0 -0
  46. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/db_commands_ops.py +0 -0
  47. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/dev.py +0 -0
  48. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/events.py +0 -0
  49. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/gen.py +0 -0
  50. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/init.py +0 -0
  51. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/inspect.py +0 -0
  52. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/meta.py +0 -0
  53. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/project.py +0 -0
  54. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/py.typed +0 -0
  55. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/run.py +0 -0
  56. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/shell.py +0 -0
  57. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/system.py +0 -0
  58. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/config.py +0 -0
  59. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/constants.py +0 -0
  60. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/__init__.py +0 -0
  61. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/base.py +0 -0
  62. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/discovery.py +0 -0
  63. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/registry.py +0 -0
  64. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/runtime.py +0 -0
  65. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/decorators.py +0 -0
  66. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/di/__init__.py +0 -0
  67. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/di/provider.py +0 -0
  68. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/di/sub_providers/__init__.py +0 -0
  69. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/di/sub_providers/contributor.py +0 -0
  70. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/events.py +0 -0
  71. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/exceptions.py +0 -0
  72. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/generators/__init__.py +0 -0
  73. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/generators/base.py +0 -0
  74. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/generators/field_parser.py +0 -0
  75. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/generators/test.py +0 -0
  76. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/hooks.py +0 -0
  77. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/lib/__init__.py +0 -0
  78. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/lib/config_loader.py +0 -0
  79. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/lib/entry_point.py +0 -0
  80. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/lib/naming.py +0 -0
  81. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/lib/templates.py +0 -0
  82. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/module.py +0 -0
  83. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/output/__init__.py +0 -0
  84. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/output/manager.py +0 -0
  85. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/package_names.py +0 -0
  86. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/protocols.py +0 -0
  87. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/py.typed +0 -0
  88. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/__init__.py +0 -0
  89. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/_lazy_exports.py +0 -0
  90. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/backend_base.py +0 -0
  91. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/command.py +0 -0
  92. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/completion.py +0 -0
  93. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/config.py +0 -0
  94. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/database.py +0 -0
  95. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/database_backends.py +0 -0
  96. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/formatter.py +0 -0
  97. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/generator.py +0 -0
  98. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health.py +0 -0
  99. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/__init__.py +0 -0
  100. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/base.py +0 -0
  101. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/deps.py +0 -0
  102. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/project.py +0 -0
  103. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/providers.py +0 -0
  104. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/python_env.py +0 -0
  105. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/tools.py +0 -0
  106. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/vcs_docker.py +0 -0
  107. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/hook.py +0 -0
  108. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/inspect.py +0 -0
  109. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/migration.py +0 -0
  110. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/provider.py +0 -0
  111. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/provider_catalog.py +0 -0
  112. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/provider_registry.py +0 -0
  113. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/secrets.py +0 -0
  114. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/serializer.py +0 -0
  115. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/server.py +0 -0
  116. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/task.py +0 -0
  117. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/telemetry.py +0 -0
  118. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/version.py +0 -0
  119. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/watcher.py +0 -0
  120. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/runtime/__init__.py +0 -0
  121. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/runtime/context.py +0 -0
  122. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/runtime/error_handler.py +0 -0
  123. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/runtime/main.py +0 -0
  124. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/__init__.py +0 -0
  125. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/app_files.py +0 -0
  126. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/render.py +0 -0
  127. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/support_files.py +0 -0
  128. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold_package.py +0 -0
  129. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/templates/provider.py.jinja2 +0 -0
  130. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/templates/py.typed +0 -0
  131. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/templates/test_unit.py.jinja2 +0 -0
  132. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/types.py +0 -0
  133. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/py.typed +0 -0
  134. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/conftest.py +0 -0
  135. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/helpers/runner.py +0 -0
  136. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/integration/__init__.py +0 -0
  137. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/integration/test_cli_lifecycle.py +0 -0
  138. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/test_package_structure_p1.py +0 -0
  139. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/__init__.py +0 -0
  140. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/_test_cli_contribution_system_support.py +0 -0
  141. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/__init__.py +0 -0
  142. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_config_happy_paths.py +0 -0
  143. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_contrib.py +0 -0
  144. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_events_cmd.py +0 -0
  145. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_inspect.py +0 -0
  146. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_meta.py +0 -0
  147. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_run.py +0 -0
  148. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_shell.py +0 -0
  149. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_system.py +0 -0
  150. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/di/test_config_alignment.py +0 -0
  151. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/lib/test_templates.py +0 -0
  152. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/__init__.py +0 -0
  153. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_completion.py +0 -0
  154. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_database.py +0 -0
  155. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_environment.py +0 -0
  156. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_formatter.py +0 -0
  157. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_health.py +0 -0
  158. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_hook.py +0 -0
  159. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_inspect_registry.py +0 -0
  160. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_migration.py +0 -0
  161. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_presets.py +0 -0
  162. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_secrets.py +0 -0
  163. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_serializer.py +0 -0
  164. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_task.py +0 -0
  165. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_telemetry.py +0 -0
  166. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_version.py +0 -0
  167. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_watcher.py +0 -0
  168. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_add.py +0 -0
  169. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_base_contributor.py +0 -0
  170. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_constants.py +0 -0
  171. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_context.py +0 -0
  172. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_contribution_system.py +0 -0
  173. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_contribution_system__testclicontributorsubprovider.py +0 -0
  174. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_errors.py +0 -0
  175. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_exceptions.py +0 -0
  176. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_import_isolation.py +0 -0
  177. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_module.py +0 -0
  178. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_types.py +0 -0
  179. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_config_async.py +0 -0
  180. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_config_loader.py +0 -0
  181. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_config_save.py +0 -0
  182. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_config_validate.py +0 -0
  183. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_contributor_registry.py +0 -0
  184. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_contributor_runtime.py +0 -0
  185. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_core_contributor.py +0 -0
  186. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_decorators.py +0 -0
  187. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_dev_command.py +0 -0
  188. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_error_handler.py +0 -0
  189. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_events.py +0 -0
  190. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_exception_handling.py +0 -0
  191. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_field_parser.py +0 -0
  192. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_gen.py +0 -0
  193. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_gen_controller.py +0 -0
  194. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_generator_base.py +0 -0
  195. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_generator_option_help.py +0 -0
  196. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_generators_contract.py +0 -0
  197. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_health_checks.py +0 -0
  198. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_hooks.py +0 -0
  199. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_init_command.py +0 -0
  200. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_main.py +0 -0
  201. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_new.py +0 -0
  202. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_output.py +0 -0
  203. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_output_json.py +0 -0
  204. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_project.py +0 -0
  205. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_provider_registry.py +0 -0
  206. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_public_surface.py +0 -0
  207. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_round40_hardening.py +0 -0
  208. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_run_detection.py +0 -0
  209. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_scaffold_and_validators.py +0 -0
  210. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_server_registry.py +0 -0
  211. {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_storage_driver_generator.py +0 -0
@@ -149,6 +149,27 @@ apps/**/*.pyc
149
149
  apps/**/*.db
150
150
  apps/**/*.db-wal
151
151
  apps/**/*.db-shm
152
+ apps/**/.env
153
+ apps/**/.env.*
154
+ !apps/**/.env.example
155
+ !apps/**/.env.full.example
156
+
157
+ # Piccolina — nested app tracked in this monorepo (standalone .git removed).
158
+ # Keep marketing packages and deployment secrets out of the tree.
159
+ apps/piccolina/piccolina-marketing/
160
+ apps/piccolina/piccolina-marketing-plans/
161
+ apps/piccolina/secrets/
162
+ apps/piccolina/**/secrets/
163
+ apps/piccolina/**/*.pem
164
+ apps/piccolina/**/*.key
165
+ apps/piccolina/**/.venv/
166
+ apps/piccolina/**/node_modules/
167
+ apps/piccolina/**/__pycache__/
168
+ apps/piccolina/**/*.pyc
169
+ apps/piccolina/**/*.pbf
170
+ apps/piccolina/**/.open-next/
171
+ apps/piccolina/**/storage/
172
+ apps/piccolina/**/.deploy-host
152
173
 
153
174
  # Re-exclude private content under the tiered paths.
154
175
  # MUST come after the !...docs/** negations above — last match wins.
@@ -180,4 +201,14 @@ test.db
180
201
  !packages/*/quadkit*/docs/gifs/
181
202
  !packages/*/quadkit*/docs/gifs/**
182
203
  examples/*/screenshot.png
183
- !banner*
204
+ !banner*
205
+ apps/quadkit-playground/**/node_modules/
206
+ apps/quadkit-playground/**/dist/
207
+ apps/quadkit-playground/**/__pycache__/
208
+ apps/quadkit-playground/**/*.pyc
209
+ apps/quadkit-playground/**/*.db
210
+ apps/quadkit-playground/**/*.db-wal
211
+ apps/quadkit-playground/**/*.db-shm
212
+ apps/quadkit-playground/projects/
213
+ apps/quadkit-playground/quadkit/
214
+ docs/superpowers/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: quadkit-cli
3
- Version: 0.0.4
3
+ Version: 0.0.42
4
4
  Summary: Command-line interface for Quadkit Framework - Project scaffolding, code generation, and development tools
5
5
  Project-URL: Homepage, https://dbtinoy-.github.io/quadkit/
6
6
  Project-URL: Repository, https://github.com/dbtinoy-/quadkit
@@ -167,10 +167,10 @@ review release notes on upgrade. Report vulnerabilities privately per
167
167
 
168
168
  ## Stability
169
169
 
170
- Version `0.0.3` in the `0.x` series, released in lockstep with the
170
+ Version `0.0.42` in the `0.x` series, released in lockstep with the
171
171
  other four distributions; APIs may change between minor versions until
172
- 1.0 — pin an exact version (`quadkit-cli==0.0.3`) or a tight range
173
- (`>=0.0.3,<0.1.0`). Full policy:
172
+ 1.0 — pin an exact version (`quadkit-cli==0.0.42`) or a tight range
173
+ (`>=0.0.42,<0.1.0`). Full policy:
174
174
  [stability and compatibility](https://dbtinoy-.github.io/quadkit/reference/stability/).
175
175
 
176
176
  ## Links
@@ -111,10 +111,10 @@ review release notes on upgrade. Report vulnerabilities privately per
111
111
 
112
112
  ## Stability
113
113
 
114
- Version `0.0.3` in the `0.x` series, released in lockstep with the
114
+ Version `0.0.42` in the `0.x` series, released in lockstep with the
115
115
  other four distributions; APIs may change between minor versions until
116
- 1.0 — pin an exact version (`quadkit-cli==0.0.3`) or a tight range
117
- (`>=0.0.3,<0.1.0`). Full policy:
116
+ 1.0 — pin an exact version (`quadkit-cli==0.0.42`) or a tight range
117
+ (`>=0.0.42,<0.1.0`). Full policy:
118
118
  [stability and compatibility](https://dbtinoy-.github.io/quadkit/reference/stability/).
119
119
 
120
120
  ## Links
@@ -155,7 +155,7 @@ sequenceDiagram
155
155
  FS-->>Impl: File written
156
156
  Impl-->>GA: GenerationResult
157
157
  GA-->>CA: GeneratorResult
158
- CA-->>Gen: Print "Created: src/providers/user_service_provider.py"
158
+ CA-->>Gen: Print "Created: src/di/user_service_provider.py"
159
159
  Gen-->>User: Success output
160
160
  ```
161
161
 
@@ -205,7 +205,7 @@ generators = [
205
205
  description="Generate a provider class",
206
206
  contributor="my_package",
207
207
  generator_path="my_package.generators:MyGenerator",
208
- default_output_dir="src/providers",
208
+ default_output_dir="src/di",
209
209
  ),
210
210
  ]
211
211
  ```
@@ -82,7 +82,7 @@ Database management commands.
82
82
  ```
83
83
  quadkit db init [--reset] [--seed]
84
84
  quadkit db migrate [--name <migration>] [--auto]
85
- quadkit db seed [--file seeds.py]
85
+ quadkit db seed [--file seeds.py] # src/<app>/shared/seeders, else ./seeds
86
86
  quadkit db shell
87
87
  ```
88
88
 
@@ -91,7 +91,7 @@ quadkit db shell
91
91
  Code generation commands.
92
92
 
93
93
  ```
94
- quadkit gen provider <name> [--output src/providers/]
94
+ quadkit gen provider <name> [--output src/di/]
95
95
  quadkit gen module <name> [--output src/modules/]
96
96
  quadkit gen migration [--auto-detect]
97
97
  ```
@@ -52,7 +52,9 @@ my-platform/
52
52
  ├── README.md
53
53
  ├── .env.example
54
54
  ├── migrations/versions/ # quadkit gen migration
55
- ├── seeds/ # quadkit gen seeder
55
+ ├── seeds/ # hand-written seeders; `quadkit db seed`
56
+ │ # falls back to this when `shared/seeders`
57
+ │ # is absent
56
58
  ├── src/
57
59
  │ └── my_platform/
58
60
  │ ├── __init__.py
@@ -65,6 +67,8 @@ my-platform/
65
67
  │ ├── infrastructure/ # framework wiring: db, cache, events, monitoring
66
68
  │ │ └── __init__.py # infrastructure_modules()
67
69
  │ ├── shared/ # cross-cutting components (see §3)
70
+ │ │ ├── di/ # quadkit gen provider
71
+ │ │ ├── seeders/ # quadkit gen seeder
68
72
  │ │ ├── errors/
69
73
  │ │ ├── filters/
70
74
  │ │ ├── middleware/
@@ -76,7 +80,6 @@ my-platform/
76
80
  │ │ ├── features/
77
81
  │ │ ├── search/
78
82
  │ │ ├── storage/backends/
79
- │ │ ├── providers/
80
83
  │ │ ├── schema/dataloaders/
81
84
  │ │ ├── vector/collections/
82
85
  │ │ └── mcp/
@@ -108,18 +111,28 @@ empty `MODULES`, and `quadkit new module <name>` fills it in.
108
111
 
109
112
  **Cross-cutting** (`src/<app>/shared/<component>/`, module ignored):
110
113
 
111
- `audit`, `errors`, `features`, `filters`, `health`, `interceptors`, `mcp`,
112
- `metrics`, `middleware`, `providers`, `schema`, `schema/dataloaders`,
113
- `search`, `storage/backends`, `tenancy`, `vector/collections`
114
+ `audit`, `auth`, `contracts`, `di`, `errors`, `features`, `filters`, `health`,
115
+ `interceptors`, `mcp`, `metrics`, `middleware`, `providers`, `schema`,
116
+ `schema/dataloaders`, `search`, `seeders`, `storage/backends`, `tenancy`,
117
+ `vector/collections`
118
+
119
+ `providers` is retained as a *resolution* row only: no generator writes there
120
+ (it was superseded by `di`, which is where `quadkit gen provider` now defaults),
121
+ and the scaffold does not pre-create it — a project generated into
122
+ `shared/providers/` before that change still resolves. `auth` and `contracts`
123
+ likewise carry no generator yet; the rows are the map's answer for where those
124
+ packages live.
114
125
 
115
126
  **Module-local** (`src/<app>/<component>/` → `src/<app>/modules/<slug>/<component>/`):
116
127
 
117
128
  `admin/actions`, `admin/resources`, `clients`, `commands`, `consumers`,
118
129
  `controllers`, `domains`, `events`, `handlers`, `notifications`, `pipelines`,
119
130
  `policies`, `projections`, `queries`, `repositories`, `sagas`, `services`,
120
- `tasks`, `webhooks`, `websocket`, `workflows`
131
+ `tasks`, `validators`, `webhooks`, `websocket`, `workflows`
121
132
 
122
- **Project root, never moved**: `migrations/versions`, `seeds`.
133
+ **Project root, never moved**: `migrations/versions`, `seeds` (a hand-written
134
+ collection — generated seeders go to the shared `seeders` package so they are
135
+ importable, and `quadkit db seed` runs whichever exists).
123
136
  `tests/unit` is the exception that follows its node: with `--module auth` a
124
137
  generated test lands in `src/<app>/modules/auth/tests/`.
125
138
 
@@ -138,7 +151,7 @@ scoped, while `src/errors` always means `src/<app>/shared/errors`.
138
151
  | domain | `src/domains` |
139
152
  | service | `src/services` |
140
153
  | repository, cache_repo, document_repo | `src/repositories` |
141
- | provider | `src/providers` |
154
+ | provider | `src/di` |
142
155
  | query | `src/queries` |
143
156
  | error | `src/errors` |
144
157
  | event / event_handler | `src/events` / `src/handlers` |
@@ -170,7 +183,8 @@ scoped, while `src/errors` always means `src/<app>/shared/errors`.
170
183
  | graphql / dataloader | `src/schema` / `src/schema/dataloaders` |
171
184
  | vector_collection | `src/vector/collections` |
172
185
  | (special) resource | `src` — writes `src/<app>/<name>_resource.py`, or into the module |
173
- | migration / seeder | `migrations/versions` / `seeds` |
186
+ | migration | `migrations/versions` |
187
+ | seeder | `src/seeders` (shared; `--output seeds` still resolves) |
174
188
  | test | `tests/unit` |
175
189
 
176
190
  Two default dirs were renamed because they shadowed real modules on
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "quadkit-cli"
9
- version = "0.0.4"
9
+ version = "0.0.42"
10
10
  description = "Command-line interface for Quadkit Framework - Project scaffolding, code generation, and development tools"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.11"
@@ -78,6 +78,9 @@ def show(
78
78
  config_path = find_config()
79
79
  config_data = await load_config_yaml_async(config_path)
80
80
  except (ConfigNotFoundError, FileNotFoundError):
81
+ # Both are reachable here: find_config() raises the former, and
82
+ # the try also wraps the read, which can lose the file to a
83
+ # race after the walk.
81
84
  out.error(
82
85
  "No application.yaml found in current directory or any parent.",
83
86
  hint="Run `quadkit init` to create one.",
@@ -342,7 +345,10 @@ def env_cmd(
342
345
  async def _run() -> None:
343
346
  try:
344
347
  config_path = find_config()
345
- except (ConfigNotFoundError, FileNotFoundError):
348
+ except ConfigNotFoundError:
349
+ # find_config() raises ConfigNotFoundError (a CliError), never
350
+ # FileNotFoundError: it walks candidates with ``.is_file()``. The
351
+ # old arm therefore never ran, and this message never printed.
346
352
  out.error("No application.yaml found")
347
353
  raise typer.Exit(1) from None
348
354
 
@@ -458,7 +464,10 @@ def env_example(
458
464
  async def _run() -> None:
459
465
  try:
460
466
  config_path = find_config()
461
- except (ConfigNotFoundError, FileNotFoundError):
467
+ except ConfigNotFoundError:
468
+ # find_config() raises ConfigNotFoundError (a CliError), never
469
+ # FileNotFoundError: it walks candidates with ``.is_file()``. The
470
+ # old arm therefore never ran, and this message never printed.
462
471
  out.error("No application.yaml found")
463
472
  raise typer.Exit(1) from None
464
473
 
@@ -6,11 +6,13 @@ import asyncio
6
6
  import importlib
7
7
  import importlib.util
8
8
  from pathlib import Path
9
+ import sys
9
10
  from typing import Annotated
10
11
 
11
12
  import typer
12
13
 
13
14
  from quadkit.cli.commands import db_bootstrap
15
+ from quadkit.cli.layout import read_project_layout, resolve_output_dir
14
16
  from quadkit.cli.output import OutputManager
15
17
  from quadkit.cli.runtime import handle_errors
16
18
  from quadkit.contracts.cli.contributions import (
@@ -21,6 +23,106 @@ from quadkit.contracts.cli.contributions import (
21
23
  from quadkit.contracts.data.sql.database import DatabaseProviderProtocol
22
24
 
23
25
 
26
+ def _seeder_source() -> tuple[Path, str | None]:
27
+ """Where this project's seeders are, and how to import them.
28
+
29
+ Resolution order:
30
+
31
+ 1. the canonical ``seeders`` component — ``src/<app>/shared/seeders``,
32
+ where ``quadkit gen seeder`` writes and where a generated application
33
+ keeps the seeders its persistence provider boots;
34
+ 2. the project-root ``seeds/`` directory — what a project generated before
35
+ the ``seeders`` row landed has, and what a hand-written collection of
36
+ seeders looks like. Those files sit outside any package, so they stay
37
+ path-loaded, exactly as this command always loaded them.
38
+
39
+ Returns:
40
+ ``(directory, module_prefix)``: the directory to read, and the dotted
41
+ package its modules belong to (``my_app.shared.seeders``) when the
42
+ directory lives inside ``src/`` — ``None`` when it does not, which is
43
+ the signal to load files by path instead.
44
+ """
45
+ layout = read_project_layout()
46
+ canonical = Path(resolve_output_dir("src/seeders", app_package=layout.app_package))
47
+ if canonical.is_dir():
48
+ return canonical, _module_prefix(canonical)
49
+ return Path("seeds"), None
50
+
51
+
52
+ def _module_prefix(directory: Path) -> str | None:
53
+ """Dotted package name for *directory*, when it is inside ``src/``."""
54
+ parts = directory.parts
55
+ if "src" not in parts:
56
+ return None
57
+ after_src = parts[parts.index("src") + 1 :]
58
+ return ".".join(after_src) if after_src else None
59
+
60
+
61
+ def _make_importable(directory: Path) -> None:
62
+ """Put the package root of *directory* on ``sys.path``.
63
+
64
+ Seeders import the application's own packages (``from my_app.shared...``),
65
+ and the CLI is not run with the project's ``src`` on the path the way the
66
+ project's own entry point is.
67
+ """
68
+ parts = directory.parts
69
+ if "src" not in parts:
70
+ return
71
+ root = Path(*parts[: parts.index("src") + 1])
72
+ resolved = str(root.resolve())
73
+ if resolved not in sys.path:
74
+ sys.path.insert(0, resolved)
75
+
76
+
77
+ def _seed_files(directory: Path) -> list[Path]:
78
+ """Seeders to run, in filename order.
79
+
80
+ ``__init__.py`` is skipped: seeders below ``src/`` live in a package, and
81
+ the package marker is not a seeder -- running it would report "No 'run'
82
+ function found" for a file the user never wrote.
83
+ """
84
+ return [
85
+ path for path in sorted(directory.glob("*.py")) if path.name != "__init__.py"
86
+ ]
87
+
88
+
89
+ async def _run_seeder(
90
+ out: OutputManager,
91
+ provider: DatabaseProviderProtocol,
92
+ seed_file: Path,
93
+ module_prefix: str | None,
94
+ ) -> None:
95
+ """Execute one seeder file.
96
+
97
+ A seeder inside the application package is *imported*: it is an ordinary
98
+ module there, and importing is what the generated application itself does
99
+ when it boots its seeders. Anything else is loaded from its path, which is
100
+ the only thing that works for a file outside ``src/``.
101
+ """
102
+ out.info(f"Running seeder: {seed_file}")
103
+
104
+ if module_prefix is not None:
105
+ _make_importable(seed_file)
106
+ module = importlib.import_module(f"{module_prefix}.{seed_file.stem}")
107
+ else:
108
+ spec = importlib.util.spec_from_file_location("seed_module", seed_file)
109
+ if spec is None or spec.loader is None:
110
+ out.warning(f"Could not load seeder: {seed_file}")
111
+ return
112
+ module = importlib.util.module_from_spec(spec)
113
+ spec.loader.exec_module(module)
114
+
115
+ if not hasattr(module, "run"):
116
+ out.warning(f"No 'run' function found in {seed_file}")
117
+ return
118
+
119
+ if asyncio.iscoroutinefunction(module.run):
120
+ await module.run(provider)
121
+ else:
122
+ module.run(provider)
123
+ out.success(f"Seeder {seed_file} completed.")
124
+
125
+
24
126
  @handle_errors
25
127
  def seed(
26
128
  file: str | None = typer.Argument(None, help="Specific seed file to run"),
@@ -33,33 +135,26 @@ def seed(
33
135
  manager = await db_bootstrap.get_migration_manager()
34
136
  await manager.provider.boot()
35
137
  try:
36
- seeds_dir = Path("seeds")
138
+ seeds_dir, module_prefix = _seeder_source()
37
139
  if not seeds_dir.exists() and not file:
38
140
  out.warning("No seeds directory found.")
39
141
  return
40
142
 
41
- files_to_run = []
143
+ # Annotated: the first branch appends ``(path, None)`` and the
144
+ # second rebuilds with a module prefix, so inference from the
145
+ # empty list would pin the tuple's second element to ``None``.
146
+ files_to_run: list[tuple[Path, str | None]] = []
42
147
  if file:
43
- files_to_run.append(Path(file))
148
+ # An explicit file is run as given, by path: the user named a
149
+ # file, not a module.
150
+ files_to_run.append((Path(file), None))
44
151
  else:
45
- files_to_run = sorted(seeds_dir.glob("*.py"))
152
+ files_to_run = [
153
+ (seed_file, module_prefix) for seed_file in _seed_files(seeds_dir)
154
+ ]
46
155
 
47
- for seed_file in files_to_run:
48
- out.info(f"Running seeder: {seed_file}")
49
-
50
- spec = importlib.util.spec_from_file_location("seed_module", seed_file)
51
- if spec and spec.loader:
52
- module = importlib.util.module_from_spec(spec)
53
- spec.loader.exec_module(module)
54
-
55
- if hasattr(module, "run"):
56
- if asyncio.iscoroutinefunction(module.run):
57
- await module.run(manager.provider)
58
- else:
59
- module.run(manager.provider)
60
- out.success(f"Seeder {seed_file} completed.")
61
- else:
62
- out.warning(f"No 'run' function found in {seed_file}")
156
+ for seed_file, prefix in files_to_run:
157
+ await _run_seeder(out, manager.provider, seed_file, prefix)
63
158
 
64
159
  except Exception as e: # noqa: BLE001 — any seeder failure is a CLI error
65
160
  out.error(f"Seeder failed: {e}")
@@ -121,25 +216,12 @@ def reset(
121
216
  out.info("No pending migrations.")
122
217
 
123
218
  if seed:
124
- seeds_dir = Path("seeds")
219
+ seeds_dir, module_prefix = _seeder_source()
125
220
  if seeds_dir.exists():
126
- files_to_run = sorted(seeds_dir.glob("*.py"))
127
- for seed_file in files_to_run:
128
- out.info(f"Running seeder: {seed_file}")
129
-
130
- spec = importlib.util.spec_from_file_location(
131
- "seed_module",
132
- seed_file,
221
+ for seed_file in _seed_files(seeds_dir):
222
+ await _run_seeder(
223
+ out, manager.provider, seed_file, module_prefix
133
224
  )
134
- if spec and spec.loader:
135
- module = importlib.util.module_from_spec(spec)
136
- spec.loader.exec_module(module)
137
- if hasattr(module, "run"):
138
- if asyncio.iscoroutinefunction(module.run):
139
- await module.run(manager.provider)
140
- else:
141
- module.run(manager.provider)
142
- out.success(f"Seeder {seed_file} completed.")
143
225
  else:
144
226
  out.warning("No seeds directory found, skipping seed step.")
145
227
 
@@ -32,7 +32,7 @@ def _valid_name(value: str, kind: str) -> str:
32
32
  def main(
33
33
  name: str = typer.Argument(..., help="Name of the project"),
34
34
  template: str = typer.Option(
35
- "web-api",
35
+ "api",
36
36
  "--template",
37
37
  "-t",
38
38
  help=(
@@ -58,7 +58,7 @@ def main(
58
58
 
59
59
  selected_template = typer.prompt(
60
60
  "Select a template",
61
- default="web-api",
61
+ default="api",
62
62
  type=click.Choice(template_names()),
63
63
  )
64
64
 
@@ -13,7 +13,7 @@ _GENERATOR_SPECS: tuple[tuple[str, str, str, str], ...] = (
13
13
  "provider",
14
14
  "Generate provider",
15
15
  "quadkit.cli.generators.provider:ProviderGenerator",
16
- "src/providers",
16
+ "src/di",
17
17
  ),
18
18
  (
19
19
  "test",
@@ -20,9 +20,9 @@ class ProviderGenerator(GeneratorBase):
20
20
 
21
21
  name = "provider"
22
22
  description = "Generate provider"
23
- default_output_dir = "src/providers"
23
+ default_output_dir = "src/di"
24
24
 
25
- def __init__(self, output_dir: str | Path = "src/providers") -> None:
25
+ def __init__(self, output_dir: str | Path = "src/di") -> None:
26
26
  super().__init__(output_dir=output_dir)
27
27
 
28
28
  def generate(
@@ -65,16 +65,43 @@ COMPONENTS: tuple[ComponentDir, ...] = (
65
65
  ComponentDir("admin/actions", "admin/actions", False, ("admin_action",)),
66
66
  ComponentDir("admin/resources", "admin/resources", False, ("admin_resource",)),
67
67
  ComponentDir("audit", "audit", True, ("audited",)),
68
+ # ``auth``, ``contracts`` and ``validators`` carry no generators on
69
+ # purpose: no registered generator writes there yet (the CLI ships
70
+ # ``provider`` and ``test``; subsystems contribute the rest), and a
71
+ # ``generators`` entry is the map's authorisation for a generator's
72
+ # declared output directory, so it should name a generator that exists.
73
+ # The rows are the map's answer for *placement and scope*, which is what
74
+ # the builder needed to delete its own copy of them
75
+ # (docs/upstream/2026-09-02-component-rows.md).
76
+ # API-key issuance and verification. Cross-cutting by definition: one
77
+ # key store, checked by every bounded context.
78
+ ComponentDir("auth", "auth", True, ()),
68
79
  ComponentDir("clients", "clients", False, ("api_client",)),
69
80
  ComponentDir("commands", "commands", False, ("command",)),
70
81
  ComponentDir("consumers", "consumers", False, ("message_consumer",)),
82
+ # Request/response schemas shared across controllers. They are the
83
+ # interface between a context and its callers, so they cannot be
84
+ # module-local without every consumer reaching across a boundary.
85
+ ComponentDir("contracts", "contracts", True, ()),
71
86
  ComponentDir("controllers", "controllers", False, ("controller",)),
87
+ # Cross-cutting DI providers. Every quadkit subsystem keeps its own
88
+ # provider at ``<subsystem>/di/provider.py`` (30 of them, no
89
+ # exceptions), so a generated provider reads like the framework it
90
+ # is built on. The ``providers`` row below is kept for projects
91
+ # that already generated into it.
92
+ ComponentDir("di", "di", True, ("provider",)),
72
93
  ComponentDir("domains", "domains", False, ("domain",)),
73
94
  ComponentDir("errors", "errors", True, ("error",)),
74
95
  ComponentDir("events", "events", False, ("event",)),
75
96
  ComponentDir("features", "features", True, ("feature_flag",)),
76
97
  ComponentDir("filters", "filters", True, ("exception_filter", "filter")),
77
- ComponentDir("guards", "guards", False, ("auth_guard", "guard")),
98
+ # Cross-cutting, like ``filters`` above: ``@require_auth()`` decorates
99
+ # controllers in every bounded context, and the palette already scopes
100
+ # ``auth``/``role`` as shared kinds. While this row said module-local,
101
+ # the two answers met at generation time and a modular app containing
102
+ # an Auth Guard could not be generated at all (playground TASK-6 /
103
+ # MODULAR-3).
104
+ ComponentDir("guards", "guards", True, ("auth_guard", "guard")),
78
105
  ComponentDir("handlers", "handlers", False, ("event_handler",)),
79
106
  ComponentDir("health", "health", True, ("health",)),
80
107
  ComponentDir("interceptors", "interceptors", True, ("interceptor",)),
@@ -85,7 +112,9 @@ COMPONENTS: tuple[ComponentDir, ...] = (
85
112
  ComponentDir("pipelines", "pipelines", False, ("pipeline",)),
86
113
  ComponentDir("policies", "policies", False, ("auth_policy",)),
87
114
  ComponentDir("projections", "projections", False, ("projection",)),
88
- ComponentDir("providers", "providers", True, ("provider",)),
115
+ # Superseded by the ``di`` row above; kept (with no generators) so a
116
+ # project that already has ``shared/providers/`` keeps resolving.
117
+ ComponentDir("providers", "providers", True, ()),
89
118
  ComponentDir("queries", "queries", False, ("query",)),
90
119
  ComponentDir(
91
120
  "repositories",
@@ -97,10 +126,20 @@ COMPONENTS: tuple[ComponentDir, ...] = (
97
126
  ComponentDir("schema", "schema", True, ("graphql",)),
98
127
  ComponentDir("schema/dataloaders", "schema/dataloaders", True, ("dataloader",)),
99
128
  ComponentDir("search", "search", True, ("search_index",)),
129
+ # Generated seeders live inside the application package so they are
130
+ # importable: a project boots them from its persistence provider, and
131
+ # `quadkit db seed` imports the same modules. ``seeds`` (project root)
132
+ # stays resolvable in ROOT_DIRS below, because that is where every
133
+ # project generated before 2026-09-23 kept them and where the command
134
+ # still looks second.
135
+ ComponentDir("seeders", "seeders", True, ("seeder",)),
100
136
  ComponentDir("services", "services", False, ("service",)),
101
137
  ComponentDir("storage/backends", "storage/backends", True, ("storage_driver",)),
102
138
  ComponentDir("tasks", "tasks", False, ("task",)),
103
139
  ComponentDir("tenancy", "tenancy", True, ("tenant_resolver",)),
140
+ # Per-entity rules. They know an entity's fields, so they belong
141
+ # wherever that entity belongs: module-local.
142
+ ComponentDir("validators", "validators", False, ()),
104
143
  ComponentDir(
105
144
  "vector/collections", "vector/collections", True, ("vector_collection",)
106
145
  ),
@@ -112,6 +151,10 @@ COMPONENTS: tuple[ComponentDir, ...] = (
112
151
  # Root-level output directories of the generator SDK (no ``src/`` prefix).
113
152
  ROOT_DIRS: tuple[tuple[str, tuple[str, ...]], ...] = (
114
153
  ("migrations/versions", ("migration",)),
154
+ # ``seeds`` is legacy-but-supported: the ``seeders`` component above is
155
+ # where the generator writes now, while this row keeps ``quadkit gen
156
+ # seeder --output seeds`` and projects generated before 2026-09-23
157
+ # resolvable. ``quadkit db seed`` looks here second.
115
158
  ("seeds", ("seeder",)),
116
159
  ("tests/unit", ("test",)),
117
160
  )
@@ -13,7 +13,16 @@ from typing import ClassVar
13
13
 
14
14
  @dataclass
15
15
  class EnvironmentConfig:
16
- """Configuration for an environment."""
16
+ """Configuration for an environment.
17
+
18
+ Attributes:
19
+ name: Environment name.
20
+ description: Human-readable description.
21
+ variables: Additional environment variables to apply when switching.
22
+ database_url: Database URL for this environment.
23
+ debug: Whether debug mode is enabled.
24
+ log_level: Logging level for this environment.
25
+ """
17
26
 
18
27
  name: str
19
28
  description: str = ""
@@ -22,6 +31,39 @@ class EnvironmentConfig:
22
31
  debug: bool = False
23
32
  log_level: str = "INFO"
24
33
 
34
+ def apply(self) -> None:
35
+ """Apply this configuration to the process environment.
36
+
37
+ Writes ``variables`` into ``os.environ``, and ensures
38
+ ``DATABASE_URL`` and ``LOG_LEVEL`` reflect the typed fields
39
+ when they are set. This is the consumption site for
40
+ ``variables``, ``database_url`` and ``log_level`` — without it
41
+ the fields would be dead configuration.
42
+ """
43
+ if self.variables:
44
+ for key, value in self.variables.items():
45
+ if key:
46
+ os.environ[key] = value
47
+ if self.database_url:
48
+ os.environ["DATABASE_URL"] = self.database_url
49
+ if self.log_level:
50
+ os.environ["LOG_LEVEL"] = self.log_level
51
+ os.environ["QK_LOG_LEVEL"] = self.log_level
52
+
53
+ def to_env_dict(self) -> dict[str, str]:
54
+ """Return the environment variables this config would set.
55
+
56
+ Returns:
57
+ Dictionary of environment variables including ``variables``,
58
+ ``DATABASE_URL`` and ``LOG_LEVEL`` when present.
59
+ """
60
+ result: dict[str, str] = dict(self.variables)
61
+ if self.database_url:
62
+ result["DATABASE_URL"] = self.database_url
63
+ if self.log_level:
64
+ result["LOG_LEVEL"] = self.log_level
65
+ return result
66
+
25
67
 
26
68
  class Environment(abc.ABC):
27
69
  """Abstract base class for environments."""
@@ -202,7 +244,13 @@ class EnvironmentManager:
202
244
  self.registry = EnvironmentRegistry.with_defaults()
203
245
 
204
246
  def switch(self, name: str) -> bool:
205
- """Switch to a different environment."""
247
+ """Switch to a different environment and apply its variables.
248
+
249
+ Validation is performed before the switch. When valid, the
250
+ environment is marked as current and its ``EnvironmentConfig``
251
+ is applied to ``os.environ`` so ``variables``, ``database_url``
252
+ and ``log_level`` take effect.
253
+ """
206
254
  env = self.registry.get(name)
207
255
  if not env:
208
256
  return False
@@ -212,6 +260,8 @@ class EnvironmentManager:
212
260
  return False
213
261
 
214
262
  self.registry.set_current(name)
263
+ config = env.get_config()
264
+ config.apply()
215
265
  return True
216
266
 
217
267
  def validate_current(self) -> tuple[bool, str]: