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.
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/.gitignore +32 -1
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/PKG-INFO +4 -4
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/README.md +3 -3
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/ARCHITECTURE.md +2 -2
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/COMMANDS.md +2 -2
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/PROJECT_LAYOUT.md +23 -9
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/pyproject.toml +1 -1
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/config.py +11 -2
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/db_commands_data.py +119 -37
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/new.py +2 -2
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/core.py +1 -1
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/generators/provider.py +2 -2
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/layout.py +45 -2
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/environment.py +52 -2
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/presets.py +2 -2
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/template.py +5 -3
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/module_files.py +18 -1
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/specs.py +6 -4
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_config.py +52 -9
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_template.py +8 -1
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_db.py +50 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_gen_provider.py +2 -2
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_generator_registry.py +1 -1
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_layout.py +2 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_package_names.py +4 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_project_layout.py +1 -1
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/LICENSE +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/NOTICE +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/CONFIGURATION.md +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/GUIDE.md +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/HOWTOS.md +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/PUBLIC_PACKAGE_CLI_MATRIX.md +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/QUICKSTART.md +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/docs/TROUBLESHOOTING.md +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/example.yaml +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/assembly/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/assembly/assembler.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/add.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/config_utils.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/contrib.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/db.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/db_bootstrap.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/db_commands_migrations.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/db_commands_ops.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/dev.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/events.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/gen.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/init.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/inspect.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/meta.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/project.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/py.typed +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/run.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/shell.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/commands/system.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/config.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/constants.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/base.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/discovery.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/registry.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/contributors/runtime.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/decorators.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/di/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/di/provider.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/di/sub_providers/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/di/sub_providers/contributor.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/events.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/exceptions.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/generators/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/generators/base.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/generators/field_parser.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/generators/test.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/hooks.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/lib/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/lib/config_loader.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/lib/entry_point.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/lib/naming.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/lib/templates.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/module.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/output/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/output/manager.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/package_names.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/protocols.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/py.typed +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/_lazy_exports.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/backend_base.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/command.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/completion.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/config.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/database.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/database_backends.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/formatter.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/generator.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/base.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/deps.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/project.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/providers.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/python_env.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/tools.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/health_checks/vcs_docker.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/hook.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/inspect.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/migration.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/provider.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/provider_catalog.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/provider_registry.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/secrets.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/serializer.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/server.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/task.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/telemetry.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/version.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/registry/watcher.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/runtime/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/runtime/context.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/runtime/error_handler.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/runtime/main.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/app_files.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/render.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold/support_files.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/scaffold_package.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/templates/provider.py.jinja2 +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/templates/py.typed +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/templates/test_unit.py.jinja2 +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/cli/types.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/src/quadkit/py.typed +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/conftest.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/helpers/runner.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/integration/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/integration/test_cli_lifecycle.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/test_package_structure_p1.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/_test_cli_contribution_system_support.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_config_happy_paths.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_contrib.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_events_cmd.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_inspect.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_meta.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_run.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_shell.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/commands/test_system.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/di/test_config_alignment.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/lib/test_templates.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/__init__.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_completion.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_database.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_environment.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_formatter.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_health.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_hook.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_inspect_registry.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_migration.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_presets.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_secrets.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_serializer.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_task.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_telemetry.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_version.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/registry/test_watcher.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_add.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_base_contributor.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_constants.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_context.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_contribution_system.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_contribution_system__testclicontributorsubprovider.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_errors.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_exceptions.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_import_isolation.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_module.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_cli_types.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_config_async.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_config_loader.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_config_save.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_config_validate.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_contributor_registry.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_contributor_runtime.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_core_contributor.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_decorators.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_dev_command.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_error_handler.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_events.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_exception_handling.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_field_parser.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_gen.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_gen_controller.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_generator_base.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_generator_option_help.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_generators_contract.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_health_checks.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_hooks.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_init_command.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_main.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_new.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_output.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_output_json.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_project.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_provider_registry.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_public_surface.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_round40_hardening.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_run_detection.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_scaffold_and_validators.py +0 -0
- {quadkit_cli-0.0.4 → quadkit_cli-0.0.42}/tests/unit/test_server_registry.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
173
|
-
(`>=0.0.
|
|
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.
|
|
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.
|
|
117
|
-
(`>=0.0.
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
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`, `
|
|
112
|
-
`metrics`, `middleware`, `providers`, `schema`,
|
|
113
|
-
`search`, `storage/backends`, `tenancy`,
|
|
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/
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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 =
|
|
219
|
+
seeds_dir, module_prefix = _seeder_source()
|
|
125
220
|
if seeds_dir.exists():
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
"
|
|
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="
|
|
61
|
+
default="api",
|
|
62
62
|
type=click.Choice(template_names()),
|
|
63
63
|
)
|
|
64
64
|
|
|
@@ -20,9 +20,9 @@ class ProviderGenerator(GeneratorBase):
|
|
|
20
20
|
|
|
21
21
|
name = "provider"
|
|
22
22
|
description = "Generate provider"
|
|
23
|
-
default_output_dir = "src/
|
|
23
|
+
default_output_dir = "src/di"
|
|
24
24
|
|
|
25
|
-
def __init__(self, output_dir: str | Path = "src/
|
|
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
|
-
|
|
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
|
-
|
|
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]:
|