tachyon-api 0.9.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.
- tachyon_api/__init__.py +59 -0
- tachyon_api/app.py +699 -0
- tachyon_api/background.py +72 -0
- tachyon_api/cache.py +270 -0
- tachyon_api/cli/__init__.py +9 -0
- tachyon_api/cli/__main__.py +8 -0
- tachyon_api/cli/commands/__init__.py +5 -0
- tachyon_api/cli/commands/generate.py +190 -0
- tachyon_api/cli/commands/lint.py +186 -0
- tachyon_api/cli/commands/new.py +82 -0
- tachyon_api/cli/commands/openapi.py +128 -0
- tachyon_api/cli/main.py +69 -0
- tachyon_api/cli/templates/__init__.py +8 -0
- tachyon_api/cli/templates/project.py +194 -0
- tachyon_api/cli/templates/service.py +330 -0
- tachyon_api/core/__init__.py +12 -0
- tachyon_api/core/lifecycle.py +106 -0
- tachyon_api/core/websocket.py +92 -0
- tachyon_api/di.py +86 -0
- tachyon_api/exceptions.py +39 -0
- tachyon_api/files.py +14 -0
- tachyon_api/middlewares/__init__.py +4 -0
- tachyon_api/middlewares/core.py +40 -0
- tachyon_api/middlewares/cors.py +159 -0
- tachyon_api/middlewares/logger.py +123 -0
- tachyon_api/models.py +73 -0
- tachyon_api/openapi.py +419 -0
- tachyon_api/params.py +268 -0
- tachyon_api/processing/__init__.py +14 -0
- tachyon_api/processing/dependencies.py +172 -0
- tachyon_api/processing/parameters.py +484 -0
- tachyon_api/processing/response_processor.py +93 -0
- tachyon_api/responses.py +92 -0
- tachyon_api/router.py +161 -0
- tachyon_api/security.py +295 -0
- tachyon_api/testing.py +110 -0
- tachyon_api/utils/__init__.py +15 -0
- tachyon_api/utils/type_converter.py +113 -0
- tachyon_api/utils/type_utils.py +162 -0
- tachyon_api-0.9.0.dist-info/METADATA +291 -0
- tachyon_api-0.9.0.dist-info/RECORD +44 -0
- tachyon_api-0.9.0.dist-info/WHEEL +4 -0
- tachyon_api-0.9.0.dist-info/entry_points.txt +3 -0
- tachyon_api-0.9.0.dist-info/licenses/LICENSE +17 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tachyon Background Tasks Module
|
|
3
|
+
|
|
4
|
+
Provides background task functionality for running tasks after response is sent.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
from typing import Any, Callable, List, Tuple
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BackgroundTasks:
|
|
12
|
+
"""
|
|
13
|
+
Background tasks that run after the response has been sent.
|
|
14
|
+
|
|
15
|
+
Tasks are executed in order after the response is complete.
|
|
16
|
+
Errors in tasks are caught and logged but don't affect the response.
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
@app.get("/send-notification")
|
|
20
|
+
def send_notification(background_tasks: BackgroundTasks):
|
|
21
|
+
background_tasks.add_task(send_email, "user@example.com", "Hello!")
|
|
22
|
+
return {"message": "Notification scheduled"}
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self._tasks: List[Tuple[Callable, tuple, dict]] = []
|
|
27
|
+
|
|
28
|
+
def add_task(
|
|
29
|
+
self,
|
|
30
|
+
func: Callable[..., Any],
|
|
31
|
+
*args: Any,
|
|
32
|
+
**kwargs: Any,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Add a task to be run in the background.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
func: The function to call. Can be sync or async.
|
|
39
|
+
*args: Positional arguments to pass to the function.
|
|
40
|
+
**kwargs: Keyword arguments to pass to the function.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
background_tasks.add_task(write_log, "User logged in")
|
|
44
|
+
background_tasks.add_task(send_email, to="user@example.com", subject="Hi")
|
|
45
|
+
"""
|
|
46
|
+
self._tasks.append((func, args, kwargs))
|
|
47
|
+
|
|
48
|
+
async def run_tasks(self) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Execute all queued background tasks.
|
|
51
|
+
|
|
52
|
+
This method is called automatically after the response is sent.
|
|
53
|
+
Each task is run in order, and errors are caught to prevent
|
|
54
|
+
one failing task from stopping the others.
|
|
55
|
+
"""
|
|
56
|
+
for func, args, kwargs in self._tasks:
|
|
57
|
+
try:
|
|
58
|
+
result = func(*args, **kwargs)
|
|
59
|
+
if asyncio.iscoroutine(result):
|
|
60
|
+
await result
|
|
61
|
+
except Exception:
|
|
62
|
+
# Log error but continue with other tasks
|
|
63
|
+
# In production, you'd want proper logging here
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
def __len__(self) -> int:
|
|
67
|
+
"""Return the number of pending tasks."""
|
|
68
|
+
return len(self._tasks)
|
|
69
|
+
|
|
70
|
+
def __bool__(self) -> bool:
|
|
71
|
+
"""BackgroundTasks is always truthy (to allow `if background_tasks:`)."""
|
|
72
|
+
return True
|
tachyon_api/cache.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cache utilities: decorator with TTL and pluggable backends.
|
|
3
|
+
|
|
4
|
+
This module provides:
|
|
5
|
+
- BaseCacheBackend protocol and an in-memory implementation
|
|
6
|
+
- CacheConfig dataclass and helpers to set global/app config
|
|
7
|
+
- cache decorator usable on any sync/async function (including routes)
|
|
8
|
+
|
|
9
|
+
Design notes:
|
|
10
|
+
- Key builder defaults to a stable representation of function + args/kwargs
|
|
11
|
+
- Unless predicate allows opt-out per-call
|
|
12
|
+
- TTL can be provided per-decorator or falls back to global default
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import time
|
|
18
|
+
import asyncio
|
|
19
|
+
import hashlib
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from functools import wraps
|
|
22
|
+
from typing import Any, Callable, Optional, Tuple
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BaseCacheBackend:
|
|
26
|
+
"""Minimal cache backend interface."""
|
|
27
|
+
|
|
28
|
+
def get(self, key: str) -> Any: # pragma: no cover - interface
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
|
|
31
|
+
def set(
|
|
32
|
+
self, key: str, value: Any, ttl: Optional[float] = None
|
|
33
|
+
) -> None: # pragma: no cover - interface
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
def delete(self, key: str) -> None: # pragma: no cover - interface
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
|
|
39
|
+
def clear(self) -> None: # pragma: no cover - interface
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class InMemoryCacheBackend(BaseCacheBackend):
|
|
44
|
+
"""Simple in-memory cache with TTL using wall-clock time."""
|
|
45
|
+
|
|
46
|
+
def __init__(self) -> None:
|
|
47
|
+
self._store: dict[str, Tuple[float | None, Any]] = {}
|
|
48
|
+
|
|
49
|
+
def _is_expired(self, expires_at: float | None) -> bool:
|
|
50
|
+
return expires_at is not None and time.time() >= expires_at
|
|
51
|
+
|
|
52
|
+
def get(self, key: str) -> Any:
|
|
53
|
+
item = self._store.get(key)
|
|
54
|
+
if not item:
|
|
55
|
+
return None
|
|
56
|
+
expires_at, value = item
|
|
57
|
+
if self._is_expired(expires_at):
|
|
58
|
+
# Lazy expiration
|
|
59
|
+
try:
|
|
60
|
+
del self._store[key]
|
|
61
|
+
except KeyError:
|
|
62
|
+
pass
|
|
63
|
+
return None
|
|
64
|
+
return value
|
|
65
|
+
|
|
66
|
+
def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
|
|
67
|
+
expires_at = time.time() + ttl if ttl and ttl > 0 else None
|
|
68
|
+
self._store[key] = (expires_at, value)
|
|
69
|
+
|
|
70
|
+
def delete(self, key: str) -> None:
|
|
71
|
+
self._store.pop(key, None)
|
|
72
|
+
|
|
73
|
+
def clear(self) -> None:
|
|
74
|
+
self._store.clear()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class CacheConfig:
|
|
79
|
+
backend: BaseCacheBackend
|
|
80
|
+
default_ttl: float = 60.0
|
|
81
|
+
key_builder: Optional[Callable[[Callable, tuple, dict], str]] = None
|
|
82
|
+
enabled: bool = True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
_cache_config: Optional[CacheConfig] = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _default_key_builder(func: Callable, args: tuple, kwargs: dict) -> str:
|
|
89
|
+
parts = [getattr(func, "__module__", ""), getattr(func, "__qualname__", ""), "|"]
|
|
90
|
+
# Stable kwargs order
|
|
91
|
+
items = [repr(a) for a in args] + [
|
|
92
|
+
f"{k}={repr(v)}" for k, v in sorted(kwargs.items())
|
|
93
|
+
]
|
|
94
|
+
raw_key = ":".join(parts + items)
|
|
95
|
+
return hashlib.sha256(raw_key.encode("utf-8")).hexdigest()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def create_cache_config(
|
|
99
|
+
backend: Optional[BaseCacheBackend] = None,
|
|
100
|
+
default_ttl: float = 60.0,
|
|
101
|
+
key_builder: Optional[Callable[[Callable, tuple, dict], str]] = None,
|
|
102
|
+
enabled: bool = True,
|
|
103
|
+
) -> CacheConfig:
|
|
104
|
+
"""Create and set the global cache configuration.
|
|
105
|
+
|
|
106
|
+
Returns the created CacheConfig and sets it as the active global config.
|
|
107
|
+
"""
|
|
108
|
+
global _cache_config
|
|
109
|
+
cfg = CacheConfig(
|
|
110
|
+
backend=backend or InMemoryCacheBackend(),
|
|
111
|
+
default_ttl=default_ttl,
|
|
112
|
+
key_builder=key_builder,
|
|
113
|
+
enabled=enabled,
|
|
114
|
+
)
|
|
115
|
+
_cache_config = cfg
|
|
116
|
+
return cfg
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def set_cache_config(config: CacheConfig) -> None:
|
|
120
|
+
"""Set the global cache configuration object."""
|
|
121
|
+
global _cache_config
|
|
122
|
+
_cache_config = config
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_cache_config() -> CacheConfig:
|
|
126
|
+
"""Get the current cache configuration, creating a default one if missing."""
|
|
127
|
+
global _cache_config
|
|
128
|
+
if _cache_config is None:
|
|
129
|
+
_cache_config = create_cache_config()
|
|
130
|
+
return _cache_config
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def cache(
|
|
134
|
+
TTL: Optional[float] = None,
|
|
135
|
+
*,
|
|
136
|
+
key_builder: Optional[Callable[[Callable, tuple, dict], str]] = None,
|
|
137
|
+
unless: Optional[Callable[[tuple, dict], bool]] = None,
|
|
138
|
+
backend: Optional[BaseCacheBackend] = None,
|
|
139
|
+
):
|
|
140
|
+
"""Cache decorator with TTL and pluggable backend.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
TTL: Time-to-live in seconds for this decorator instance. Falls back to config.default_ttl.
|
|
144
|
+
key_builder: Optional custom function to build cache keys.
|
|
145
|
+
unless: Predicate receiving (args, kwargs). If returns True, skip cache for that call.
|
|
146
|
+
backend: Optional backend override for this decorator.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def decorator(func: Callable):
|
|
150
|
+
cfg = get_cache_config()
|
|
151
|
+
be = backend or cfg.backend
|
|
152
|
+
ttl_value = cfg.default_ttl if TTL is None else TTL
|
|
153
|
+
kb = (
|
|
154
|
+
key_builder
|
|
155
|
+
or cfg.key_builder
|
|
156
|
+
or (lambda f, a, kw: _default_key_builder(f, a, kw))
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if asyncio.iscoroutinefunction(func):
|
|
160
|
+
|
|
161
|
+
@wraps(func)
|
|
162
|
+
async def async_wrapper(*args, **kwargs):
|
|
163
|
+
if not cfg.enabled or (unless and unless(args, kwargs)):
|
|
164
|
+
return await func(*args, **kwargs)
|
|
165
|
+
key = kb(func, args, kwargs)
|
|
166
|
+
cached = be.get(key)
|
|
167
|
+
if cached is not None:
|
|
168
|
+
return cached
|
|
169
|
+
result = await func(*args, **kwargs)
|
|
170
|
+
try:
|
|
171
|
+
be.set(key, result, ttl_value)
|
|
172
|
+
except Exception:
|
|
173
|
+
# Backend errors should not break the app
|
|
174
|
+
pass
|
|
175
|
+
return result
|
|
176
|
+
|
|
177
|
+
return async_wrapper
|
|
178
|
+
else:
|
|
179
|
+
|
|
180
|
+
@wraps(func)
|
|
181
|
+
def wrapper(*args, **kwargs):
|
|
182
|
+
if not cfg.enabled or (unless and unless(args, kwargs)):
|
|
183
|
+
return func(*args, **kwargs)
|
|
184
|
+
key = kb(func, args, kwargs)
|
|
185
|
+
cached = be.get(key)
|
|
186
|
+
if cached is not None:
|
|
187
|
+
return cached
|
|
188
|
+
result = func(*args, **kwargs)
|
|
189
|
+
try:
|
|
190
|
+
be.set(key, result, ttl_value)
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
return wrapper
|
|
196
|
+
|
|
197
|
+
return decorator
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class RedisCacheBackend(BaseCacheBackend):
|
|
201
|
+
"""Adapter backend for Redis-like clients.
|
|
202
|
+
|
|
203
|
+
Expects a client with .get(key) -> bytes|str|None and .set(key, value, ex=ttl_seconds).
|
|
204
|
+
The stored values should be JSON-serializable or pickled externally by the user.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
def __init__(self, client) -> None:
|
|
208
|
+
self.client = client
|
|
209
|
+
|
|
210
|
+
def get(self, key: str) -> Any:
|
|
211
|
+
value = self.client.get(key)
|
|
212
|
+
# Many clients return bytes; decode if possible
|
|
213
|
+
if isinstance(value, bytes):
|
|
214
|
+
try:
|
|
215
|
+
return value.decode("utf-8")
|
|
216
|
+
except Exception:
|
|
217
|
+
return value
|
|
218
|
+
return value
|
|
219
|
+
|
|
220
|
+
def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
|
|
221
|
+
# Use ex (expire seconds) if available
|
|
222
|
+
kwargs = {}
|
|
223
|
+
if ttl and ttl > 0:
|
|
224
|
+
kwargs["ex"] = int(ttl)
|
|
225
|
+
# Best effort set
|
|
226
|
+
self.client.set(key, value, **kwargs)
|
|
227
|
+
|
|
228
|
+
def delete(self, key: str) -> None:
|
|
229
|
+
try:
|
|
230
|
+
self.client.delete(key)
|
|
231
|
+
except Exception:
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
def clear(self) -> None:
|
|
235
|
+
# Not standardized; no-op by default
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class MemcachedCacheBackend(BaseCacheBackend):
|
|
240
|
+
"""Adapter backend for Memcached-like clients.
|
|
241
|
+
|
|
242
|
+
Expects a client with .get(key) and .set(key, value, expire=ttl_seconds).
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
def __init__(self, client) -> None:
|
|
246
|
+
self.client = client
|
|
247
|
+
|
|
248
|
+
def get(self, key: str) -> Any:
|
|
249
|
+
return self.client.get(key)
|
|
250
|
+
|
|
251
|
+
def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
|
|
252
|
+
expire = int(ttl) if ttl and ttl > 0 else 0
|
|
253
|
+
try:
|
|
254
|
+
# pymemcache: set(key, value, expire=...)
|
|
255
|
+
self.client.set(key, value, expire=expire)
|
|
256
|
+
except TypeError:
|
|
257
|
+
# python-binary-memcached: set(key, value, time=...)
|
|
258
|
+
self.client.set(key, value, time=expire)
|
|
259
|
+
|
|
260
|
+
def delete(self, key: str) -> None:
|
|
261
|
+
try:
|
|
262
|
+
self.client.delete(key)
|
|
263
|
+
except Exception:
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
def clear(self) -> None:
|
|
267
|
+
try:
|
|
268
|
+
self.client.flush_all()
|
|
269
|
+
except Exception:
|
|
270
|
+
pass
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tachyon generate - Generate components (service, controller, repository, dto)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from ..templates import ServiceTemplates
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _to_class_name(name: str) -> str:
|
|
15
|
+
"""Convert snake_case or kebab-case to PascalCase."""
|
|
16
|
+
return "".join(word.capitalize() for word in name.replace("-", "_").split("_"))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _to_snake_case(name: str) -> str:
|
|
20
|
+
"""Ensure name is in snake_case."""
|
|
21
|
+
return name.replace("-", "_").lower()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _create_file(path: Path, content: str, name: str):
|
|
25
|
+
"""Create a file and print status."""
|
|
26
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
path.write_text(content)
|
|
28
|
+
typer.echo(f" š Created {name}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command()
|
|
32
|
+
def service(
|
|
33
|
+
name: str = typer.Argument(..., help="Service name (e.g., 'auth', 'users')"),
|
|
34
|
+
path: Optional[Path] = typer.Option(
|
|
35
|
+
None, "--path", "-p", help="Base path for modules (default: ./modules)"
|
|
36
|
+
),
|
|
37
|
+
no_tests: bool = typer.Option(
|
|
38
|
+
False, "--no-tests", help="Skip test file generation"
|
|
39
|
+
),
|
|
40
|
+
crud: bool = typer.Option(
|
|
41
|
+
False, "--crud", help="Generate with basic CRUD operations"
|
|
42
|
+
),
|
|
43
|
+
):
|
|
44
|
+
"""
|
|
45
|
+
š§ Generate a complete service module.
|
|
46
|
+
|
|
47
|
+
Creates: controller, service, repository, dto, and tests.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
tachyon g service auth
|
|
51
|
+
tachyon g service products --crud
|
|
52
|
+
tachyon g service users --path src/modules
|
|
53
|
+
"""
|
|
54
|
+
snake_name = _to_snake_case(name)
|
|
55
|
+
class_name = _to_class_name(name)
|
|
56
|
+
|
|
57
|
+
base_path = path or Path.cwd() / "modules"
|
|
58
|
+
service_path = base_path / snake_name
|
|
59
|
+
|
|
60
|
+
if service_path.exists():
|
|
61
|
+
typer.secho(f"ā Module '{snake_name}' already exists!", fg=typer.colors.RED)
|
|
62
|
+
raise typer.Exit(1)
|
|
63
|
+
|
|
64
|
+
typer.echo(f"\nš§ Generating service: {typer.style(snake_name, bold=True)}\n")
|
|
65
|
+
|
|
66
|
+
# Create directory
|
|
67
|
+
service_path.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
tests_path = service_path / "tests"
|
|
69
|
+
tests_path.mkdir(exist_ok=True)
|
|
70
|
+
|
|
71
|
+
# Generate files
|
|
72
|
+
files = {
|
|
73
|
+
"__init__.py": ServiceTemplates.init(snake_name, class_name),
|
|
74
|
+
f"{snake_name}_controller.py": ServiceTemplates.controller(
|
|
75
|
+
snake_name, class_name, crud
|
|
76
|
+
),
|
|
77
|
+
f"{snake_name}_service.py": ServiceTemplates.service(
|
|
78
|
+
snake_name, class_name, crud
|
|
79
|
+
),
|
|
80
|
+
f"{snake_name}_repository.py": ServiceTemplates.repository(
|
|
81
|
+
snake_name, class_name, crud
|
|
82
|
+
),
|
|
83
|
+
f"{snake_name}_dto.py": ServiceTemplates.dto(snake_name, class_name, crud),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for filename, content in files.items():
|
|
87
|
+
_create_file(service_path / filename, content, filename)
|
|
88
|
+
|
|
89
|
+
# Generate tests
|
|
90
|
+
if not no_tests:
|
|
91
|
+
_create_file(tests_path / "__init__.py", "", "tests/__init__.py")
|
|
92
|
+
_create_file(
|
|
93
|
+
tests_path / f"test_{snake_name}_service.py",
|
|
94
|
+
ServiceTemplates.test_service(snake_name, class_name),
|
|
95
|
+
f"tests/test_{snake_name}_service.py",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
typer.echo(
|
|
99
|
+
f"\nā
Service {typer.style(snake_name, bold=True, fg=typer.colors.GREEN)} generated!"
|
|
100
|
+
)
|
|
101
|
+
typer.echo("\nš Don't forget to register in app.py:")
|
|
102
|
+
typer.echo(f" from modules.{snake_name} import router as {snake_name}_router")
|
|
103
|
+
typer.echo(f" app.include_router({snake_name}_router)")
|
|
104
|
+
typer.echo()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@app.command()
|
|
108
|
+
def controller(
|
|
109
|
+
name: str = typer.Argument(..., help="Controller name"),
|
|
110
|
+
path: Optional[Path] = typer.Option(None, "--path", "-p"),
|
|
111
|
+
):
|
|
112
|
+
"""
|
|
113
|
+
š” Generate a controller (router) file.
|
|
114
|
+
|
|
115
|
+
Example:
|
|
116
|
+
tachyon g controller users
|
|
117
|
+
"""
|
|
118
|
+
snake_name = _to_snake_case(name)
|
|
119
|
+
class_name = _to_class_name(name)
|
|
120
|
+
|
|
121
|
+
base_path = path or Path.cwd() / "modules" / snake_name
|
|
122
|
+
base_path.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
|
|
124
|
+
typer.echo(f"\nš” Generating controller: {snake_name}\n")
|
|
125
|
+
|
|
126
|
+
_create_file(
|
|
127
|
+
base_path / f"{snake_name}_controller.py",
|
|
128
|
+
ServiceTemplates.controller(snake_name, class_name, False),
|
|
129
|
+
f"{snake_name}_controller.py",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
typer.echo("\nā
Controller generated!")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@app.command("repo")
|
|
136
|
+
@app.command("repository")
|
|
137
|
+
def repository(
|
|
138
|
+
name: str = typer.Argument(..., help="Repository name"),
|
|
139
|
+
path: Optional[Path] = typer.Option(None, "--path", "-p"),
|
|
140
|
+
):
|
|
141
|
+
"""
|
|
142
|
+
šļø Generate a repository file.
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
tachyon g repository users
|
|
146
|
+
tachyon g repo products
|
|
147
|
+
"""
|
|
148
|
+
snake_name = _to_snake_case(name)
|
|
149
|
+
class_name = _to_class_name(name)
|
|
150
|
+
|
|
151
|
+
base_path = path or Path.cwd() / "modules" / snake_name
|
|
152
|
+
base_path.mkdir(parents=True, exist_ok=True)
|
|
153
|
+
|
|
154
|
+
typer.echo(f"\nšļø Generating repository: {snake_name}\n")
|
|
155
|
+
|
|
156
|
+
_create_file(
|
|
157
|
+
base_path / f"{snake_name}_repository.py",
|
|
158
|
+
ServiceTemplates.repository(snake_name, class_name, False),
|
|
159
|
+
f"{snake_name}_repository.py",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
typer.echo("\nā
Repository generated!")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@app.command()
|
|
166
|
+
def dto(
|
|
167
|
+
name: str = typer.Argument(..., help="DTO name"),
|
|
168
|
+
path: Optional[Path] = typer.Option(None, "--path", "-p"),
|
|
169
|
+
):
|
|
170
|
+
"""
|
|
171
|
+
š¦ Generate a DTO (Data Transfer Object) file.
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
tachyon g dto users
|
|
175
|
+
"""
|
|
176
|
+
snake_name = _to_snake_case(name)
|
|
177
|
+
class_name = _to_class_name(name)
|
|
178
|
+
|
|
179
|
+
base_path = path or Path.cwd() / "modules" / snake_name
|
|
180
|
+
base_path.mkdir(parents=True, exist_ok=True)
|
|
181
|
+
|
|
182
|
+
typer.echo(f"\nš¦ Generating DTO: {snake_name}\n")
|
|
183
|
+
|
|
184
|
+
_create_file(
|
|
185
|
+
base_path / f"{snake_name}_dto.py",
|
|
186
|
+
ServiceTemplates.dto(snake_name, class_name, False),
|
|
187
|
+
f"{snake_name}_dto.py",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
typer.echo("\nā
DTO generated!")
|