fast-clean-architecture 1.0.0__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.
Files changed (30) hide show
  1. fast_clean_architecture/__init__.py +24 -0
  2. fast_clean_architecture/cli.py +480 -0
  3. fast_clean_architecture/config.py +506 -0
  4. fast_clean_architecture/exceptions.py +63 -0
  5. fast_clean_architecture/generators/__init__.py +11 -0
  6. fast_clean_architecture/generators/component_generator.py +1039 -0
  7. fast_clean_architecture/generators/config_updater.py +308 -0
  8. fast_clean_architecture/generators/package_generator.py +174 -0
  9. fast_clean_architecture/generators/template_validator.py +546 -0
  10. fast_clean_architecture/generators/validation_config.py +75 -0
  11. fast_clean_architecture/generators/validation_metrics.py +193 -0
  12. fast_clean_architecture/templates/__init__.py +7 -0
  13. fast_clean_architecture/templates/__init__.py.j2 +26 -0
  14. fast_clean_architecture/templates/api.py.j2 +65 -0
  15. fast_clean_architecture/templates/command.py.j2 +26 -0
  16. fast_clean_architecture/templates/entity.py.j2 +49 -0
  17. fast_clean_architecture/templates/external.py.j2 +61 -0
  18. fast_clean_architecture/templates/infrastructure_repository.py.j2 +69 -0
  19. fast_clean_architecture/templates/model.py.j2 +38 -0
  20. fast_clean_architecture/templates/query.py.j2 +26 -0
  21. fast_clean_architecture/templates/repository.py.j2 +57 -0
  22. fast_clean_architecture/templates/schemas.py.j2 +32 -0
  23. fast_clean_architecture/templates/service.py.j2 +109 -0
  24. fast_clean_architecture/templates/value_object.py.j2 +34 -0
  25. fast_clean_architecture/utils.py +553 -0
  26. fast_clean_architecture-1.0.0.dist-info/METADATA +541 -0
  27. fast_clean_architecture-1.0.0.dist-info/RECORD +30 -0
  28. fast_clean_architecture-1.0.0.dist-info/WHEEL +4 -0
  29. fast_clean_architecture-1.0.0.dist-info/entry_points.txt +2 -0
  30. fast_clean_architecture-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,193 @@
1
+ """Performance monitoring and metrics collection for template validation."""
2
+
3
+ import time
4
+ import logging
5
+ import threading
6
+ from contextlib import contextmanager
7
+ from typing import Dict, Any, Optional, Generator
8
+ from dataclasses import dataclass, field
9
+ from threading import Lock
10
+
11
+ from .validation_config import ValidationMetrics
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class ValidationStats:
18
+ """Simplified statistics for template validation operations."""
19
+
20
+ total_validations: int = 0
21
+ successful_validations: int = 0
22
+ failed_validations: int = 0
23
+ errors_by_type: Dict[str, int] = field(default_factory=dict)
24
+ fallback_used_count: int = 0
25
+ total_time_ms: float = 0.0
26
+ min_time_ms: float = float("inf")
27
+ max_time_ms: float = 0.0
28
+ strategy_usage: Dict[str, int] = field(default_factory=dict)
29
+ _lock: threading.Lock = field(default_factory=threading.Lock)
30
+
31
+ def update(
32
+ self,
33
+ metrics: ValidationMetrics,
34
+ success: bool,
35
+ error_type: Optional[str] = None,
36
+ ):
37
+ """Update statistics with new validation metrics."""
38
+ with self._lock:
39
+ self.total_validations += 1
40
+
41
+ if success:
42
+ self.successful_validations += 1
43
+ else:
44
+ self.failed_validations += 1
45
+ if error_type:
46
+ self.errors_by_type[error_type] = (
47
+ self.errors_by_type.get(error_type, 0) + 1
48
+ )
49
+
50
+ if metrics.fallback_used:
51
+ self.fallback_used_count += 1
52
+
53
+ # Update timing statistics
54
+ self.total_time_ms += metrics.validation_time_ms
55
+ self.min_time_ms = min(self.min_time_ms, metrics.validation_time_ms)
56
+ self.max_time_ms = max(self.max_time_ms, metrics.validation_time_ms)
57
+
58
+ # Update strategy usage
59
+ strategy = metrics.strategy_used
60
+ self.strategy_usage[strategy] = self.strategy_usage.get(strategy, 0) + 1
61
+
62
+ @property
63
+ def average_time_ms(self) -> float:
64
+ """Calculate average validation time."""
65
+ if self.total_validations == 0:
66
+ return 0.0
67
+ return self.total_time_ms / self.total_validations
68
+
69
+ @property
70
+ def success_rate(self) -> float:
71
+ """Calculate success rate as percentage."""
72
+ if self.total_validations == 0:
73
+ return 0.0
74
+ return (self.successful_validations / self.total_validations) * 100
75
+
76
+ @property
77
+ def fallback_rate(self) -> float:
78
+ """Calculate fallback usage rate as percentage."""
79
+ if self.total_validations == 0:
80
+ return 0.0
81
+ return (self.fallback_used_count / self.total_validations) * 100
82
+
83
+ def to_dict(self) -> Dict[str, Any]:
84
+ """Convert statistics to dictionary for reporting."""
85
+ return {
86
+ "total_validations": self.total_validations,
87
+ "successful_validations": self.successful_validations,
88
+ "failed_validations": self.failed_validations,
89
+ "success_rate_percent": round(self.success_rate, 2),
90
+ "fallback_used_count": self.fallback_used_count,
91
+ "fallback_rate_percent": round(self.fallback_rate, 2),
92
+ "timing": {
93
+ "total_time_ms": round(self.total_time_ms, 3),
94
+ "average_time_ms": round(self.average_time_ms, 3),
95
+ "min_time_ms": (
96
+ round(self.min_time_ms, 3)
97
+ if self.min_time_ms != float("inf")
98
+ else 0
99
+ ),
100
+ "max_time_ms": round(self.max_time_ms, 3),
101
+ },
102
+ "strategy_usage": self.strategy_usage,
103
+ "error_types": self.errors_by_type,
104
+ }
105
+
106
+
107
+ class ValidationMetricsCollector:
108
+ """Simplified collector for validation metrics."""
109
+
110
+ def __init__(self):
111
+ self.stats = ValidationStats()
112
+
113
+ def record_validation(self, success: bool, error_type: Optional[str] = None):
114
+ """Record a validation operation.
115
+
116
+ Args:
117
+ success: Whether the validation succeeded
118
+ error_type: Type of error if validation failed
119
+ """
120
+ self.stats.total_validations += 1
121
+ if success:
122
+ self.stats.successful_validations += 1
123
+ else:
124
+ self.stats.failed_validations += 1
125
+ if error_type:
126
+ self.stats.errors_by_type[error_type] = (
127
+ self.stats.errors_by_type.get(error_type, 0) + 1
128
+ )
129
+
130
+ def get_stats(self) -> ValidationStats:
131
+ """Get current statistics."""
132
+ return self.stats
133
+
134
+ def reset(self) -> None:
135
+ """Reset all statistics."""
136
+ self.stats = ValidationStats()
137
+
138
+
139
+ # Global metrics collector instance
140
+ _metrics_collector = ValidationMetricsCollector()
141
+
142
+
143
+ def get_metrics_collector() -> ValidationMetricsCollector:
144
+ """Get the global metrics collector instance."""
145
+ return _metrics_collector
146
+
147
+
148
+ @contextmanager
149
+ def timed_validation(
150
+ strategy_name: str,
151
+ template_size: int,
152
+ variables_count: int,
153
+ enable_timing: bool = True,
154
+ ) -> Generator[ValidationMetrics, None, None]:
155
+ """Context manager for timing validation operations."""
156
+ start_time = time.perf_counter()
157
+
158
+ metrics = ValidationMetrics(
159
+ strategy_used=strategy_name,
160
+ validation_time_ms=0.0,
161
+ template_size_bytes=template_size,
162
+ variables_count=variables_count,
163
+ undefined_variables_found=0,
164
+ )
165
+
166
+ try:
167
+ yield metrics
168
+ finally:
169
+ if enable_timing:
170
+ end_time = time.perf_counter()
171
+ metrics.validation_time_ms = (end_time - start_time) * 1000
172
+
173
+ # Log slow validations
174
+ if metrics.validation_time_ms > 100: # Configurable threshold
175
+ logger.warning(
176
+ f"Slow validation detected: {strategy_name} took {metrics.validation_time_ms:.3f}ms"
177
+ )
178
+
179
+
180
+ @contextmanager
181
+ def validation_timeout(timeout_seconds: float):
182
+ """Context manager for validation timeout (placeholder for future implementation)."""
183
+ # Note: This is a placeholder. Full timeout implementation would require
184
+ # threading or async support, which depends on the application architecture.
185
+ start_time = time.time()
186
+ try:
187
+ yield
188
+ finally:
189
+ elapsed = time.time() - start_time
190
+ if elapsed > timeout_seconds:
191
+ logger.warning(
192
+ f"Validation exceeded timeout: {elapsed:.3f}s > {timeout_seconds}s"
193
+ )
@@ -0,0 +1,7 @@
1
+ """Jinja2 templates for Fast Clean Architecture."""
2
+
3
+ from pathlib import Path
4
+
5
+ TEMPLATES_DIR = Path(__file__).parent
6
+
7
+ __all__ = ["TEMPLATES_DIR"]
@@ -0,0 +1,26 @@
1
+ {% if package_type == "empty" %}
2
+ """
3
+ {{ package_description }} package for {{ context }}.
4
+ """
5
+ {% elif package_type == "component" %}
6
+ """
7
+ {{ component_type|title }} components for {{ module_name }} module.
8
+ """
9
+ {% for component in components %}
10
+ from .{{ component.file_name }} import {{ component.class_name }}
11
+ {% endfor %}
12
+
13
+ __all__ = [
14
+ {% for component in components %}
15
+ "{{ component.class_name }}",
16
+ {% endfor %}
17
+ ]
18
+ {% elif package_type == "system" %}
19
+ """
20
+ {{ system_name|title }} system context.
21
+ """
22
+ {% elif package_type == "module" %}
23
+ """
24
+ {{ module_name|title }} module for {{ system_name }} system.
25
+ """
26
+ {% endif %}
@@ -0,0 +1,65 @@
1
+ """
2
+ {{ router_name }} API router for {{ module_name }} module.
3
+ """
4
+ from fastapi import APIRouter, Depends, HTTPException
5
+ from typing import List
6
+ from ..schemas.{{ schema_file }} import {{ SchemaName }}Create, {{ SchemaName }}Response
7
+ from ...application.services.{{ service_file }} import {{ ServiceName }}Service
8
+
9
+ router = APIRouter(prefix="/{{ resource_name }}", tags=["{{ resource_name }}"])
10
+
11
+
12
+ @router.post("/", response_model={{ SchemaName }}Response)
13
+ async def create_{{ entity_name }}(
14
+ data: {{ SchemaName }}Create,
15
+ service: {{ ServiceName }}Service = Depends()
16
+ ):
17
+ """Create new {{ entity_name }}."""
18
+ entity = await service.create_{{ entity_name }}(data.model_dump())
19
+ return {{ SchemaName }}Response.model_validate(entity)
20
+
21
+
22
+ @router.get("/", response_model=List[{{ SchemaName }}Response])
23
+ async def list_{{ resource_name }}(
24
+ service: {{ ServiceName }}Service = Depends()
25
+ ):
26
+ """List all {{ resource_name }}."""
27
+ entities = await service.list_{{ resource_name }}()
28
+ return [{{ SchemaName }}Response.model_validate(entity) for entity in entities]
29
+
30
+
31
+ @router.get("/{id}", response_model={{ SchemaName }}Response)
32
+ async def get_{{ entity_name }}(
33
+ id: str,
34
+ service: {{ ServiceName }}Service = Depends()
35
+ ):
36
+ """Get {{ entity_name }} by ID."""
37
+ entity = await service.get_{{ entity_name }}_by_id(id)
38
+ if not entity:
39
+ raise HTTPException(status_code=404, detail="{{ EntityName }} not found")
40
+ return {{ SchemaName }}Response.model_validate(entity)
41
+
42
+
43
+ @router.put("/{id}", response_model={{ SchemaName }}Response)
44
+ async def update_{{ entity_name }}(
45
+ id: str,
46
+ data: {{ SchemaName }}Create,
47
+ service: {{ ServiceName }}Service = Depends()
48
+ ):
49
+ """Update {{ entity_name }}."""
50
+ entity = await service.update_{{ entity_name }}(id, data.model_dump())
51
+ if not entity:
52
+ raise HTTPException(status_code=404, detail="{{ EntityName }} not found")
53
+ return {{ SchemaName }}Response.model_validate(entity)
54
+
55
+
56
+ @router.delete("/{id}")
57
+ async def delete_{{ entity_name }}(
58
+ id: str,
59
+ service: {{ ServiceName }}Service = Depends()
60
+ ):
61
+ """Delete {{ entity_name }}."""
62
+ success = await service.delete_{{ entity_name }}(id)
63
+ if not success:
64
+ raise HTTPException(status_code=404, detail="{{ EntityName }} not found")
65
+ return {"message": "{{ EntityName }} deleted successfully"}
@@ -0,0 +1,26 @@
1
+ """
2
+ {{ command_name }} command for {{ module_name }} module.
3
+ """
4
+ from dataclasses import dataclass
5
+ from typing import Any, Dict
6
+
7
+
8
+ @dataclass
9
+ class {{ CommandName }}Command:
10
+ """Command for {{ command_name }} operation."""
11
+
12
+ def __post_init__(self):
13
+ """Post-initialization validation."""
14
+ pass
15
+
16
+
17
+ class {{ CommandName }}Handler:
18
+ """Handler for {{ CommandName }}Command."""
19
+
20
+ def __init__(self):
21
+ pass
22
+
23
+ async def handle(self, command: {{ CommandName }}Command) -> Any:
24
+ """Handle the {{ command_name }} command."""
25
+ # Implementation here
26
+ pass
@@ -0,0 +1,49 @@
1
+ """
2
+ {{ entity_name }} entity for {{ module_name }} module.
3
+
4
+ Generated at: {{ generated_at }}
5
+ Generator version: {{ generator_version }}
6
+ """
7
+ from dataclasses import dataclass, field
8
+ from typing import Optional
9
+ from datetime import datetime
10
+ from uuid import UUID, uuid4
11
+
12
+
13
+ @dataclass
14
+ class {{ EntityName }}:
15
+ """{{ EntityName }} domain entity.
16
+
17
+ Represents a {{ entity_description }} in the {{ module_name }} domain.
18
+ """
19
+
20
+ # Primary identifier
21
+ id: Optional[UUID] = field(default_factory=uuid4)
22
+
23
+ # Add your domain-specific fields here
24
+ # Example:
25
+ # name: str = ""
26
+ # description: Optional[str] = None
27
+
28
+ # Audit fields
29
+ created_at: Optional[datetime] = field(default_factory=lambda: datetime.utcnow())
30
+ updated_at: Optional[datetime] = field(default_factory=lambda: datetime.utcnow())
31
+
32
+ def __post_init__(self) -> None:
33
+ """Post-initialization validation and setup."""
34
+ if self.id is None:
35
+ self.id = uuid4()
36
+
37
+ now = datetime.utcnow()
38
+ if self.created_at is None:
39
+ self.created_at = now
40
+ if self.updated_at is None:
41
+ self.updated_at = now
42
+
43
+ def update_timestamp(self) -> None:
44
+ """Update the updated_at timestamp."""
45
+ self.updated_at = datetime.utcnow()
46
+
47
+ def is_new(self) -> bool:
48
+ """Check if this is a new entity (not persisted yet)."""
49
+ return self.created_at == self.updated_at
@@ -0,0 +1,61 @@
1
+ """
2
+ {{ external_service_name }} client for {{ module_name }} module.
3
+ """
4
+ import httpx
5
+ from typing import Any, Dict, Optional
6
+ from pydantic import BaseModel
7
+
8
+
9
+ class {{ ExternalServiceName }}Config(BaseModel):
10
+ """Configuration for {{ ExternalServiceName }} client."""
11
+
12
+ base_url: str
13
+ api_key: Optional[str] = None
14
+ timeout: int = 30
15
+
16
+
17
+ class {{ ExternalServiceName }}Client:
18
+ """Client for {{ external_service_name }} external service."""
19
+
20
+ def __init__(self, config: {{ ExternalServiceName }}Config):
21
+ self._config = config
22
+ self._client = httpx.AsyncClient(
23
+ base_url=config.base_url,
24
+ timeout=config.timeout,
25
+ )
26
+
27
+ async def __aenter__(self):
28
+ return self
29
+
30
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
31
+ await self._client.aclose()
32
+
33
+ async def get_data(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
34
+ """Get data from external service."""
35
+ headers = {}
36
+ if self._config.api_key:
37
+ headers["Authorization"] = f"Bearer {self._config.api_key}"
38
+
39
+ response = await self._client.get(
40
+ endpoint,
41
+ params=params,
42
+ headers=headers,
43
+ )
44
+ response.raise_for_status()
45
+
46
+ return response.json()
47
+
48
+ async def post_data(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]:
49
+ """Post data to external service."""
50
+ headers = {"Content-Type": "application/json"}
51
+ if self._config.api_key:
52
+ headers["Authorization"] = f"Bearer {self._config.api_key}"
53
+
54
+ response = await self._client.post(
55
+ endpoint,
56
+ json=data,
57
+ headers=headers,
58
+ )
59
+ response.raise_for_status()
60
+
61
+ return response.json()
@@ -0,0 +1,69 @@
1
+ """
2
+ SQLAlchemy {{ repository_name }} repository implementation for {{ module_name }} module.
3
+ """
4
+ from typing import List, Optional
5
+ from sqlalchemy.orm import Session
6
+ from ...domain.entities.{{ entity_file }} import {{ EntityName }}
7
+ from ...domain.repositories.{{ repository_file }} import {{ RepositoryName }}Repository
8
+ from ..models.{{ model_file }} import {{ ModelName }}Model
9
+
10
+
11
+ class SQLAlchemy{{ RepositoryName }}Repository({{ RepositoryName }}Repository):
12
+ """SQLAlchemy implementation of {{ RepositoryName }}Repository."""
13
+
14
+ def __init__(self, session: Session):
15
+ self._session = session
16
+
17
+ async def get_by_id(self, id: str) -> Optional[{{ EntityName }}]:
18
+ """Retrieve {{ entity_name }} by ID."""
19
+ model = self._session.query({{ ModelName }}Model).filter(
20
+ {{ ModelName }}Model.id == id
21
+ ).first()
22
+
23
+ return model.to_entity() if model else None
24
+
25
+ async def save(self, entity: {{ EntityName }}) -> {{ EntityName }}:
26
+ """Save {{ entity_name }} entity."""
27
+ if entity.id:
28
+ # Update existing
29
+ model = self._session.query({{ ModelName }}Model).filter(
30
+ {{ ModelName }}Model.id == entity.id
31
+ ).first()
32
+
33
+ if model:
34
+ # Update model from entity
35
+ for key, value in entity.__dict__.items():
36
+ if hasattr(model, key) and key != 'id':
37
+ setattr(model, key, value)
38
+ else:
39
+ model = {{ ModelName }}Model.from_entity(entity)
40
+ self._session.add(model)
41
+ else:
42
+ # Create new
43
+ import uuid
44
+ entity.id = str(uuid.uuid4())
45
+ model = {{ ModelName }}Model.from_entity(entity)
46
+ self._session.add(model)
47
+
48
+ self._session.commit()
49
+ self._session.refresh(model)
50
+
51
+ return model.to_entity()
52
+
53
+ async def delete(self, id: str) -> bool:
54
+ """Delete {{ entity_name }} by ID."""
55
+ model = self._session.query({{ ModelName }}Model).filter(
56
+ {{ ModelName }}Model.id == id
57
+ ).first()
58
+
59
+ if model:
60
+ self._session.delete(model)
61
+ self._session.commit()
62
+ return True
63
+
64
+ return False
65
+
66
+ async def list_all(self) -> List[{{ EntityName }}]:
67
+ """List all {{ entity_name }} entities."""
68
+ models = self._session.query({{ ModelName }}Model).all()
69
+ return [model.to_entity() for model in models]
@@ -0,0 +1,38 @@
1
+ """
2
+ {{ model_name }} model for {{ module_name }} module.
3
+ """
4
+ from datetime import datetime
5
+ from typing import Optional
6
+ from sqlalchemy import Column, String, DateTime, func
7
+ from sqlalchemy.ext.declarative import declarative_base
8
+
9
+ Base = declarative_base()
10
+
11
+
12
+ class {{ ModelName }}Model(Base):
13
+ """SQLAlchemy model for {{ entity_name }}."""
14
+
15
+ __tablename__ = "{{ resource_name }}"
16
+
17
+ id = Column(String, primary_key=True)
18
+ created_at = Column(DateTime, default=func.now())
19
+ updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
20
+
21
+ def to_entity(self):
22
+ """Convert model to domain entity."""
23
+ from ...domain.entities.{{ entity_file }} import {{ EntityName }}
24
+
25
+ return {{ EntityName }}(
26
+ id=self.id,
27
+ created_at=self.created_at,
28
+ updated_at=self.updated_at,
29
+ )
30
+
31
+ @classmethod
32
+ def from_entity(cls, entity):
33
+ """Create model from domain entity."""
34
+ return cls(
35
+ id=entity.id,
36
+ created_at=entity.created_at,
37
+ updated_at=entity.updated_at,
38
+ )
@@ -0,0 +1,26 @@
1
+ """
2
+ {{ query_name }} query for {{ module_name }} module.
3
+ """
4
+ from dataclasses import dataclass
5
+ from typing import Any, List, Optional
6
+
7
+
8
+ @dataclass
9
+ class {{ QueryName }}Query:
10
+ """Query for {{ query_name }} operation."""
11
+
12
+ def __post_init__(self):
13
+ """Post-initialization validation."""
14
+ pass
15
+
16
+
17
+ class {{ QueryName }}Handler:
18
+ """Handler for {{ QueryName }}Query."""
19
+
20
+ def __init__(self):
21
+ pass
22
+
23
+ async def handle(self, query: {{ QueryName }}Query) -> Any:
24
+ """Handle the {{ query_name }} query."""
25
+ # Implementation here
26
+ pass
@@ -0,0 +1,57 @@
1
+ """{{ repository_name }} repository interface for {{ module_name }} module."""
2
+ from abc import ABC, abstractmethod
3
+ from typing import List, Optional
4
+ from ..entities.{{ entity_file }} import {{ EntityName }}
5
+
6
+
7
+ class {{ RepositoryName }}Repository(ABC):
8
+ """Abstract repository interface for {{ entity_name }} operations.
9
+
10
+ This is an abstract base class that enforces implementation of all methods
11
+ in concrete repository classes.
12
+ """
13
+
14
+ @abstractmethod
15
+ async def get_by_id(self, id: str) -> Optional[{{ EntityName }}]:
16
+ """Retrieve {{ entity_name }} by ID.
17
+
18
+ Args:
19
+ id: The unique identifier of the {{ entity_name }}
20
+
21
+ Returns:
22
+ The {{ entity_name }} entity if found, None otherwise
23
+ """
24
+ pass
25
+
26
+ @abstractmethod
27
+ async def save(self, entity: {{ EntityName }}) -> {{ EntityName }}:
28
+ """Save {{ entity_name }} entity.
29
+
30
+ Args:
31
+ entity: The {{ entity_name }} entity to save
32
+
33
+ Returns:
34
+ The saved {{ entity_name }} entity with updated fields
35
+ """
36
+ pass
37
+
38
+ @abstractmethod
39
+ async def delete(self, id: str) -> bool:
40
+ """Delete {{ entity_name }} by ID.
41
+
42
+ Args:
43
+ id: The unique identifier of the {{ entity_name }} to delete
44
+
45
+ Returns:
46
+ True if deletion was successful, False otherwise
47
+ """
48
+ pass
49
+
50
+ @abstractmethod
51
+ async def list_all(self) -> List[{{ EntityName }}]:
52
+ """List all {{ entity_name }} entities.
53
+
54
+ Returns:
55
+ List of all {{ entity_name }} entities
56
+ """
57
+ pass
@@ -0,0 +1,32 @@
1
+ """
2
+ {{ schema_name }} schemas for {{ module_name }} module.
3
+ """
4
+ from datetime import datetime
5
+ from typing import Optional
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class {{ SchemaName }}Base(BaseModel):
10
+ """Base schema for {{ entity_name }}."""
11
+ pass
12
+
13
+
14
+ class {{ SchemaName }}Create({{ SchemaName }}Base):
15
+ """Schema for creating {{ entity_name }}."""
16
+ pass
17
+
18
+
19
+ class {{ SchemaName }}Update({{ SchemaName }}Base):
20
+ """Schema for updating {{ entity_name }}."""
21
+ pass
22
+
23
+
24
+ class {{ SchemaName }}Response({{ SchemaName }}Base):
25
+ """Schema for {{ entity_name }} response."""
26
+
27
+ id: Optional[str] = None
28
+ created_at: Optional[datetime] = None
29
+ updated_at: Optional[datetime] = None
30
+
31
+ class Config:
32
+ from_attributes = True