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,186 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tachyon lint - Code quality tools (ruff wrapper)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
import subprocess
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional, List
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _check_ruff_installed() -> bool:
|
|
15
|
+
"""Check if ruff is installed."""
|
|
16
|
+
return shutil.which("ruff") is not None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _run_ruff(args: List[str], check: bool = True) -> int:
|
|
20
|
+
"""Run ruff with given arguments."""
|
|
21
|
+
if not _check_ruff_installed():
|
|
22
|
+
typer.secho(
|
|
23
|
+
"ā ruff is not installed. Install it with: pip install ruff",
|
|
24
|
+
fg=typer.colors.RED,
|
|
25
|
+
)
|
|
26
|
+
raise typer.Exit(1)
|
|
27
|
+
|
|
28
|
+
cmd = ["ruff"] + args
|
|
29
|
+
result = subprocess.run(cmd)
|
|
30
|
+
return result.returncode
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command()
|
|
34
|
+
def check(
|
|
35
|
+
path: Optional[Path] = typer.Argument(
|
|
36
|
+
None, help="Path to check (default: current directory)"
|
|
37
|
+
),
|
|
38
|
+
fix: bool = typer.Option(
|
|
39
|
+
False, "--fix", "-f", help="Automatically fix issues where possible"
|
|
40
|
+
),
|
|
41
|
+
watch: bool = typer.Option(
|
|
42
|
+
False, "--watch", "-w", help="Watch for changes and re-run"
|
|
43
|
+
),
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
š Check code for linting issues.
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
tachyon lint check
|
|
50
|
+
tachyon lint check --fix
|
|
51
|
+
tachyon lint check ./src --watch
|
|
52
|
+
"""
|
|
53
|
+
target = str(path) if path else "."
|
|
54
|
+
|
|
55
|
+
args = ["check", target]
|
|
56
|
+
|
|
57
|
+
if fix:
|
|
58
|
+
args.append("--fix")
|
|
59
|
+
|
|
60
|
+
if watch:
|
|
61
|
+
args.append("--watch")
|
|
62
|
+
|
|
63
|
+
typer.echo(f"š Checking: {target}\n")
|
|
64
|
+
exit_code = _run_ruff(args)
|
|
65
|
+
|
|
66
|
+
if exit_code == 0:
|
|
67
|
+
typer.secho("\nā
No issues found!", fg=typer.colors.GREEN)
|
|
68
|
+
|
|
69
|
+
raise typer.Exit(exit_code)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@app.command()
|
|
73
|
+
def fix(
|
|
74
|
+
path: Optional[Path] = typer.Argument(
|
|
75
|
+
None, help="Path to fix (default: current directory)"
|
|
76
|
+
),
|
|
77
|
+
unsafe: bool = typer.Option(False, "--unsafe", help="Apply unsafe fixes as well"),
|
|
78
|
+
):
|
|
79
|
+
"""
|
|
80
|
+
š§ Automatically fix linting issues.
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
tachyon lint fix
|
|
84
|
+
tachyon lint fix ./src
|
|
85
|
+
tachyon lint fix --unsafe
|
|
86
|
+
"""
|
|
87
|
+
target = str(path) if path else "."
|
|
88
|
+
|
|
89
|
+
args = ["check", target, "--fix"]
|
|
90
|
+
|
|
91
|
+
if unsafe:
|
|
92
|
+
args.append("--unsafe-fixes")
|
|
93
|
+
|
|
94
|
+
typer.echo(f"š§ Fixing: {target}\n")
|
|
95
|
+
exit_code = _run_ruff(args)
|
|
96
|
+
|
|
97
|
+
if exit_code == 0:
|
|
98
|
+
typer.secho("\nā
All fixable issues resolved!", fg=typer.colors.GREEN)
|
|
99
|
+
|
|
100
|
+
raise typer.Exit(exit_code)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@app.command()
|
|
104
|
+
def format(
|
|
105
|
+
path: Optional[Path] = typer.Argument(
|
|
106
|
+
None, help="Path to format (default: current directory)"
|
|
107
|
+
),
|
|
108
|
+
check_only: bool = typer.Option(
|
|
109
|
+
False, "--check", help="Check formatting without making changes"
|
|
110
|
+
),
|
|
111
|
+
diff: bool = typer.Option(False, "--diff", help="Show diff of formatting changes"),
|
|
112
|
+
):
|
|
113
|
+
"""
|
|
114
|
+
šØ Format code using ruff formatter.
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
tachyon lint format
|
|
118
|
+
tachyon lint format --check
|
|
119
|
+
tachyon lint format --diff
|
|
120
|
+
"""
|
|
121
|
+
target = str(path) if path else "."
|
|
122
|
+
|
|
123
|
+
args = ["format", target]
|
|
124
|
+
|
|
125
|
+
if check_only:
|
|
126
|
+
args.append("--check")
|
|
127
|
+
|
|
128
|
+
if diff:
|
|
129
|
+
args.append("--diff")
|
|
130
|
+
|
|
131
|
+
typer.echo(f"šØ Formatting: {target}\n")
|
|
132
|
+
exit_code = _run_ruff(args)
|
|
133
|
+
|
|
134
|
+
if exit_code == 0 and not check_only:
|
|
135
|
+
typer.secho("\nā
Formatting complete!", fg=typer.colors.GREEN)
|
|
136
|
+
elif exit_code == 0 and check_only:
|
|
137
|
+
typer.secho("\nā
Code is properly formatted!", fg=typer.colors.GREEN)
|
|
138
|
+
|
|
139
|
+
raise typer.Exit(exit_code)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@app.command()
|
|
143
|
+
def all(
|
|
144
|
+
path: Optional[Path] = typer.Argument(
|
|
145
|
+
None, help="Path to check and format (default: current directory)"
|
|
146
|
+
),
|
|
147
|
+
fix: bool = typer.Option(True, "--fix/--no-fix", help="Auto-fix issues"),
|
|
148
|
+
):
|
|
149
|
+
"""
|
|
150
|
+
š Run all quality checks: lint + format.
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
tachyon lint all
|
|
154
|
+
tachyon lint all --no-fix
|
|
155
|
+
"""
|
|
156
|
+
target = str(path) if path else "."
|
|
157
|
+
|
|
158
|
+
typer.echo(f"š Running all quality checks on: {target}\n")
|
|
159
|
+
|
|
160
|
+
# Run linting
|
|
161
|
+
typer.echo("ā" * 40)
|
|
162
|
+
typer.echo("š Step 1: Linting")
|
|
163
|
+
typer.echo("ā" * 40)
|
|
164
|
+
|
|
165
|
+
lint_args = ["check", target]
|
|
166
|
+
if fix:
|
|
167
|
+
lint_args.append("--fix")
|
|
168
|
+
|
|
169
|
+
lint_code = _run_ruff(lint_args)
|
|
170
|
+
|
|
171
|
+
# Run formatting
|
|
172
|
+
typer.echo("\n" + "ā" * 40)
|
|
173
|
+
typer.echo("šØ Step 2: Formatting")
|
|
174
|
+
typer.echo("ā" * 40)
|
|
175
|
+
|
|
176
|
+
format_args = ["format", target]
|
|
177
|
+
format_code = _run_ruff(format_args)
|
|
178
|
+
|
|
179
|
+
# Summary
|
|
180
|
+
typer.echo("\n" + "ā" * 40)
|
|
181
|
+
if lint_code == 0 and format_code == 0:
|
|
182
|
+
typer.secho("ā
All checks passed!", fg=typer.colors.GREEN, bold=True)
|
|
183
|
+
else:
|
|
184
|
+
typer.secho("ā ļø Some issues found", fg=typer.colors.YELLOW, bold=True)
|
|
185
|
+
|
|
186
|
+
raise typer.Exit(max(lint_code, format_code))
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tachyon new - Create new project with clean architecture
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from ..templates import ProjectTemplates
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def create_project(name: str, parent_path: Optional[Path] = None):
|
|
13
|
+
"""
|
|
14
|
+
Create a new Tachyon project with clean architecture structure.
|
|
15
|
+
|
|
16
|
+
Structure:
|
|
17
|
+
my-api/
|
|
18
|
+
āāā app.py
|
|
19
|
+
āāā config.py
|
|
20
|
+
āāā requirements.txt
|
|
21
|
+
āāā modules/
|
|
22
|
+
ā āāā __init__.py
|
|
23
|
+
āāā shared/
|
|
24
|
+
ā āāā __init__.py
|
|
25
|
+
ā āāā exceptions.py
|
|
26
|
+
ā āāā dependencies.py
|
|
27
|
+
āāā tests/
|
|
28
|
+
āāā __init__.py
|
|
29
|
+
āāā conftest.py
|
|
30
|
+
"""
|
|
31
|
+
# Determine project path
|
|
32
|
+
base_path = parent_path or Path.cwd()
|
|
33
|
+
project_path = base_path / name
|
|
34
|
+
|
|
35
|
+
# Check if already exists
|
|
36
|
+
if project_path.exists():
|
|
37
|
+
typer.secho(f"ā Directory '{name}' already exists!", fg=typer.colors.RED)
|
|
38
|
+
raise typer.Exit(1)
|
|
39
|
+
|
|
40
|
+
typer.echo(f"\nš Creating Tachyon project: {typer.style(name, bold=True)}\n")
|
|
41
|
+
|
|
42
|
+
# Create directory structure
|
|
43
|
+
directories = [
|
|
44
|
+
project_path,
|
|
45
|
+
project_path / "modules",
|
|
46
|
+
project_path / "shared",
|
|
47
|
+
project_path / "tests",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
for directory in directories:
|
|
51
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
typer.echo(f" š Created {directory.relative_to(base_path)}/")
|
|
53
|
+
|
|
54
|
+
# Create files
|
|
55
|
+
files = {
|
|
56
|
+
"app.py": ProjectTemplates.APP,
|
|
57
|
+
"config.py": ProjectTemplates.CONFIG,
|
|
58
|
+
"requirements.txt": ProjectTemplates.REQUIREMENTS,
|
|
59
|
+
"modules/__init__.py": ProjectTemplates.MODULES_INIT,
|
|
60
|
+
"shared/__init__.py": ProjectTemplates.SHARED_INIT,
|
|
61
|
+
"shared/exceptions.py": ProjectTemplates.SHARED_EXCEPTIONS,
|
|
62
|
+
"shared/dependencies.py": ProjectTemplates.SHARED_DEPENDENCIES,
|
|
63
|
+
"tests/__init__.py": "",
|
|
64
|
+
"tests/conftest.py": ProjectTemplates.TESTS_CONFTEST,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for file_path, content in files.items():
|
|
68
|
+
full_path = project_path / file_path
|
|
69
|
+
full_path.write_text(content)
|
|
70
|
+
typer.echo(f" š Created {file_path}")
|
|
71
|
+
|
|
72
|
+
# Success message
|
|
73
|
+
typer.echo(
|
|
74
|
+
f"\nā
Project {typer.style(name, bold=True, fg=typer.colors.GREEN)} created successfully!"
|
|
75
|
+
)
|
|
76
|
+
typer.echo("\nš Next steps:")
|
|
77
|
+
typer.echo(f" cd {name}")
|
|
78
|
+
typer.echo(" pip install -r requirements.txt")
|
|
79
|
+
typer.echo(" python app.py")
|
|
80
|
+
typer.echo("\n Then generate your first service:")
|
|
81
|
+
typer.echo(" tachyon g service users")
|
|
82
|
+
typer.echo()
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tachyon openapi - OpenAPI schema utilities
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
import json
|
|
7
|
+
import importlib
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _load_app(app_path: str):
|
|
15
|
+
"""
|
|
16
|
+
Load a Tachyon app from module:attribute format.
|
|
17
|
+
|
|
18
|
+
Example: "app:app" or "main:application"
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
module_path, attr_name = app_path.split(":")
|
|
22
|
+
except ValueError:
|
|
23
|
+
typer.secho(
|
|
24
|
+
"ā Invalid app path format. Use 'module:attribute' (e.g., 'app:app')",
|
|
25
|
+
fg=typer.colors.RED,
|
|
26
|
+
)
|
|
27
|
+
raise typer.Exit(1)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
module = importlib.import_module(module_path)
|
|
31
|
+
app_instance = getattr(module, attr_name)
|
|
32
|
+
return app_instance
|
|
33
|
+
except ModuleNotFoundError as e:
|
|
34
|
+
typer.secho(f"ā Module not found: {module_path}", fg=typer.colors.RED)
|
|
35
|
+
typer.secho(f" Error: {e}", fg=typer.colors.YELLOW)
|
|
36
|
+
raise typer.Exit(1)
|
|
37
|
+
except AttributeError:
|
|
38
|
+
typer.secho(
|
|
39
|
+
f"ā Attribute '{attr_name}' not found in module '{module_path}'",
|
|
40
|
+
fg=typer.colors.RED,
|
|
41
|
+
)
|
|
42
|
+
raise typer.Exit(1)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command()
|
|
46
|
+
def export(
|
|
47
|
+
app_path: str = typer.Argument(
|
|
48
|
+
..., help="App path in format 'module:attribute' (e.g., 'app:app')"
|
|
49
|
+
),
|
|
50
|
+
output: Optional[Path] = typer.Option(
|
|
51
|
+
None, "--output", "-o", help="Output file path (default: stdout)"
|
|
52
|
+
),
|
|
53
|
+
indent: int = typer.Option(2, "--indent", "-i", help="JSON indentation level"),
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
š Export OpenAPI schema to JSON.
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
tachyon openapi export app:app
|
|
60
|
+
tachyon openapi export app:app -o openapi.json
|
|
61
|
+
tachyon openapi export app:app | jq .
|
|
62
|
+
"""
|
|
63
|
+
# Add current directory to path for imports
|
|
64
|
+
import sys
|
|
65
|
+
|
|
66
|
+
sys.path.insert(0, str(Path.cwd()))
|
|
67
|
+
|
|
68
|
+
typer.echo(f"š Loading app from: {app_path}", err=True)
|
|
69
|
+
|
|
70
|
+
app_instance = _load_app(app_path)
|
|
71
|
+
|
|
72
|
+
# Get OpenAPI schema
|
|
73
|
+
try:
|
|
74
|
+
schema = app_instance.openapi_generator.get_openapi_schema()
|
|
75
|
+
except AttributeError:
|
|
76
|
+
typer.secho(
|
|
77
|
+
"ā The loaded object doesn't appear to be a Tachyon app",
|
|
78
|
+
fg=typer.colors.RED,
|
|
79
|
+
)
|
|
80
|
+
raise typer.Exit(1)
|
|
81
|
+
|
|
82
|
+
# Convert to JSON
|
|
83
|
+
json_output = json.dumps(schema, indent=indent, ensure_ascii=False)
|
|
84
|
+
|
|
85
|
+
if output:
|
|
86
|
+
output.write_text(json_output)
|
|
87
|
+
typer.echo(f"ā
Schema exported to: {output}", err=True)
|
|
88
|
+
else:
|
|
89
|
+
typer.echo(json_output)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.command()
|
|
93
|
+
def validate(
|
|
94
|
+
schema_path: Path = typer.Argument(..., help="Path to OpenAPI schema file"),
|
|
95
|
+
):
|
|
96
|
+
"""
|
|
97
|
+
ā
Validate an OpenAPI schema file.
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
tachyon openapi validate openapi.json
|
|
101
|
+
"""
|
|
102
|
+
if not schema_path.exists():
|
|
103
|
+
typer.secho(f"ā File not found: {schema_path}", fg=typer.colors.RED)
|
|
104
|
+
raise typer.Exit(1)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
content = schema_path.read_text()
|
|
108
|
+
schema = json.loads(content)
|
|
109
|
+
|
|
110
|
+
# Basic validation
|
|
111
|
+
required_fields = ["openapi", "info", "paths"]
|
|
112
|
+
missing = [f for f in required_fields if f not in schema]
|
|
113
|
+
|
|
114
|
+
if missing:
|
|
115
|
+
typer.secho(
|
|
116
|
+
f"ā Invalid schema: missing required fields: {missing}",
|
|
117
|
+
fg=typer.colors.RED,
|
|
118
|
+
)
|
|
119
|
+
raise typer.Exit(1)
|
|
120
|
+
|
|
121
|
+
typer.secho("ā
Schema is valid!", fg=typer.colors.GREEN)
|
|
122
|
+
typer.echo(f" OpenAPI version: {schema.get('openapi')}")
|
|
123
|
+
typer.echo(f" Title: {schema.get('info', {}).get('title')}")
|
|
124
|
+
typer.echo(f" Paths: {len(schema.get('paths', {}))}")
|
|
125
|
+
|
|
126
|
+
except json.JSONDecodeError as e:
|
|
127
|
+
typer.secho(f"ā Invalid JSON: {e}", fg=typer.colors.RED)
|
|
128
|
+
raise typer.Exit(1)
|
tachyon_api/cli/main.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tachyon CLI - Main entry point
|
|
3
|
+
|
|
4
|
+
Commands:
|
|
5
|
+
- tachyon new <project> Create new project
|
|
6
|
+
- tachyon generate Generate components (alias: g)
|
|
7
|
+
- tachyon openapi OpenAPI utilities
|
|
8
|
+
- tachyon lint Code quality (ruff wrapper)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from .commands import generate, openapi, lint
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(
|
|
18
|
+
name="tachyon",
|
|
19
|
+
help="š Tachyon CLI - Fast API development toolkit",
|
|
20
|
+
add_completion=False,
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Register sub-commands
|
|
25
|
+
app.add_typer(
|
|
26
|
+
generate.app,
|
|
27
|
+
name="generate",
|
|
28
|
+
help="Generate components (service, controller, etc.)",
|
|
29
|
+
)
|
|
30
|
+
app.add_typer(generate.app, name="g", help="Alias for 'generate'", hidden=True)
|
|
31
|
+
app.add_typer(openapi.app, name="openapi", help="OpenAPI schema utilities")
|
|
32
|
+
app.add_typer(lint.app, name="lint", help="Code quality tools (ruff wrapper)")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command()
|
|
36
|
+
def new(
|
|
37
|
+
name: str = typer.Argument(..., help="Project name"),
|
|
38
|
+
path: Optional[Path] = typer.Option(
|
|
39
|
+
None,
|
|
40
|
+
"--path",
|
|
41
|
+
"-p",
|
|
42
|
+
help="Parent directory for the project (default: current directory)",
|
|
43
|
+
),
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
šļø Create a new Tachyon project with clean architecture.
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
tachyon new my-api
|
|
50
|
+
tachyon new my-api --path ./projects
|
|
51
|
+
"""
|
|
52
|
+
from .commands.new import create_project
|
|
53
|
+
|
|
54
|
+
create_project(name, path)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command()
|
|
58
|
+
def version():
|
|
59
|
+
"""Show Tachyon version."""
|
|
60
|
+
typer.echo("Tachyon API v0.6.6")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main():
|
|
64
|
+
"""Entry point for the CLI."""
|
|
65
|
+
app()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
main()
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Project scaffolding templates.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ProjectTemplates:
|
|
7
|
+
"""Templates for `tachyon new` command."""
|
|
8
|
+
|
|
9
|
+
APP = '''"""
|
|
10
|
+
Main application entry point.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import uvicorn
|
|
14
|
+
from tachyon_api import Tachyon
|
|
15
|
+
from config import settings
|
|
16
|
+
|
|
17
|
+
# Initialize app
|
|
18
|
+
app = Tachyon()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@app.get("/")
|
|
22
|
+
def root():
|
|
23
|
+
"""Health check endpoint."""
|
|
24
|
+
return {"status": "ok", "app": settings.APP_NAME}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.get("/health")
|
|
28
|
+
def health():
|
|
29
|
+
"""Detailed health check."""
|
|
30
|
+
return {
|
|
31
|
+
"status": "healthy",
|
|
32
|
+
"version": settings.VERSION,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Import and register routers here
|
|
37
|
+
# from modules.users import router as users_router
|
|
38
|
+
# app.include_router(users_router)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
uvicorn.run(
|
|
43
|
+
"app:app",
|
|
44
|
+
host=settings.HOST,
|
|
45
|
+
port=settings.PORT,
|
|
46
|
+
reload=settings.DEBUG,
|
|
47
|
+
)
|
|
48
|
+
'''
|
|
49
|
+
|
|
50
|
+
CONFIG = '''"""
|
|
51
|
+
Application configuration.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
import os
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Settings:
|
|
58
|
+
"""Application settings loaded from environment variables."""
|
|
59
|
+
|
|
60
|
+
APP_NAME: str = os.getenv("APP_NAME", "Tachyon API")
|
|
61
|
+
VERSION: str = os.getenv("VERSION", "0.1.0")
|
|
62
|
+
DEBUG: bool = os.getenv("DEBUG", "true").lower() == "true"
|
|
63
|
+
|
|
64
|
+
# Server
|
|
65
|
+
HOST: str = os.getenv("HOST", "0.0.0.0")
|
|
66
|
+
PORT: int = int(os.getenv("PORT", "8000"))
|
|
67
|
+
|
|
68
|
+
# Database (example)
|
|
69
|
+
# DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///./app.db")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
settings = Settings()
|
|
73
|
+
'''
|
|
74
|
+
|
|
75
|
+
REQUIREMENTS = """# Tachyon API Framework
|
|
76
|
+
tachyon-api>=0.6.0
|
|
77
|
+
|
|
78
|
+
# Server
|
|
79
|
+
uvicorn[standard]>=0.30.0
|
|
80
|
+
|
|
81
|
+
# Development
|
|
82
|
+
pytest>=8.0.0
|
|
83
|
+
pytest-asyncio>=0.23.0
|
|
84
|
+
httpx>=0.27.0
|
|
85
|
+
ruff>=0.4.0
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
MODULES_INIT = '''"""
|
|
89
|
+
Application modules.
|
|
90
|
+
|
|
91
|
+
Each module follows clean architecture:
|
|
92
|
+
- controller: HTTP endpoints (router)
|
|
93
|
+
- service: Business logic
|
|
94
|
+
- repository: Data access
|
|
95
|
+
- dto: Data transfer objects
|
|
96
|
+
"""
|
|
97
|
+
'''
|
|
98
|
+
|
|
99
|
+
SHARED_INIT = '''"""
|
|
100
|
+
Shared utilities and dependencies.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
from .exceptions import *
|
|
104
|
+
from .dependencies import *
|
|
105
|
+
'''
|
|
106
|
+
|
|
107
|
+
SHARED_EXCEPTIONS = '''"""
|
|
108
|
+
Custom application exceptions.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
from tachyon_api import HTTPException
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class NotFoundError(HTTPException):
|
|
115
|
+
"""Resource not found."""
|
|
116
|
+
|
|
117
|
+
def __init__(self, resource: str, id: str):
|
|
118
|
+
super().__init__(
|
|
119
|
+
status_code=404,
|
|
120
|
+
detail=f"{resource} with id '{id}' not found"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class UnauthorizedError(HTTPException):
|
|
125
|
+
"""Authentication required."""
|
|
126
|
+
|
|
127
|
+
def __init__(self, detail: str = "Not authenticated"):
|
|
128
|
+
super().__init__(
|
|
129
|
+
status_code=401,
|
|
130
|
+
detail=detail,
|
|
131
|
+
headers={"WWW-Authenticate": "Bearer"}
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class ForbiddenError(HTTPException):
|
|
136
|
+
"""Access denied."""
|
|
137
|
+
|
|
138
|
+
def __init__(self, detail: str = "Access denied"):
|
|
139
|
+
super().__init__(status_code=403, detail=detail)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class BadRequestError(HTTPException):
|
|
143
|
+
"""Invalid request."""
|
|
144
|
+
|
|
145
|
+
def __init__(self, detail: str):
|
|
146
|
+
super().__init__(status_code=400, detail=detail)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ConflictError(HTTPException):
|
|
150
|
+
"""Resource conflict (e.g., duplicate)."""
|
|
151
|
+
|
|
152
|
+
def __init__(self, detail: str):
|
|
153
|
+
super().__init__(status_code=409, detail=detail)
|
|
154
|
+
'''
|
|
155
|
+
|
|
156
|
+
SHARED_DEPENDENCIES = '''"""
|
|
157
|
+
Shared dependencies for dependency injection.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
from tachyon_api import Depends
|
|
161
|
+
from tachyon_api.security import OAuth2PasswordBearer
|
|
162
|
+
|
|
163
|
+
# Example: OAuth2 token dependency
|
|
164
|
+
# oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
|
|
165
|
+
#
|
|
166
|
+
# async def get_current_user(token: str = Depends(oauth2_scheme)):
|
|
167
|
+
# # Validate token and return user
|
|
168
|
+
# pass
|
|
169
|
+
'''
|
|
170
|
+
|
|
171
|
+
TESTS_CONFTEST = '''"""
|
|
172
|
+
Pytest configuration and fixtures.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
import pytest
|
|
176
|
+
from httpx import AsyncClient, ASGITransport
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@pytest.fixture
|
|
180
|
+
def app():
|
|
181
|
+
"""Create test application instance."""
|
|
182
|
+
from app import app
|
|
183
|
+
return app
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@pytest.fixture
|
|
187
|
+
async def client(app):
|
|
188
|
+
"""Create async test client."""
|
|
189
|
+
async with AsyncClient(
|
|
190
|
+
transport=ASGITransport(app=app),
|
|
191
|
+
base_url="http://test"
|
|
192
|
+
) as client:
|
|
193
|
+
yield client
|
|
194
|
+
'''
|