vega-framework 0.1.22__py3-none-any.whl → 0.1.23__py3-none-any.whl

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.
@@ -16,6 +16,7 @@ from vega.cli.templates import (
16
16
  render_sqlalchemy_model,
17
17
  render_cli_command,
18
18
  render_cli_command_simple,
19
+ render_template,
19
20
  )
20
21
  from vega.cli.scaffolds import create_fastapi_scaffold
21
22
 
@@ -69,6 +70,8 @@ def generate_component(
69
70
  name: str,
70
71
  project_path: str,
71
72
  implementation: str | None = None,
73
+ is_request: bool = False,
74
+ is_response: bool = False,
72
75
  ):
73
76
  """Generate a component in the Vega project"""
74
77
 
@@ -126,6 +129,8 @@ def generate_component(
126
129
  _generate_middleware(project_root, project_name, class_name, file_name)
127
130
  elif component_type == 'model':
128
131
  _generate_sqlalchemy_model(project_root, project_name, class_name, file_name)
132
+ elif component_type == 'webmodel':
133
+ _generate_web_models(project_root, project_name, name, is_request, is_response)
129
134
  elif component_type == 'command':
130
135
  _generate_command(project_root, project_name, name, implementation)
131
136
 
@@ -451,6 +456,97 @@ def _generate_router(project_root: Path, project_name: str, name: str) -> None:
451
456
  click.echo(click.style(f" (Router auto-discovered from web/routes/)", fg='bright_black'))
452
457
 
453
458
 
459
+ def _generate_web_models(project_root: Path, project_name: str, name: str, is_request: bool, is_response: bool) -> None:
460
+ """Generate Pydantic request or response model for FastAPI"""
461
+
462
+ # Check if web folder exists
463
+ web_path = project_root / "presentation" / "web"
464
+ if not web_path.exists():
465
+ click.echo(click.style("ERROR: Web module not found", fg='red'))
466
+ click.echo(" Model generation requires FastAPI web module")
467
+ click.echo(" Install it with: vega add web")
468
+ return
469
+
470
+ # Validate flags
471
+ if not is_request and not is_response:
472
+ click.echo(click.style("ERROR: Must specify either --request or --response", fg='red'))
473
+ click.echo(" Examples:")
474
+ click.echo(" vega generate webmodel CreateUserRequest --request")
475
+ click.echo(" vega generate webmodel UserResponse --response")
476
+ return
477
+
478
+ if is_request and is_response:
479
+ click.echo(click.style("ERROR: Cannot specify both --request and --response", fg='red'))
480
+ click.echo(" Use separate commands to generate both types")
481
+ return
482
+
483
+ # Ensure models directory exists
484
+ models_path = web_path / "models"
485
+ models_path.mkdir(exist_ok=True)
486
+
487
+ # Ensure __init__.py exists
488
+ init_file = models_path / "__init__.py"
489
+ if not init_file.exists():
490
+ init_file.write_text('"""Pydantic models for API validation"""\n')
491
+
492
+ # Convert name to PascalCase for class names
493
+ model_name = to_pascal_case(name)
494
+ model_file = to_snake_case(model_name)
495
+
496
+ # Determine model type
497
+ if is_request:
498
+ template_file = "request_model.py.j2"
499
+ description = "Request model for API validation"
500
+ model_type = "request"
501
+ else:
502
+ template_file = "response_model.py.j2"
503
+ description = "Response model for API data"
504
+ model_type = "response"
505
+
506
+ # Generate model file
507
+ file_path = models_path / f"{model_file}.py"
508
+
509
+ if file_path.exists():
510
+ # Append to existing file
511
+ click.echo(click.style(f"WARNING: {file_path.relative_to(project_root)} already exists", fg='yellow'))
512
+ click.echo(f" Appending {model_name} to existing file...")
513
+
514
+ content = render_template(
515
+ template_file,
516
+ subfolder="web",
517
+ model_name=model_name,
518
+ description=description
519
+ )
520
+
521
+ # Remove imports from template since they're already in the file
522
+ lines = content.split('\n')
523
+ class_start = next((i for i, line in enumerate(lines) if line.startswith('class ')), 0)
524
+ content_to_append = '\n\n' + '\n'.join(lines[class_start:])
525
+
526
+ with file_path.open('a', encoding='utf-8') as f:
527
+ f.write(content_to_append)
528
+
529
+ click.echo(click.style("+ ", fg='green', bold=True) + f"Added {model_name} to {file_path.relative_to(project_root)}")
530
+ else:
531
+ # Create new file
532
+ content = render_template(
533
+ template_file,
534
+ subfolder="web",
535
+ model_name=model_name,
536
+ description=description
537
+ )
538
+ file_path.write_text(content, encoding='utf-8')
539
+
540
+ click.echo(click.style("+ ", fg='green', bold=True) + f"Created {file_path.relative_to(project_root)}")
541
+
542
+ # Instructions for next steps
543
+ click.echo(f"\nNext steps:")
544
+ click.echo(f" 1. Add fields to {model_name} in {file_path.relative_to(project_root)}")
545
+ click.echo(f" 2. Update the Config.json_schema_extra with example values")
546
+ click.echo(f" 3. Import in your router:")
547
+ click.echo(f" from presentation.web.models.{model_file} import {model_name}")
548
+
549
+
454
550
  def _generate_middleware(project_root: Path, project_name: str, class_name: str, file_name: str) -> None:
455
551
  """Generate a FastAPI middleware"""
456
552
 
vega/cli/main.py CHANGED
@@ -56,12 +56,14 @@ 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', 'command'
59
+ 'entity', 'repository', 'repo', 'service', 'interactor', 'mediator', 'router', 'middleware', 'webmodel', 'model', 'command'
60
60
  ]))
61
61
  @click.argument('name')
62
62
  @click.option('--path', default='.', help='Project root path')
63
63
  @click.option('--impl', default=None, help='Generate infrastructure implementation for repository/service (e.g., memory, sql) or command type (async, sync)')
64
- def generate(component_type, name, path, impl):
64
+ @click.option('--request', is_flag=True, help='Generate request model (for webmodel)')
65
+ @click.option('--response', is_flag=True, help='Generate response model (for webmodel)')
66
+ def generate(component_type, name, path, impl, request, response):
65
67
  """
66
68
  Generate a component in your Vega project.
67
69
 
@@ -74,6 +76,7 @@ def generate(component_type, name, path, impl):
74
76
  mediator - Workflow (orchestrates use cases)
75
77
  router - FastAPI router (requires web module)
76
78
  middleware - FastAPI middleware (requires web module)
79
+ webmodel - Pydantic request/response models (requires web module)
77
80
  model - SQLAlchemy model (requires sqlalchemy module)
78
81
  command - CLI command (async by default)
79
82
 
@@ -85,6 +88,8 @@ def generate(component_type, name, path, impl):
85
88
  vega generate mediator CheckoutFlow
86
89
  vega generate router Product
87
90
  vega generate middleware Logging
91
+ vega generate webmodel CreateUserRequest --request
92
+ vega generate webmodel UserResponse --response
88
93
  vega generate model User
89
94
  vega generate command CreateUser
90
95
  vega generate command ListUsers --impl sync
@@ -93,7 +98,7 @@ def generate(component_type, name, path, impl):
93
98
  if component_type == 'repo':
94
99
  component_type = 'repository'
95
100
 
96
- generate_component(component_type, name, path, impl)
101
+ generate_component(component_type, name, path, impl, request, response)
97
102
 
98
103
 
99
104
  @cli.command()
@@ -1,8 +1,8 @@
1
1
  """{{ impl_class }} implementation"""
2
2
  from typing import List
3
3
 
4
- from ...domain.entities.{{ entity_file }} import {{ entity_name }}
5
- from ...domain.repositories.{{ interface_file_name }} import {{ interface_class_name }}
4
+ from domain.entities.{{ entity_file }} import {{ entity_name }}
5
+ from domain.repositories.{{ interface_file_name }} import {{ interface_class_name }}
6
6
 
7
7
 
8
8
  class {{ impl_class }}({{ interface_class_name }}):
@@ -1,5 +1,5 @@
1
1
  """{{ impl_class }} implementation"""
2
- from ...domain.services.{{ interface_file_name }} import {{ interface_class_name }}
2
+ from domain.services.{{ interface_file_name }} import {{ interface_class_name }}
3
3
 
4
4
 
5
5
  class {{ impl_class }}({{ interface_class_name }}):
@@ -342,6 +342,8 @@ The generator will interactively prompt for:
342
342
  - Whether to use interactors
343
343
  - Parameter types and validation
344
344
 
345
+ Commands are **automatically discovered** from `presentation/cli/commands/` - no manual registration required.
346
+
345
347
  **FastAPI Routers** - HTTP API endpoints (requires web support):
346
348
  ```bash
347
349
  vega generate router User
@@ -349,6 +351,8 @@ vega generate router Product
349
351
  vega generate router Order
350
352
  ```
351
353
 
354
+ Routers are **automatically discovered** from `presentation/web/routes/` - no manual registration required.
355
+
352
356
  **FastAPI Middleware** - Request/response processing (requires web support):
353
357
  ```bash
354
358
  vega generate middleware Logging
@@ -488,6 +492,77 @@ vega doctor
488
492
 
489
493
  For complete documentation, see the [README](README.md#cli-commands).
490
494
 
495
+ ## Auto-Discovery
496
+
497
+ Vega Framework provides **automatic component discovery** for routers and commands, eliminating manual registration boilerplate.
498
+
499
+ ### How It Works
500
+
501
+ The framework automatically scans specific directories and registers components at runtime:
502
+
503
+ **CLI Commands Discovery:**
504
+ - Location: `presentation/cli/commands/`
505
+ - Discovers: All `click.Command` instances
506
+ - Convention: Export commands in module files
507
+
508
+ **Router Discovery:**
509
+ - Location: `presentation/web/routes/`
510
+ - Discovers: All `APIRouter` instances named `router`
511
+ - Convention: Each file exports a `router = APIRouter()`
512
+
513
+ ### Usage Example
514
+
515
+ **Creating a new router:**
516
+ ```bash
517
+ vega generate router Product
518
+ ```
519
+
520
+ This creates `presentation/web/routes/product.py`:
521
+ ```python
522
+ from fastapi import APIRouter
523
+
524
+ router = APIRouter() # MUST be named 'router'
525
+
526
+ @router.get("/")
527
+ async def list_products():
528
+ return {"products": []}
529
+ ```
530
+
531
+ The router is **automatically**:
532
+ - ✅ Discovered by scanning the routes directory
533
+ - ✅ Registered with prefix `/product`
534
+ - ✅ Tagged as "Product"
535
+ - ✅ Available at `/api/product`
536
+
537
+ **No manual registration needed!**
538
+
539
+ ### Framework Implementation
540
+
541
+ The discovery system is centralized in the framework:
542
+
543
+ ```python
544
+ # In presentation/web/routes/__init__.py (auto-generated)
545
+ from vega.discovery import discover_routers
546
+
547
+ def get_api_router():
548
+ return discover_routers(__package__)
549
+ ```
550
+
551
+ ```python
552
+ # In presentation/cli/commands/__init__.py (auto-generated)
553
+ from vega.discovery import discover_commands
554
+
555
+ def get_commands():
556
+ return discover_commands(__package__)
557
+ ```
558
+
559
+ ### Benefits
560
+
561
+ - **Zero Boilerplate**: No need to manually import and register components
562
+ - **Convention Over Configuration**: Follow naming conventions, get automatic registration
563
+ - **Maintainability**: Add/remove components without touching configuration files
564
+ - **Consistency**: Same pattern as dependency injection (`@bind`, `@injectable`)
565
+
491
566
  ## Dependency Injection
492
567
 
493
568
  Vega provides automatic dependency injection through decorators and a container.
@@ -0,0 +1,18 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional
3
+
4
+
5
+ class {{ model_name }}(BaseModel):
6
+ """{{ description }}"""
7
+ # Add your fields here
8
+ # Example:
9
+ # name: str = Field(..., min_length=1, max_length=100, description="Name field")
10
+ # email: str = Field(..., description="Email address")
11
+ # age: Optional[int] = Field(None, ge=0, description="Age field")
12
+
13
+ class Config:
14
+ json_schema_extra = {
15
+ "example": {
16
+ # Add example values here
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional
3
+
4
+
5
+ class {{ model_name }}(BaseModel):
6
+ """{{ description }}"""
7
+ # Add your fields here
8
+ # Example:
9
+ # id: str = Field(..., description="Unique identifier")
10
+ # name: str = Field(..., description="Name field")
11
+ # created_at: str = Field(..., description="Creation timestamp")
12
+
13
+ class Config:
14
+ json_schema_extra = {
15
+ "example": {
16
+ # Add example values here
17
+ }
18
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vega-framework
3
- Version: 0.1.22
3
+ Version: 0.1.23
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
@@ -2,11 +2,11 @@ vega/__init__.py,sha256=A05RwOYXooAZUz3GnbJ--ofLXgtRZK9gaSmsLVRdGPY,1811
2
2
  vega/cli/__init__.py,sha256=NCzOOyhKHqLeN1r80ekhMfkQwBdAQXKcKiKoNwYPNiY,304
3
3
  vega/cli/commands/__init__.py,sha256=UH7MdYduBG_YoulgdiWkUCtcgGLzuYRGFzxaqoa0pyg,19
4
4
  vega/cli/commands/add.py,sha256=yiFXD4wb6gc_1nA-ofzt7VQKOaNbeMGNmJ-LQWG6Gh4,5110
5
- vega/cli/commands/generate.py,sha256=t5TM14HQEQPcM-KgJU7aXd9azXwCXvdhWUrHLjRJsJ0,30766
5
+ vega/cli/commands/generate.py,sha256=sR8d-0mgF9Aq0qy-QGAB_-JndCw82EpkwHcXQQxDMnM,34569
6
6
  vega/cli/commands/init.py,sha256=C_ye_hOIyaIUalSfXmLBE7QfSpHoZ_LTB-h2HPP6_R8,6064
7
7
  vega/cli/commands/migrate.py,sha256=00swKeVhmKr1_1VJhg3GpIMcJ6Jfgz5FUpmJoLf5qSQ,3805
8
8
  vega/cli/commands/update.py,sha256=0zRWkHvQwKGlJL9XF3bi--dThkFapyNOugL6AgGr6Ic,5897
9
- vega/cli/main.py,sha256=AhH560Fin6BDUXpVOJ2rVwt4e7cq2iqrF8XqDmaJjEI,4655
9
+ vega/cli/main.py,sha256=_UEgiHj9OrNu-pqNrTQ6Fkp_fRBfs2wxU4n7JCMadyo,5072
10
10
  vega/cli/scaffolds/__init__.py,sha256=WFJf2H_4UWL89gDxX8PXKkTVSVOfw7hFfyaPWKokp1g,217
11
11
  vega/cli/scaffolds/fastapi.py,sha256=a_vZVSfMgyteRURFZAShbtjSRMOSM4YeEIKKvBtAOfo,3788
12
12
  vega/cli/scaffolds/sqlalchemy.py,sha256=il5JqiA8LSQKnNoOYfAFD82rdYx5l_ZsqsjHnplYohw,6164
@@ -21,12 +21,12 @@ vega/cli/templates/domain/mediator.py.j2,sha256=gdEDNscYaB3wOemaVrFr3qKVE5sTS5wR
21
21
  vega/cli/templates/domain/repository_interface.py.j2,sha256=oywc1Uvyx-bBrUveE-bMt4x4eO2LCf5EUbddPbNGXm8,579
22
22
  vega/cli/templates/domain/service_interface.py.j2,sha256=Pq-wwGvGbns9Z_2lqL8Xi2_G-MMU-0JjAuGW94H6mrE,330
23
23
  vega/cli/templates/infrastructure/model.py.j2,sha256=zNY4m7BZJeQhdu_bTFY6WIhizsYW4eJY6PS1DCs0vbE,796
24
- vega/cli/templates/infrastructure/repository_impl.py.j2,sha256=1x69Tj6lZB1aOQDuaiuFESVHzyFoRDaLbuI2-cFMjLY,494
25
- vega/cli/templates/infrastructure/service_impl.py.j2,sha256=AgP2oqMbbcd0ctVfiVyOWbHFXTf26Jr70CA20n-xgmI,379
24
+ vega/cli/templates/infrastructure/repository_impl.py.j2,sha256=Rmip4Swh3wdJRioVOWKgJVQgl45Y9STu3M-oviaOrPM,488
25
+ vega/cli/templates/infrastructure/service_impl.py.j2,sha256=JYCfPDOcdVd-x1Y0nSh7qoHLc2yEsDhEraS1YGm5iiQ,376
26
26
  vega/cli/templates/loader.py,sha256=cyt9ZOVLwBgv4ijPxoqR4JgFSbdkXCMzviHcq3fdv8Q,2712
27
27
  vega/cli/templates/project/.env.example,sha256=BqfRxYeOasgZMz4_B2kybu6areIy9dHCt5BJ6fa56ik,221
28
28
  vega/cli/templates/project/.gitignore,sha256=7JSDihCtBznd-QxQ4wUtHM9fnbYnbw6PK4Auh56ofAY,229
29
- vega/cli/templates/project/ARCHITECTURE.md.j2,sha256=P7NiaGDp3ekPP3Fn5mtlMUWf66qqAAFtZzcmYRXJKis,23737
29
+ vega/cli/templates/project/ARCHITECTURE.md.j2,sha256=HunrJ_9LlPxd5-GONaJxjoLlw-XfjYaLpsVHFa72Yf4,25868
30
30
  vega/cli/templates/project/README.md.j2,sha256=Oea0AiDb_rm-GPsLFAPhYEpMmtHt_pqCRjrreymZj8Y,4601
31
31
  vega/cli/templates/project/config.py.j2,sha256=1Iva9JEz5ej_WmTbRVBvOfSBhYUKIzN88p6GYKR0m4s,866
32
32
  vega/cli/templates/project/main.py.j2,sha256=RfasD-JOYv8rjtZOapmh7YNybvtsdIGED7pzyRxNyAM,2733
@@ -44,6 +44,8 @@ vega/cli/templates/web/health_route.py.j2,sha256=iq30wENqti14j3JOfVqJNO4WAE2NiNY
44
44
  vega/cli/templates/web/main.py.j2,sha256=QKWFDUNeIESkmVrvUIcRQH0DXnMu5fRCH2uHp3u8Sws,312
45
45
  vega/cli/templates/web/middleware.py.j2,sha256=ild8nAdq45HGCiA-XVcWgmMBCfwIgORcZ7qsykmOclw,2165
46
46
  vega/cli/templates/web/models_init.py.j2,sha256=QdNOaZ6iMh3Tz7uut1VA3XUIivYD1SkHFmDHCfstqho,159
47
+ vega/cli/templates/web/request_model.py.j2,sha256=sD91Gxgeelj6g3XV9QgqyfpAn86EdD4zVESD54B-eZU,527
48
+ vega/cli/templates/web/response_model.py.j2,sha256=tDZoeMRb7OweACG77jAixJgOcx0Vv7HwirrjkanLQ2s,497
47
49
  vega/cli/templates/web/router.py.j2,sha256=5FgBYTcsdHH24Y9GtlsPWMllwygFO5aaCNCqNQGlhgc,4295
48
50
  vega/cli/templates/web/routes_init.py.j2,sha256=uyeuLGRZ-bDY-xefJ1eI9wU1kt9z7lwoVjw_nvY7Vk4,397
49
51
  vega/cli/templates/web/routes_init_autodiscovery.py.j2,sha256=jxvFGmWsmW1tMCeOoYfPi4l0U94YkUxcZm2VFsGgTsw,310
@@ -69,8 +71,8 @@ vega/patterns/repository.py,sha256=uYUyLs-O8OqW1Wb9ZqIo8UUcCjZ5UFuHors_F2iDg9A,1
69
71
  vega/patterns/service.py,sha256=buFRgJoeQtZQK22Upb4vh84c1elWKFXWBaB0X4RaruE,1374
70
72
  vega/settings/__init__.py,sha256=Eb8PMUyXAlCAQIcL2W8QhTTUHUbVlkAfXdpTUlADo1I,786
71
73
  vega/settings/base.py,sha256=bL45hyoa3t-hQOvur860eSo7O833sQMsXJJPwbTVbwE,1321
72
- vega_framework-0.1.22.dist-info/METADATA,sha256=IEc8i6bby7j6rIt6pGrNwSjNNcWfAqwHaxMf5xe8sfs,12271
73
- vega_framework-0.1.22.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
74
- vega_framework-0.1.22.dist-info/entry_points.txt,sha256=p3gyTmPYjNRLbuiKS-hG3ytWd-ssBweFy6VZ-F9FTNk,42
75
- vega_framework-0.1.22.dist-info/licenses/LICENSE,sha256=wlHh1MBTcs2kSQr99P30mZ61s7uh7Cp9Rk0YiJxots0,1084
76
- vega_framework-0.1.22.dist-info/RECORD,,
74
+ vega_framework-0.1.23.dist-info/METADATA,sha256=BXMTa0Evjhiw7vC2JOIJDOPZQYWCisC02AZUDB-pRFc,12271
75
+ vega_framework-0.1.23.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
76
+ vega_framework-0.1.23.dist-info/entry_points.txt,sha256=p3gyTmPYjNRLbuiKS-hG3ytWd-ssBweFy6VZ-F9FTNk,42
77
+ vega_framework-0.1.23.dist-info/licenses/LICENSE,sha256=wlHh1MBTcs2kSQr99P30mZ61s7uh7Cp9Rk0YiJxots0,1084
78
+ vega_framework-0.1.23.dist-info/RECORD,,