vega-framework 0.1.14__tar.gz → 0.1.16__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.
- {vega_framework-0.1.14 → vega_framework-0.1.16}/PKG-INFO +43 -1
- {vega_framework-0.1.14 → vega_framework-0.1.16}/README.md +42 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/pyproject.toml +1 -1
- vega_framework-0.1.16/vega/cli/__init__.py +11 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/generate.py +138 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/migrate.py +3 -2
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/main.py +5 -2
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/__init__.py +4 -0
- vega_framework-0.1.16/vega/cli/templates/cli/command.py.j2 +40 -0
- vega_framework-0.1.16/vega/cli/templates/cli/command_simple.py.j2 +19 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/components.py +48 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/ARCHITECTURE.md.j2 +41 -1
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/README.md.j2 +22 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/config.py.j2 +2 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/main_fastapi.py.j2 +28 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/main_standard.py.j2 +25 -1
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/sqlalchemy/env.py.j2 +2 -2
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/utils/__init__.py +3 -0
- vega_framework-0.1.16/vega/cli/utils/async_support.py +55 -0
- vega_framework-0.1.14/vega/cli/__init__.py +0 -5
- {vega_framework-0.1.14 → vega_framework-0.1.16}/LICENSE +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/__init__.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/__init__.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/add.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/init.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/update.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/scaffolds/__init__.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/scaffolds/fastapi.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/scaffolds/sqlalchemy.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/entity.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/interactor.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/mediator.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/repository_interface.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/service_interface.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/infrastructure/model.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/infrastructure/repository_impl.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/infrastructure/service_impl.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/loader.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/.env.example +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/.gitignore +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/main.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/pyproject.toml.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/settings.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/sqlalchemy/alembic.ini.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/sqlalchemy/database_manager.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/sqlalchemy/script.py.mako +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/__init__.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/app.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/health_route.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/main.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/middleware.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/models_init.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/router.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/routes_init.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/user_models.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/users_route.py.j2 +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/utils/messages.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/utils/naming.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/utils/validators.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/di/__init__.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/di/container.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/di/decorators.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/di/errors.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/di/scope.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/patterns/__init__.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/patterns/interactor.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/patterns/mediator.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/patterns/repository.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/patterns/service.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/settings/__init__.py +0 -0
- {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/settings/base.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: vega-framework
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.16
|
4
4
|
Summary: Enterprise-ready Python framework that enforces Clean Architecture for building maintainable and scalable applications.
|
5
5
|
License: MIT
|
6
6
|
License-File: LICENSE
|
@@ -36,6 +36,7 @@ An enterprise-ready Python framework that enforces Clean Architecture for buildi
|
|
36
36
|
|
37
37
|
- ✅ **Automatic Dependency Injection** - Zero boilerplate, type-safe DI
|
38
38
|
- ✅ **Clean Architecture Patterns** - Interactor, Mediator, Repository, Service
|
39
|
+
- ✅ **Async/Await Support** - Full async support for CLI and web
|
39
40
|
- ✅ **Scope Management** - Singleton, Scoped, Transient lifetimes
|
40
41
|
- ✅ **Type-Safe** - Full type hints support
|
41
42
|
- ✅ **Framework-Agnostic** - Works with any domain (web, AI, IoT, fintech, etc.)
|
@@ -92,6 +93,8 @@ vega generate mediator <Name>
|
|
92
93
|
vega generate router <Name> # Requires FastAPI
|
93
94
|
vega generate middleware <Name> # Requires FastAPI
|
94
95
|
vega generate model <Name> # Requires SQLAlchemy
|
96
|
+
vega generate command <Name> # CLI command (async by default)
|
97
|
+
vega generate command <Name> --impl sync # Synchronous CLI command
|
95
98
|
```
|
96
99
|
|
97
100
|
### Add Features
|
@@ -136,6 +139,45 @@ vega doctor [--path .]
|
|
136
139
|
|
137
140
|
Validates project structure, DI configuration, and architecture compliance.
|
138
141
|
|
142
|
+
## Async CLI Commands
|
143
|
+
|
144
|
+
Vega provides seamless async/await support in CLI commands, allowing you to execute interactors directly.
|
145
|
+
|
146
|
+
### Generate a CLI Command
|
147
|
+
|
148
|
+
```bash
|
149
|
+
# Generate an async command (default)
|
150
|
+
vega generate command CreateUser
|
151
|
+
|
152
|
+
# Generate a synchronous command
|
153
|
+
vega generate command ListUsers --impl sync
|
154
|
+
```
|
155
|
+
|
156
|
+
The generator will prompt you for:
|
157
|
+
- Command description
|
158
|
+
- Options and arguments
|
159
|
+
- Whether it will use interactors
|
160
|
+
|
161
|
+
### Manual Command Example
|
162
|
+
|
163
|
+
```python
|
164
|
+
import click
|
165
|
+
from vega.cli.utils import async_command
|
166
|
+
|
167
|
+
@click.command()
|
168
|
+
@click.option('--name', required=True)
|
169
|
+
@async_command
|
170
|
+
async def create_user(name: str):
|
171
|
+
"""Create a user using an interactor"""
|
172
|
+
import config # Initialize DI container
|
173
|
+
from domain.interactors.create_user import CreateUser
|
174
|
+
|
175
|
+
user = await CreateUser(name=name)
|
176
|
+
click.echo(f"Created: {user.name}")
|
177
|
+
```
|
178
|
+
|
179
|
+
This enables the same async business logic to work in both CLI and web (FastAPI) contexts.
|
180
|
+
|
139
181
|
## Use Cases
|
140
182
|
|
141
183
|
Perfect for:
|
@@ -6,6 +6,7 @@ An enterprise-ready Python framework that enforces Clean Architecture for buildi
|
|
6
6
|
|
7
7
|
- ✅ **Automatic Dependency Injection** - Zero boilerplate, type-safe DI
|
8
8
|
- ✅ **Clean Architecture Patterns** - Interactor, Mediator, Repository, Service
|
9
|
+
- ✅ **Async/Await Support** - Full async support for CLI and web
|
9
10
|
- ✅ **Scope Management** - Singleton, Scoped, Transient lifetimes
|
10
11
|
- ✅ **Type-Safe** - Full type hints support
|
11
12
|
- ✅ **Framework-Agnostic** - Works with any domain (web, AI, IoT, fintech, etc.)
|
@@ -62,6 +63,8 @@ vega generate mediator <Name>
|
|
62
63
|
vega generate router <Name> # Requires FastAPI
|
63
64
|
vega generate middleware <Name> # Requires FastAPI
|
64
65
|
vega generate model <Name> # Requires SQLAlchemy
|
66
|
+
vega generate command <Name> # CLI command (async by default)
|
67
|
+
vega generate command <Name> --impl sync # Synchronous CLI command
|
65
68
|
```
|
66
69
|
|
67
70
|
### Add Features
|
@@ -106,6 +109,45 @@ vega doctor [--path .]
|
|
106
109
|
|
107
110
|
Validates project structure, DI configuration, and architecture compliance.
|
108
111
|
|
112
|
+
## Async CLI Commands
|
113
|
+
|
114
|
+
Vega provides seamless async/await support in CLI commands, allowing you to execute interactors directly.
|
115
|
+
|
116
|
+
### Generate a CLI Command
|
117
|
+
|
118
|
+
```bash
|
119
|
+
# Generate an async command (default)
|
120
|
+
vega generate command CreateUser
|
121
|
+
|
122
|
+
# Generate a synchronous command
|
123
|
+
vega generate command ListUsers --impl sync
|
124
|
+
```
|
125
|
+
|
126
|
+
The generator will prompt you for:
|
127
|
+
- Command description
|
128
|
+
- Options and arguments
|
129
|
+
- Whether it will use interactors
|
130
|
+
|
131
|
+
### Manual Command Example
|
132
|
+
|
133
|
+
```python
|
134
|
+
import click
|
135
|
+
from vega.cli.utils import async_command
|
136
|
+
|
137
|
+
@click.command()
|
138
|
+
@click.option('--name', required=True)
|
139
|
+
@async_command
|
140
|
+
async def create_user(name: str):
|
141
|
+
"""Create a user using an interactor"""
|
142
|
+
import config # Initialize DI container
|
143
|
+
from domain.interactors.create_user import CreateUser
|
144
|
+
|
145
|
+
user = await CreateUser(name=name)
|
146
|
+
click.echo(f"Created: {user.name}")
|
147
|
+
```
|
148
|
+
|
149
|
+
This enables the same async business logic to work in both CLI and web (FastAPI) contexts.
|
150
|
+
|
109
151
|
## Use Cases
|
110
152
|
|
111
153
|
Perfect for:
|
@@ -0,0 +1,11 @@
|
|
1
|
+
"""Vega Framework CLI tools"""
|
2
|
+
|
3
|
+
# Lazy import to avoid circular dependencies when importing utilities
|
4
|
+
def __getattr__(name: str):
|
5
|
+
if name == "cli":
|
6
|
+
from vega.cli.main import cli
|
7
|
+
return cli
|
8
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
9
|
+
|
10
|
+
|
11
|
+
__all__ = ["cli"]
|
@@ -14,6 +14,8 @@ from vega.cli.templates import (
|
|
14
14
|
render_fastapi_router,
|
15
15
|
render_fastapi_middleware,
|
16
16
|
render_sqlalchemy_model,
|
17
|
+
render_cli_command,
|
18
|
+
render_cli_command_simple,
|
17
19
|
)
|
18
20
|
from vega.cli.scaffolds import create_fastapi_scaffold
|
19
21
|
|
@@ -124,6 +126,8 @@ def generate_component(
|
|
124
126
|
_generate_middleware(project_root, project_name, class_name, file_name)
|
125
127
|
elif component_type == 'model':
|
126
128
|
_generate_sqlalchemy_model(project_root, project_name, class_name, file_name)
|
129
|
+
elif component_type == 'command':
|
130
|
+
_generate_command(project_root, project_name, name, implementation)
|
127
131
|
|
128
132
|
|
129
133
|
def _generate_entity(project_root: Path, project_name: str, class_name: str, file_name: str):
|
@@ -670,3 +674,137 @@ from infrastructure.models.{file_name} import {class_name}Model # noqa: F401
|
|
670
674
|
click.echo(click.style(f'''
|
671
675
|
from infrastructure.models.{file_name} import {class_name}Model # noqa: F401
|
672
676
|
''', fg='cyan'))
|
677
|
+
|
678
|
+
|
679
|
+
def _generate_command(project_root: Path, project_name: str, name: str, is_async: str | None = None) -> None:
|
680
|
+
"""Generate a CLI command"""
|
681
|
+
|
682
|
+
# Check if presentation/cli exists
|
683
|
+
cli_path = project_root / "presentation" / "cli"
|
684
|
+
if not cli_path.exists():
|
685
|
+
cli_path.mkdir(parents=True, exist_ok=True)
|
686
|
+
click.echo(f"+ Created {click.style(str(cli_path.relative_to(project_root)), fg='green')}")
|
687
|
+
|
688
|
+
# Create commands directory if it doesn't exist
|
689
|
+
commands_path = cli_path / "commands"
|
690
|
+
commands_path.mkdir(exist_ok=True)
|
691
|
+
|
692
|
+
# Check if __init__.py exists
|
693
|
+
init_file = commands_path / "__init__.py"
|
694
|
+
if not init_file.exists():
|
695
|
+
init_file.write_text('"""CLI Commands"""\n')
|
696
|
+
click.echo(f"+ Created {click.style(str(init_file.relative_to(project_root)), fg='green')}")
|
697
|
+
|
698
|
+
# Convert name to snake_case for command and file
|
699
|
+
command_name = to_snake_case(name).replace('_', '-')
|
700
|
+
file_name = to_snake_case(name)
|
701
|
+
|
702
|
+
# Generate command file
|
703
|
+
command_file = commands_path / f"{file_name}.py"
|
704
|
+
|
705
|
+
if command_file.exists():
|
706
|
+
click.echo(click.style(f"ERROR: Error: {command_file.relative_to(project_root)} already exists", fg='red'))
|
707
|
+
return
|
708
|
+
|
709
|
+
# Determine if async (default is async unless explicitly set to 'sync' or 'simple')
|
710
|
+
use_async = is_async not in ['sync', 'simple', 'false', 'no'] if is_async else True
|
711
|
+
|
712
|
+
# Prompt for command details
|
713
|
+
description = click.prompt("Command description", default=f"{name} command")
|
714
|
+
|
715
|
+
# Ask if user wants to add options/arguments
|
716
|
+
add_params = click.confirm("Add options or arguments?", default=False)
|
717
|
+
|
718
|
+
options = []
|
719
|
+
arguments = []
|
720
|
+
params_list = []
|
721
|
+
|
722
|
+
if add_params:
|
723
|
+
click.echo("\nAdd options (e.g., --name, --email). Press Enter when done.")
|
724
|
+
while True:
|
725
|
+
opt_name = click.prompt("Option name (without --)", default="", show_default=False)
|
726
|
+
if not opt_name:
|
727
|
+
break
|
728
|
+
opt_type = click.prompt("Type", default="str", type=click.Choice(['str', 'int', 'bool']))
|
729
|
+
opt_required = click.confirm("Required?", default=False)
|
730
|
+
opt_help = click.prompt("Help text", default=f"{opt_name.replace('-', ' ').replace('_', ' ')}")
|
731
|
+
|
732
|
+
params_list.append(opt_name.replace('-', '_'))
|
733
|
+
|
734
|
+
opt_params = f"help='{opt_help}'"
|
735
|
+
if opt_required:
|
736
|
+
opt_params += ", required=True"
|
737
|
+
if opt_type != 'str':
|
738
|
+
if opt_type == 'bool':
|
739
|
+
opt_params += ", is_flag=True"
|
740
|
+
else:
|
741
|
+
opt_params += f", type={opt_type}"
|
742
|
+
|
743
|
+
options.append({
|
744
|
+
"flag": f"--{opt_name}",
|
745
|
+
"params": opt_params
|
746
|
+
})
|
747
|
+
|
748
|
+
click.echo("\nAdd arguments (positional). Press Enter when done.")
|
749
|
+
while True:
|
750
|
+
arg_name = click.prompt("Argument name", default="", show_default=False)
|
751
|
+
if not arg_name:
|
752
|
+
break
|
753
|
+
arg_required = click.confirm("Required?", default=True)
|
754
|
+
|
755
|
+
params_list.append(arg_name)
|
756
|
+
|
757
|
+
arg_params = "" if arg_required else ", required=False"
|
758
|
+
arguments.append({
|
759
|
+
"name": arg_name,
|
760
|
+
"params": arg_params
|
761
|
+
})
|
762
|
+
|
763
|
+
params_signature = ", ".join(params_list) if params_list else ""
|
764
|
+
|
765
|
+
# Ask about interactor usage
|
766
|
+
with_interactor = False
|
767
|
+
interactor_name = ""
|
768
|
+
if use_async:
|
769
|
+
with_interactor = click.confirm("Will this command use an interactor?", default=True)
|
770
|
+
if with_interactor:
|
771
|
+
interactor_name = click.prompt("Interactor name", default=f"{to_pascal_case(name)}")
|
772
|
+
|
773
|
+
usage_example = f"python main.py {command_name}"
|
774
|
+
if params_list:
|
775
|
+
usage_example += " " + " ".join([f"--{p.replace('_', '-')}=value" if f"--{p.replace('_', '-')}" in str(options) else p for p in params_list])
|
776
|
+
|
777
|
+
# Generate content
|
778
|
+
if use_async:
|
779
|
+
content = render_cli_command(
|
780
|
+
command_name=file_name,
|
781
|
+
description=description,
|
782
|
+
options=options,
|
783
|
+
arguments=arguments,
|
784
|
+
params_signature=params_signature,
|
785
|
+
params_list=params_list,
|
786
|
+
with_interactor=with_interactor,
|
787
|
+
usage_example=usage_example,
|
788
|
+
interactor_name=interactor_name,
|
789
|
+
)
|
790
|
+
else:
|
791
|
+
content = render_cli_command_simple(
|
792
|
+
command_name=file_name,
|
793
|
+
description=description,
|
794
|
+
options=options,
|
795
|
+
arguments=arguments,
|
796
|
+
params_signature=params_signature,
|
797
|
+
params_list=params_list,
|
798
|
+
)
|
799
|
+
|
800
|
+
command_file.write_text(content)
|
801
|
+
click.echo(f"+ Created {click.style(str(command_file.relative_to(project_root)), fg='green')}")
|
802
|
+
|
803
|
+
# Instructions for next steps
|
804
|
+
click.echo(f"\nNext steps:")
|
805
|
+
click.echo(f" 1. Implement your command logic in {command_file.relative_to(project_root)}")
|
806
|
+
click.echo(f" 2. Import and register in main.py:")
|
807
|
+
click.echo(click.style(f" from presentation.cli.commands.{file_name} import {file_name}", fg='cyan'))
|
808
|
+
click.echo(click.style(f" cli.add_command({file_name})", fg='cyan'))
|
809
|
+
if with_interactor:
|
810
|
+
click.echo(f" 3. Create interactor: vega generate interactor {interactor_name}")
|
@@ -107,9 +107,9 @@ def history():
|
|
107
107
|
@migrate.command()
|
108
108
|
def init():
|
109
109
|
"""Initialize database with current schema (create tables)"""
|
110
|
-
import asyncio
|
111
110
|
from pathlib import Path
|
112
111
|
import sys
|
112
|
+
from vega.cli.utils import async_command
|
113
113
|
|
114
114
|
# Add project root to path to allow imports
|
115
115
|
project_root = Path.cwd()
|
@@ -123,13 +123,14 @@ def init():
|
|
123
123
|
click.echo("Make sure you have SQLAlchemy configured in your project")
|
124
124
|
sys.exit(1)
|
125
125
|
|
126
|
+
@async_command
|
126
127
|
async def _init():
|
127
128
|
click.echo("Creating database tables...")
|
128
129
|
await db_manager.create_tables()
|
129
130
|
click.secho("Database tables created successfully", fg='green')
|
130
131
|
|
131
132
|
try:
|
132
|
-
|
133
|
+
_init()
|
133
134
|
except Exception as e:
|
134
135
|
click.secho(f"Failed to initialize database: {e}", fg='red')
|
135
136
|
sys.exit(1)
|
@@ -56,11 +56,11 @@ def init(project_name, template, path):
|
|
56
56
|
|
57
57
|
@cli.command()
|
58
58
|
@click.argument('component_type', type=click.Choice([
|
59
|
-
'entity', 'repository', 'repo', 'service', 'interactor', 'mediator', 'router', 'middleware', 'model'
|
59
|
+
'entity', 'repository', 'repo', 'service', 'interactor', 'mediator', 'router', 'middleware', 'model', 'command'
|
60
60
|
]))
|
61
61
|
@click.argument('name')
|
62
62
|
@click.option('--path', default='.', help='Project root path')
|
63
|
-
@click.option('--impl', default=None, help='Generate infrastructure implementation for repository/service (e.g., memory, sql)')
|
63
|
+
@click.option('--impl', default=None, help='Generate infrastructure implementation for repository/service (e.g., memory, sql) or command type (async, sync)')
|
64
64
|
def generate(component_type, name, path, impl):
|
65
65
|
"""
|
66
66
|
Generate a component in your Vega project.
|
@@ -75,6 +75,7 @@ def generate(component_type, name, path, impl):
|
|
75
75
|
router - FastAPI router (requires web module)
|
76
76
|
middleware - FastAPI middleware (requires web module)
|
77
77
|
model - SQLAlchemy model (requires sqlalchemy module)
|
78
|
+
command - CLI command (async by default)
|
78
79
|
|
79
80
|
Examples:
|
80
81
|
vega generate entity Product
|
@@ -85,6 +86,8 @@ def generate(component_type, name, path, impl):
|
|
85
86
|
vega generate router Product
|
86
87
|
vega generate middleware Logging
|
87
88
|
vega generate model User
|
89
|
+
vega generate command CreateUser
|
90
|
+
vega generate command ListUsers --impl sync
|
88
91
|
"""
|
89
92
|
# Normalize 'repo' to 'repository'
|
90
93
|
if component_type == 'repo':
|
@@ -24,6 +24,8 @@ from .components import (
|
|
24
24
|
render_alembic_env,
|
25
25
|
render_alembic_script_mako,
|
26
26
|
render_sqlalchemy_model,
|
27
|
+
render_cli_command,
|
28
|
+
render_cli_command_simple,
|
27
29
|
)
|
28
30
|
from .loader import render_template
|
29
31
|
|
@@ -53,5 +55,7 @@ __all__ = [
|
|
53
55
|
"render_alembic_env",
|
54
56
|
"render_alembic_script_mako",
|
55
57
|
"render_sqlalchemy_model",
|
58
|
+
"render_cli_command",
|
59
|
+
"render_cli_command_simple",
|
56
60
|
"render_template",
|
57
61
|
]
|
@@ -0,0 +1,40 @@
|
|
1
|
+
"""{{ description }}"""
|
2
|
+
import click
|
3
|
+
from vega.cli.utils import async_command
|
4
|
+
import config # noqa: F401 - Initialize DI container
|
5
|
+
|
6
|
+
{% if with_interactor -%}
|
7
|
+
# Import your interactors here
|
8
|
+
# from domain.interactors.{{ interactor_name }} import {{ interactor_name }}
|
9
|
+
{% endif %}
|
10
|
+
|
11
|
+
@click.command()
|
12
|
+
{%- for option in options %}
|
13
|
+
@click.option('{{ option.flag }}', {{ option.params }})
|
14
|
+
{%- endfor %}
|
15
|
+
{%- for argument in arguments %}
|
16
|
+
@click.argument('{{ argument.name }}'{{ argument.params }})
|
17
|
+
{%- endfor %}
|
18
|
+
@async_command
|
19
|
+
async def {{ command_name }}({{ params_signature }}):
|
20
|
+
"""
|
21
|
+
{{ description }}
|
22
|
+
|
23
|
+
{% if usage_example -%}
|
24
|
+
Usage:
|
25
|
+
{{ usage_example }}
|
26
|
+
{% endif -%}
|
27
|
+
"""
|
28
|
+
{% if with_interactor -%}
|
29
|
+
# Example: Execute an interactor
|
30
|
+
# result = await MyInteractor(param=value)
|
31
|
+
# click.echo(f"Result: {result}")
|
32
|
+
{% endif -%}
|
33
|
+
|
34
|
+
# Your command implementation here
|
35
|
+
click.echo("{{ command_name }} executed!")
|
36
|
+
{% if params_signature -%}
|
37
|
+
{% for param in params_list -%}
|
38
|
+
click.echo(f" {{ param }}: {{'{'}}{{ param }}{{'}'}}")
|
39
|
+
{% endfor -%}
|
40
|
+
{% endif %}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
"""{{ description }}"""
|
2
|
+
import click
|
3
|
+
|
4
|
+
|
5
|
+
@click.command()
|
6
|
+
{%- for option in options %}
|
7
|
+
@click.option('{{ option.flag }}', {{ option.params }})
|
8
|
+
{%- endfor %}
|
9
|
+
{%- for argument in arguments %}
|
10
|
+
@click.argument('{{ argument.name }}'{{ argument.params }})
|
11
|
+
{%- endfor %}
|
12
|
+
def {{ command_name }}({{ params_signature }}):
|
13
|
+
"""{{ description }}"""
|
14
|
+
click.echo("{{ command_name }} executed!")
|
15
|
+
{% if params_signature -%}
|
16
|
+
{% for param in params_list -%}
|
17
|
+
click.echo(f" {{ param }}: {{'{'}}{{ param }}{{'}'}}")
|
18
|
+
{% endfor -%}
|
19
|
+
{% endif %}
|
@@ -189,3 +189,51 @@ def render_sqlalchemy_model(class_name: str, table_name: str) -> str:
|
|
189
189
|
class_name=class_name,
|
190
190
|
table_name=table_name,
|
191
191
|
)
|
192
|
+
|
193
|
+
|
194
|
+
def render_cli_command(
|
195
|
+
command_name: str,
|
196
|
+
description: str,
|
197
|
+
options: list[dict],
|
198
|
+
arguments: list[dict],
|
199
|
+
params_signature: str,
|
200
|
+
params_list: list[str],
|
201
|
+
with_interactor: bool = True,
|
202
|
+
usage_example: str = "",
|
203
|
+
interactor_name: str = "",
|
204
|
+
) -> str:
|
205
|
+
"""Return the template for a CLI command"""
|
206
|
+
return render_template(
|
207
|
+
"command.py.j2",
|
208
|
+
subfolder="cli",
|
209
|
+
command_name=command_name,
|
210
|
+
description=description,
|
211
|
+
options=options,
|
212
|
+
arguments=arguments,
|
213
|
+
params_signature=params_signature,
|
214
|
+
params_list=params_list,
|
215
|
+
with_interactor=with_interactor,
|
216
|
+
usage_example=usage_example,
|
217
|
+
interactor_name=interactor_name,
|
218
|
+
)
|
219
|
+
|
220
|
+
|
221
|
+
def render_cli_command_simple(
|
222
|
+
command_name: str,
|
223
|
+
description: str,
|
224
|
+
options: list[dict],
|
225
|
+
arguments: list[dict],
|
226
|
+
params_signature: str,
|
227
|
+
params_list: list[str],
|
228
|
+
) -> str:
|
229
|
+
"""Return the template for a simple CLI command (non-async)"""
|
230
|
+
return render_template(
|
231
|
+
"command_simple.py.j2",
|
232
|
+
subfolder="cli",
|
233
|
+
command_name=command_name,
|
234
|
+
description=description,
|
235
|
+
options=options,
|
236
|
+
arguments=arguments,
|
237
|
+
params_signature=params_signature,
|
238
|
+
params_list=params_list,
|
239
|
+
)
|
{vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/ARCHITECTURE.md.j2
RENAMED
@@ -507,7 +507,47 @@ class PriceCalculator:
|
|
507
507
|
pass
|
508
508
|
```
|
509
509
|
|
510
|
-
### 6.
|
510
|
+
### 6. Async CLI Commands
|
511
|
+
|
512
|
+
Vega provides the `@async_command` decorator to use async/await in Click CLI commands, allowing you to execute interactors seamlessly:
|
513
|
+
|
514
|
+
```python
|
515
|
+
import click
|
516
|
+
from vega.cli.utils import async_command
|
517
|
+
|
518
|
+
@click.command()
|
519
|
+
@click.option('--name', required=True)
|
520
|
+
@click.option('--email', required=True)
|
521
|
+
@async_command
|
522
|
+
async def create_user(name: str, email: str):
|
523
|
+
"""Create a new user via CLI"""
|
524
|
+
# Import config to initialize DI container
|
525
|
+
import config # noqa: F401
|
526
|
+
from domain.interactors.create_user import CreateUser
|
527
|
+
|
528
|
+
# Execute async interactor
|
529
|
+
user = await CreateUser(name=name, email=email)
|
530
|
+
click.echo(f"Created user: {user.id} - {user.name}")
|
531
|
+
```
|
532
|
+
|
533
|
+
**Benefits:**
|
534
|
+
- Execute async interactors directly in CLI commands
|
535
|
+
- Same business logic works in both CLI and Web (FastAPI) contexts
|
536
|
+
- Clean async/await syntax
|
537
|
+
- Automatic asyncio event loop management
|
538
|
+
|
539
|
+
**Alternative short syntax:**
|
540
|
+
```python
|
541
|
+
from vega.cli.utils import coro # Alias for async_command
|
542
|
+
|
543
|
+
@click.command()
|
544
|
+
@coro
|
545
|
+
async def my_command():
|
546
|
+
result = await MyInteractor()
|
547
|
+
click.echo(result)
|
548
|
+
```
|
549
|
+
|
550
|
+
### 7. Testing
|
511
551
|
|
512
552
|
Vega's architecture makes testing straightforward:
|
513
553
|
|
@@ -54,6 +54,28 @@ vega generate interactor CreateUser
|
|
54
54
|
poetry run pytest
|
55
55
|
```
|
56
56
|
|
57
|
+
## Using Async Commands
|
58
|
+
|
59
|
+
Vega Framework supports async/await in CLI commands, allowing you to execute interactors seamlessly:
|
60
|
+
|
61
|
+
```python
|
62
|
+
import click
|
63
|
+
from vega.cli.utils import async_command
|
64
|
+
|
65
|
+
@click.command()
|
66
|
+
@click.option('--name', required=True)
|
67
|
+
@async_command
|
68
|
+
async def create_user(name: str):
|
69
|
+
"""Create a user using an interactor"""
|
70
|
+
import config # Initialize DI container
|
71
|
+
from domain.interactors.create_user import CreateUser
|
72
|
+
|
73
|
+
user = await CreateUser(name=name)
|
74
|
+
click.echo(f"Created: {user.name}")
|
75
|
+
```
|
76
|
+
|
77
|
+
This allows the same async business logic to work in both CLI and web contexts (FastAPI).
|
78
|
+
|
57
79
|
## Vega Framework
|
58
80
|
|
59
81
|
This project uses [Vega Framework](https://github.com/your-org/vega-framework) for Clean Architecture:
|
@@ -10,6 +10,8 @@ from vega.di import Container, set_container
|
|
10
10
|
# from {{ project_name }}.infrastructure.repositories.memory_user_repository import MemoryUserRepository
|
11
11
|
|
12
12
|
# Uncomment and configure database manager if using SQLAlchemy
|
13
|
+
|
14
|
+
# from settings import settings
|
13
15
|
# from infrastructure.database_manager import DatabaseManager
|
14
16
|
# db_manager = DatabaseManager(url=settings.database_url)
|
15
17
|
|
{vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/main_fastapi.py.j2
RENAMED
@@ -1,7 +1,11 @@
|
|
1
1
|
"""Main entry point for {{ project_name }}"""
|
2
2
|
import click
|
3
|
+
from vega.cli.utils import async_command
|
3
4
|
import config # noqa: F401 - Import to initialize DI container
|
4
5
|
|
6
|
+
# Import your use cases here
|
7
|
+
# from domain.interactors.create_user import CreateUser
|
8
|
+
|
5
9
|
|
6
10
|
@click.group()
|
7
11
|
def cli():
|
@@ -34,6 +38,30 @@ def hello():
|
|
34
38
|
click.echo("Add your CLI commands in presentation/cli/commands/")
|
35
39
|
|
36
40
|
|
41
|
+
@cli.command()
|
42
|
+
@click.option('--name', required=True, help='User name')
|
43
|
+
@click.option('--email', required=True, help='User email')
|
44
|
+
@async_command
|
45
|
+
async def create_user(name: str, email: str):
|
46
|
+
"""
|
47
|
+
Example async CLI command that uses an interactor.
|
48
|
+
|
49
|
+
This demonstrates how to use async/await with Click commands
|
50
|
+
to execute interactors and other async operations.
|
51
|
+
|
52
|
+
Usage:
|
53
|
+
python main.py create-user --name="John Doe" --email="john@example.com"
|
54
|
+
"""
|
55
|
+
# Import your interactor
|
56
|
+
# from domain.interactors.create_user import CreateUser
|
57
|
+
# user = await CreateUser(name=name, email=email)
|
58
|
+
# click.echo(f"Created user: {user.id} - {user.name}")
|
59
|
+
|
60
|
+
# Placeholder implementation
|
61
|
+
click.echo(f"Creating user: {name} ({email})")
|
62
|
+
click.echo("Note: Replace this with your actual CreateUser interactor")
|
63
|
+
|
64
|
+
|
37
65
|
# Add more CLI commands here or import them from presentation/cli/commands/
|
38
66
|
|
39
67
|
|
{vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/main_standard.py.j2
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
"""Main entry point for {{ project_name }}"""
|
2
|
-
import asyncio
|
3
2
|
import click
|
3
|
+
from vega.cli.utils import async_command
|
4
4
|
import config # noqa: F401 - Import to initialize DI container
|
5
5
|
|
6
6
|
# Import your use cases here
|
@@ -27,6 +27,30 @@ def greet(name: str):
|
|
27
27
|
click.echo(f"Hello, {name}!")
|
28
28
|
|
29
29
|
|
30
|
+
@cli.command()
|
31
|
+
@click.option('--name', required=True, help='User name')
|
32
|
+
@click.option('--email', required=True, help='User email')
|
33
|
+
@async_command
|
34
|
+
async def create_user(name: str, email: str):
|
35
|
+
"""
|
36
|
+
Example async command that uses an interactor.
|
37
|
+
|
38
|
+
This demonstrates how to use async/await with Click commands
|
39
|
+
to execute interactors and other async operations.
|
40
|
+
|
41
|
+
Usage:
|
42
|
+
python main.py create-user --name="John Doe" --email="john@example.com"
|
43
|
+
"""
|
44
|
+
# Import your interactor
|
45
|
+
# from domain.interactors.create_user import CreateUser
|
46
|
+
# user = await CreateUser(name=name, email=email)
|
47
|
+
# click.echo(f"Created user: {user.id} - {user.name}")
|
48
|
+
|
49
|
+
# Placeholder implementation
|
50
|
+
click.echo(f"Creating user: {name} ({email})")
|
51
|
+
click.echo("Note: Replace this with your actual CreateUser interactor")
|
52
|
+
|
53
|
+
|
30
54
|
# Uncomment this block if you enable FastAPI support
|
31
55
|
# @cli.command()
|
32
56
|
# @click.option('--host', default='0.0.0.0', help='Host to bind')
|
@@ -7,7 +7,7 @@ from alembic import context
|
|
7
7
|
|
8
8
|
# Import your Base and models
|
9
9
|
from infrastructure.database_manager import Base
|
10
|
-
from
|
10
|
+
from settings import settings
|
11
11
|
|
12
12
|
# this is the Alembic Config object, which provides
|
13
13
|
# access to the values within the .ini file in use.
|
@@ -30,7 +30,7 @@ target_metadata = Base.metadata
|
|
30
30
|
|
31
31
|
def get_url():
|
32
32
|
"""Get database URL, converting async drivers to sync for Alembic"""
|
33
|
-
url = settings.
|
33
|
+
url = settings.database_url
|
34
34
|
# Convert async SQLite driver to sync for Alembic
|
35
35
|
return url.replace("sqlite+aiosqlite:", "sqlite:")
|
36
36
|
|
@@ -2,10 +2,13 @@
|
|
2
2
|
from .naming import NamingConverter
|
3
3
|
from .messages import CLIMessages
|
4
4
|
from .validators import validate_project_name, validate_path_exists
|
5
|
+
from .async_support import async_command, coro
|
5
6
|
|
6
7
|
__all__ = [
|
7
8
|
"NamingConverter",
|
8
9
|
"CLIMessages",
|
9
10
|
"validate_project_name",
|
10
11
|
"validate_path_exists",
|
12
|
+
"async_command",
|
13
|
+
"coro",
|
11
14
|
]
|
@@ -0,0 +1,55 @@
|
|
1
|
+
"""Async support utilities for Click CLI commands"""
|
2
|
+
import asyncio
|
3
|
+
import functools
|
4
|
+
from typing import TypeVar, Callable, Any
|
5
|
+
|
6
|
+
import click
|
7
|
+
|
8
|
+
F = TypeVar('F', bound=Callable[..., Any])
|
9
|
+
|
10
|
+
|
11
|
+
def async_command(f: F) -> F:
|
12
|
+
"""
|
13
|
+
Decorator to make Click commands support async functions.
|
14
|
+
|
15
|
+
This allows you to define async Click commands that can call
|
16
|
+
async interactors and other async operations.
|
17
|
+
|
18
|
+
Example:
|
19
|
+
@click.command()
|
20
|
+
@click.option('--name', required=True)
|
21
|
+
@async_command
|
22
|
+
async def create_user(name: str):
|
23
|
+
# Import config to initialize DI container
|
24
|
+
import config # noqa: F401
|
25
|
+
from domain.interactors.create_user import CreateUser
|
26
|
+
|
27
|
+
user = await CreateUser(name=name)
|
28
|
+
click.echo(f"Created user: {user.name}")
|
29
|
+
|
30
|
+
Usage in Click groups:
|
31
|
+
@cli.command()
|
32
|
+
@click.argument('user_id')
|
33
|
+
@async_command
|
34
|
+
async def get_user(user_id: str):
|
35
|
+
user = await GetUser(user_id=user_id)
|
36
|
+
click.echo(f"User: {user.name}")
|
37
|
+
"""
|
38
|
+
@functools.wraps(f)
|
39
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
40
|
+
return asyncio.run(f(*args, **kwargs))
|
41
|
+
return wrapper # type: ignore
|
42
|
+
|
43
|
+
|
44
|
+
def coro(f: F) -> F:
|
45
|
+
"""
|
46
|
+
Alias for async_command. Shorter name for convenience.
|
47
|
+
|
48
|
+
Example:
|
49
|
+
@click.command()
|
50
|
+
@coro
|
51
|
+
async def my_command():
|
52
|
+
result = await MyInteractor()
|
53
|
+
click.echo(result)
|
54
|
+
"""
|
55
|
+
return async_command(f)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/repository_interface.py.j2
RENAMED
File without changes
|
{vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/service_interface.py.j2
RENAMED
File without changes
|
{vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/infrastructure/model.py.j2
RENAMED
File without changes
|
File without changes
|
{vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/infrastructure/service_impl.py.j2
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/pyproject.toml.j2
RENAMED
File without changes
|
File without changes
|
{vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/sqlalchemy/alembic.ini.j2
RENAMED
File without changes
|
{vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/sqlalchemy/database_manager.py.j2
RENAMED
File without changes
|
{vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/sqlalchemy/script.py.mako
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|