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.
Files changed (71) hide show
  1. {vega_framework-0.1.14 → vega_framework-0.1.16}/PKG-INFO +43 -1
  2. {vega_framework-0.1.14 → vega_framework-0.1.16}/README.md +42 -0
  3. {vega_framework-0.1.14 → vega_framework-0.1.16}/pyproject.toml +1 -1
  4. vega_framework-0.1.16/vega/cli/__init__.py +11 -0
  5. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/generate.py +138 -0
  6. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/migrate.py +3 -2
  7. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/main.py +5 -2
  8. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/__init__.py +4 -0
  9. vega_framework-0.1.16/vega/cli/templates/cli/command.py.j2 +40 -0
  10. vega_framework-0.1.16/vega/cli/templates/cli/command_simple.py.j2 +19 -0
  11. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/components.py +48 -0
  12. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/ARCHITECTURE.md.j2 +41 -1
  13. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/README.md.j2 +22 -0
  14. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/config.py.j2 +2 -0
  15. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/main_fastapi.py.j2 +28 -0
  16. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/main_standard.py.j2 +25 -1
  17. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/sqlalchemy/env.py.j2 +2 -2
  18. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/utils/__init__.py +3 -0
  19. vega_framework-0.1.16/vega/cli/utils/async_support.py +55 -0
  20. vega_framework-0.1.14/vega/cli/__init__.py +0 -5
  21. {vega_framework-0.1.14 → vega_framework-0.1.16}/LICENSE +0 -0
  22. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/__init__.py +0 -0
  23. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/__init__.py +0 -0
  24. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/add.py +0 -0
  25. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/init.py +0 -0
  26. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/commands/update.py +0 -0
  27. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/scaffolds/__init__.py +0 -0
  28. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/scaffolds/fastapi.py +0 -0
  29. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/scaffolds/sqlalchemy.py +0 -0
  30. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/entity.py.j2 +0 -0
  31. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/interactor.py.j2 +0 -0
  32. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/mediator.py.j2 +0 -0
  33. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/repository_interface.py.j2 +0 -0
  34. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/domain/service_interface.py.j2 +0 -0
  35. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/infrastructure/model.py.j2 +0 -0
  36. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/infrastructure/repository_impl.py.j2 +0 -0
  37. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/infrastructure/service_impl.py.j2 +0 -0
  38. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/loader.py +0 -0
  39. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/.env.example +0 -0
  40. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/.gitignore +0 -0
  41. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/main.py.j2 +0 -0
  42. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/pyproject.toml.j2 +0 -0
  43. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/project/settings.py.j2 +0 -0
  44. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/sqlalchemy/alembic.ini.j2 +0 -0
  45. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/sqlalchemy/database_manager.py.j2 +0 -0
  46. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/sqlalchemy/script.py.mako +0 -0
  47. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/__init__.py.j2 +0 -0
  48. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/app.py.j2 +0 -0
  49. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/health_route.py.j2 +0 -0
  50. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/main.py.j2 +0 -0
  51. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/middleware.py.j2 +0 -0
  52. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/models_init.py.j2 +0 -0
  53. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/router.py.j2 +0 -0
  54. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/routes_init.py.j2 +0 -0
  55. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/user_models.py.j2 +0 -0
  56. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/templates/web/users_route.py.j2 +0 -0
  57. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/utils/messages.py +0 -0
  58. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/utils/naming.py +0 -0
  59. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/cli/utils/validators.py +0 -0
  60. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/di/__init__.py +0 -0
  61. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/di/container.py +0 -0
  62. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/di/decorators.py +0 -0
  63. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/di/errors.py +0 -0
  64. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/di/scope.py +0 -0
  65. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/patterns/__init__.py +0 -0
  66. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/patterns/interactor.py +0 -0
  67. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/patterns/mediator.py +0 -0
  68. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/patterns/repository.py +0 -0
  69. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/patterns/service.py +0 -0
  70. {vega_framework-0.1.14 → vega_framework-0.1.16}/vega/settings/__init__.py +0 -0
  71. {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.14
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:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "vega-framework"
3
- version = "0.1.14"
3
+ version = "0.1.16"
4
4
  description = "Enterprise-ready Python framework that enforces Clean Architecture for building maintainable and scalable applications."
5
5
  authors = ["Roberto Ferro"]
6
6
  license = "MIT"
@@ -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
- asyncio.run(_init())
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
+ )
@@ -507,7 +507,47 @@ class PriceCalculator:
507
507
  pass
508
508
  ```
509
509
 
510
- ### 6. Testing
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
 
@@ -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
 
@@ -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 config import settings
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.DATABASE_URL
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)
@@ -1,5 +0,0 @@
1
- """Vega Framework CLI tools"""
2
-
3
- from vega.cli.main import cli
4
-
5
- __all__ = ["cli"]
File without changes