fastkit-cli 0.1.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.
- fastkit_cli/__init__.py +0 -0
- fastkit_cli/commands/__init__.py +13 -0
- fastkit_cli/commands/make.py +367 -0
- fastkit_cli/commands/migrate.py +27 -0
- fastkit_cli/commands/new.py +9 -0
- fastkit_cli/commands/seed.py +12 -0
- fastkit_cli/commands/server.py +13 -0
- fastkit_cli/main.py +20 -0
- fastkit_cli/templates/__init__.py +0 -0
- fastkit_cli-0.1.0.dist-info/METADATA +457 -0
- fastkit_cli-0.1.0.dist-info/RECORD +14 -0
- fastkit_cli-0.1.0.dist-info/WHEEL +5 -0
- fastkit_cli-0.1.0.dist-info/entry_points.txt +2 -0
- fastkit_cli-0.1.0.dist-info/top_level.txt +1 -0
fastkit_cli/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from jinja2 import Environment, FileSystemLoader
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(help="Code generation commands.")
|
|
8
|
+
|
|
9
|
+
TEMPLATES_DIR = Path(__file__).parent.parent / "templates" / "module"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
# Naming Helpers
|
|
14
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
def _to_snake_case(name: str) -> str:
|
|
17
|
+
name = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', name)
|
|
18
|
+
name = re.sub(r'([a-z\d])([A-Z])', r'\1_\2', name)
|
|
19
|
+
return name.lower()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _to_pascal_case(name: str) -> str:
|
|
23
|
+
return ''.join(word.capitalize() for word in re.split(r'[_\s-]', name))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _to_plural(name: str) -> str:
|
|
27
|
+
if name.endswith('y') and not name.endswith(('ay', 'ey', 'iy', 'oy', 'uy')):
|
|
28
|
+
return name[:-1] + 'ies'
|
|
29
|
+
if name.endswith(('s', 'sh', 'ch', 'x', 'z')):
|
|
30
|
+
return name + 'es'
|
|
31
|
+
return name + 's'
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
# Shared Helpers
|
|
36
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
def _build_context(name: str) -> dict:
|
|
39
|
+
model_name = _to_pascal_case(name)
|
|
40
|
+
snake_name = _to_snake_case(model_name)
|
|
41
|
+
table_name = _to_plural(snake_name)
|
|
42
|
+
return {
|
|
43
|
+
"model_name": model_name,
|
|
44
|
+
"snake_name": snake_name,
|
|
45
|
+
"table_name": table_name,
|
|
46
|
+
"module_folder": table_name,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _render_template(template_name: str, context: dict) -> str:
|
|
51
|
+
env = Environment(
|
|
52
|
+
loader=FileSystemLoader(str(TEMPLATES_DIR)),
|
|
53
|
+
keep_trailing_newline=True,
|
|
54
|
+
)
|
|
55
|
+
return env.get_template(template_name).render(**context)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _render_and_write(
|
|
59
|
+
template_name: str,
|
|
60
|
+
output_path: Path,
|
|
61
|
+
context: dict,
|
|
62
|
+
force: bool = False,
|
|
63
|
+
skipped: list | None = None,
|
|
64
|
+
) -> None:
|
|
65
|
+
if output_path.exists() and not force:
|
|
66
|
+
if skipped is not None:
|
|
67
|
+
skipped.append(output_path.name)
|
|
68
|
+
return
|
|
69
|
+
try:
|
|
70
|
+
content = _render_template(template_name, context)
|
|
71
|
+
output_path.write_text(content)
|
|
72
|
+
typer.secho(f" ✓ {output_path.name}", fg=typer.colors.GREEN)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
typer.secho(f" ✗ {output_path.name} — {e}", fg=typer.colors.RED)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _register_in_alembic(model_name: str, module_folder: str) -> None:
|
|
78
|
+
env_py = None
|
|
79
|
+
for candidate in ["alembic/env.py", "migrations/env.py"]:
|
|
80
|
+
path = Path(candidate)
|
|
81
|
+
if path.exists():
|
|
82
|
+
env_py = path
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
if env_py is None:
|
|
86
|
+
typer.secho(
|
|
87
|
+
" ⚠ Could not find alembic/env.py or migrations/env.py. "
|
|
88
|
+
"Please register the model manually.",
|
|
89
|
+
fg=typer.colors.YELLOW,
|
|
90
|
+
)
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
content = env_py.read_text()
|
|
94
|
+
import_line = f"from modules.{module_folder}.models import {model_name} # noqa"
|
|
95
|
+
|
|
96
|
+
if import_line in content:
|
|
97
|
+
typer.secho(f" ✓ Model already registered in {env_py}", fg=typer.colors.YELLOW)
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
insert_marker = "target_metadata"
|
|
101
|
+
if insert_marker in content:
|
|
102
|
+
content = content.replace(insert_marker, f"{import_line}\n{insert_marker}", 1)
|
|
103
|
+
env_py.write_text(content)
|
|
104
|
+
typer.secho(f" ✓ Registered model in {env_py}", fg=typer.colors.GREEN)
|
|
105
|
+
else:
|
|
106
|
+
typer.secho(
|
|
107
|
+
f" ⚠ Could not auto-register model in {env_py}. "
|
|
108
|
+
f"Please add manually:\n {import_line}",
|
|
109
|
+
fg=typer.colors.YELLOW,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _print_skipped(skipped: list) -> None:
|
|
114
|
+
if skipped:
|
|
115
|
+
typer.echo("")
|
|
116
|
+
typer.secho(
|
|
117
|
+
f" Skipped {len(skipped)} existing file(s). Use --force to overwrite.",
|
|
118
|
+
fg=typer.colors.YELLOW,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
123
|
+
# Commands
|
|
124
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
@app.command()
|
|
127
|
+
def module(
|
|
128
|
+
name: str = typer.Argument(..., help="Module name in PascalCase (e.g. Invoice, InvoiceItem)"),
|
|
129
|
+
modules_dir: str = typer.Option("modules", "--dir", "-d", help="Modules root directory"),
|
|
130
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"),
|
|
131
|
+
async_mode: bool = typer.Option(False, "--async", "-a", help="Use async repository and service"),
|
|
132
|
+
):
|
|
133
|
+
"""
|
|
134
|
+
Generate a new module with model, schemas, repository, and service.
|
|
135
|
+
|
|
136
|
+
\b
|
|
137
|
+
Example:
|
|
138
|
+
fastkit make module Invoice
|
|
139
|
+
fastkit make module Invoice --async
|
|
140
|
+
fastkit make module InvoiceItem --dir src/modules
|
|
141
|
+
"""
|
|
142
|
+
context = _build_context(name)
|
|
143
|
+
module_path = Path(modules_dir) / context["table_name"]
|
|
144
|
+
|
|
145
|
+
typer.echo("")
|
|
146
|
+
typer.secho(f"Generating module: {context['model_name']}", fg=typer.colors.BRIGHT_CYAN, bold=True)
|
|
147
|
+
typer.echo(f" Location : {module_path}/")
|
|
148
|
+
typer.echo(f" Model : {context['model_name']}")
|
|
149
|
+
typer.echo(f" Table : {context['table_name']}")
|
|
150
|
+
typer.echo(f" Mode : {'async' if async_mode else 'sync'}")
|
|
151
|
+
typer.echo("")
|
|
152
|
+
|
|
153
|
+
module_path.mkdir(parents=True, exist_ok=True)
|
|
154
|
+
|
|
155
|
+
init_file = module_path / "__init__.py"
|
|
156
|
+
if not init_file.exists() or force:
|
|
157
|
+
init_file.write_text("")
|
|
158
|
+
typer.secho(" ✓ __init__.py", fg=typer.colors.GREEN)
|
|
159
|
+
|
|
160
|
+
templates = [
|
|
161
|
+
("model.py.jinja", "models.py"),
|
|
162
|
+
("schemas.py.jinja", "schemas.py"),
|
|
163
|
+
("async_repository.py.jinja" if async_mode else "repository.py.jinja", "repository.py"),
|
|
164
|
+
("async_service.py.jinja" if async_mode else "service.py.jinja", "service.py"),
|
|
165
|
+
("async_router.py.jinja" if async_mode else "router.py.jinja", "router.py"),
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
skipped: list = []
|
|
169
|
+
for template_name, output_filename in templates:
|
|
170
|
+
_render_and_write(
|
|
171
|
+
template_name=template_name,
|
|
172
|
+
output_path=module_path / output_filename,
|
|
173
|
+
context=context,
|
|
174
|
+
force=force,
|
|
175
|
+
skipped=skipped,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
_print_skipped(skipped)
|
|
179
|
+
|
|
180
|
+
typer.echo("")
|
|
181
|
+
_register_in_alembic(context["model_name"], context["table_name"])
|
|
182
|
+
|
|
183
|
+
typer.echo("")
|
|
184
|
+
typer.secho("Done! Next steps:", fg=typer.colors.BRIGHT_WHITE, bold=True)
|
|
185
|
+
typer.echo(f" 1. Define your fields in {module_path}/models.py")
|
|
186
|
+
typer.echo(f" 2. Add schemas in {module_path}/schemas.py")
|
|
187
|
+
typer.echo(f" 3. Run: fastkit migrate make -m 'create_{context['table_name']}'")
|
|
188
|
+
typer.echo("")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@app.command()
|
|
192
|
+
def model(
|
|
193
|
+
name: str = typer.Argument(..., help="Model name in PascalCase (e.g. Invoice, InvoiceItem)"),
|
|
194
|
+
path: str = typer.Option(".", "--path", "-p", help="Path to target directory"),
|
|
195
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing file"),
|
|
196
|
+
):
|
|
197
|
+
"""
|
|
198
|
+
Generate only a model file.
|
|
199
|
+
|
|
200
|
+
\b
|
|
201
|
+
Example:
|
|
202
|
+
fastkit make model Invoice
|
|
203
|
+
fastkit make model Invoice --path modules/invoices
|
|
204
|
+
"""
|
|
205
|
+
context = _build_context(name)
|
|
206
|
+
module_path = Path(path)
|
|
207
|
+
|
|
208
|
+
typer.echo("")
|
|
209
|
+
typer.secho(f"Generating model: {context['model_name']}", fg=typer.colors.BRIGHT_CYAN, bold=True)
|
|
210
|
+
typer.echo("")
|
|
211
|
+
|
|
212
|
+
_render_and_write(
|
|
213
|
+
template_name="model.py.jinja",
|
|
214
|
+
output_path=module_path / "models.py",
|
|
215
|
+
context=context,
|
|
216
|
+
force=force,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
typer.echo("")
|
|
220
|
+
_register_in_alembic(context["model_name"], context["table_name"])
|
|
221
|
+
|
|
222
|
+
typer.echo("")
|
|
223
|
+
typer.secho("Done!", fg=typer.colors.BRIGHT_WHITE, bold=True)
|
|
224
|
+
typer.echo(f" Define your fields in {module_path}/models.py")
|
|
225
|
+
typer.echo("")
|
|
226
|
+
|
|
227
|
+
@app.command()
|
|
228
|
+
def schema(
|
|
229
|
+
name: str = typer.Argument(..., help="Schema name in PascalCase (e.g. Invoice)"),
|
|
230
|
+
path: str = typer.Option(".", "--path", "-p", help="Path to target directory"),
|
|
231
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing file"),
|
|
232
|
+
):
|
|
233
|
+
"""
|
|
234
|
+
Generate only a schemas file.
|
|
235
|
+
|
|
236
|
+
\b
|
|
237
|
+
Example:
|
|
238
|
+
fastkit make schema Invoice
|
|
239
|
+
fastkit make schema Invoice --path modules/invoices
|
|
240
|
+
"""
|
|
241
|
+
context = _build_context(name)
|
|
242
|
+
module_path = Path(path)
|
|
243
|
+
|
|
244
|
+
typer.echo("")
|
|
245
|
+
typer.secho(f"Generating schemas: {context['model_name']}", fg=typer.colors.BRIGHT_CYAN, bold=True)
|
|
246
|
+
typer.echo("")
|
|
247
|
+
|
|
248
|
+
_render_and_write(
|
|
249
|
+
template_name="schemas.py.jinja",
|
|
250
|
+
output_path=module_path / "schemas.py",
|
|
251
|
+
context=context,
|
|
252
|
+
force=force,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
typer.echo("")
|
|
256
|
+
typer.secho("Done!", fg=typer.colors.BRIGHT_WHITE, bold=True)
|
|
257
|
+
typer.echo(f" Define your schemas in {module_path}/schemas.py")
|
|
258
|
+
typer.echo("")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@app.command()
|
|
262
|
+
def repository(
|
|
263
|
+
name: str = typer.Argument(..., help="Repository name in PascalCase (e.g. Invoice)"),
|
|
264
|
+
path: str = typer.Option(".", "--path", "-p", help="Path to target directory"),
|
|
265
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing file"),
|
|
266
|
+
async_mode: bool = typer.Option(False, "--async", "-a", help="Use async repository"),
|
|
267
|
+
):
|
|
268
|
+
"""
|
|
269
|
+
Generate only a repository file.
|
|
270
|
+
|
|
271
|
+
\b
|
|
272
|
+
Example:
|
|
273
|
+
fastkit make repository Invoice
|
|
274
|
+
fastkit make repository Invoice --async
|
|
275
|
+
fastkit make repository Invoice --path modules/invoices
|
|
276
|
+
"""
|
|
277
|
+
context = _build_context(name)
|
|
278
|
+
module_path = Path(path)
|
|
279
|
+
template = "async_repository.py.jinja" if async_mode else "repository.py.jinja"
|
|
280
|
+
|
|
281
|
+
typer.echo("")
|
|
282
|
+
typer.secho(f"Generating repository: {context['model_name']}", fg=typer.colors.BRIGHT_CYAN, bold=True)
|
|
283
|
+
typer.echo(f" Mode: {'async' if async_mode else 'sync'}")
|
|
284
|
+
typer.echo("")
|
|
285
|
+
|
|
286
|
+
_render_and_write(
|
|
287
|
+
template_name=template,
|
|
288
|
+
output_path=module_path / "repository.py",
|
|
289
|
+
context=context,
|
|
290
|
+
force=force,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
typer.echo("")
|
|
294
|
+
typer.secho("Done!", fg=typer.colors.BRIGHT_WHITE, bold=True)
|
|
295
|
+
typer.echo("")
|
|
296
|
+
|
|
297
|
+
@app.command()
|
|
298
|
+
def service(
|
|
299
|
+
name: str = typer.Argument(..., help="Service name in PascalCase (e.g. Invoice)"),
|
|
300
|
+
path: str = typer.Option(".", "--path", "-p", help="Path to target directory"),
|
|
301
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing file"),
|
|
302
|
+
async_mode: bool = typer.Option(False, "--async", "-a", help="Use async service"),
|
|
303
|
+
):
|
|
304
|
+
"""
|
|
305
|
+
Generate only a service file.
|
|
306
|
+
|
|
307
|
+
\b
|
|
308
|
+
Example:
|
|
309
|
+
fastkit make service Invoice
|
|
310
|
+
fastkit make service Invoice --async
|
|
311
|
+
fastkit make service Invoice --path modules/invoices
|
|
312
|
+
"""
|
|
313
|
+
context = _build_context(name)
|
|
314
|
+
module_path = Path(path)
|
|
315
|
+
template = "async_service.py.jinja" if async_mode else "service.py.jinja"
|
|
316
|
+
|
|
317
|
+
typer.echo("")
|
|
318
|
+
typer.secho(f"Generating service: {context['model_name']}", fg=typer.colors.BRIGHT_CYAN, bold=True)
|
|
319
|
+
typer.echo(f" Mode: {'async' if async_mode else 'sync'}")
|
|
320
|
+
typer.echo("")
|
|
321
|
+
|
|
322
|
+
_render_and_write(
|
|
323
|
+
template_name=template,
|
|
324
|
+
output_path=module_path / "service.py",
|
|
325
|
+
context=context,
|
|
326
|
+
force=force,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
typer.echo("")
|
|
330
|
+
typer.secho("Done!", fg=typer.colors.BRIGHT_WHITE, bold=True)
|
|
331
|
+
typer.echo("")
|
|
332
|
+
|
|
333
|
+
@app.command()
|
|
334
|
+
def router(
|
|
335
|
+
name: str = typer.Argument(..., help="Router name in PascalCase (e.g. Invoice)"),
|
|
336
|
+
path: str = typer.Option(".", "--path", "-p", help="Path to target directory"),
|
|
337
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing file"),
|
|
338
|
+
async_mode: bool = typer.Option(False, "--async", "-a", help="Use async router"),
|
|
339
|
+
):
|
|
340
|
+
"""
|
|
341
|
+
Generate only a router file.
|
|
342
|
+
|
|
343
|
+
\b
|
|
344
|
+
Example:
|
|
345
|
+
fastkit make router Invoice
|
|
346
|
+
fastkit make router Invoice --async
|
|
347
|
+
fastkit make router Invoice --path modules/invoices
|
|
348
|
+
"""
|
|
349
|
+
context = _build_context(name)
|
|
350
|
+
module_path = Path(path)
|
|
351
|
+
template = "async_router.py.jinja" if async_mode else "router.py.jinja"
|
|
352
|
+
|
|
353
|
+
typer.echo("")
|
|
354
|
+
typer.secho(f"Generating router: {context['model_name']}", fg=typer.colors.BRIGHT_CYAN, bold=True)
|
|
355
|
+
typer.echo(f" Mode: {'async' if async_mode else 'sync'}")
|
|
356
|
+
typer.echo("")
|
|
357
|
+
|
|
358
|
+
_render_and_write(
|
|
359
|
+
template_name=template,
|
|
360
|
+
output_path=module_path / "router.py",
|
|
361
|
+
context=context,
|
|
362
|
+
force=force,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
typer.echo("")
|
|
366
|
+
typer.secho("Done!", fg=typer.colors.BRIGHT_WHITE, bold=True)
|
|
367
|
+
typer.echo("")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
app = typer.Typer(help="Migration commands.")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@app.command()
|
|
7
|
+
def run():
|
|
8
|
+
"""Run pending migrations. (alembic upgrade head)"""
|
|
9
|
+
typer.echo("Running migrations...")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command()
|
|
13
|
+
def make(message: str = typer.Option(..., "--message", "-m", help="Migration message")):
|
|
14
|
+
"""Generate a new migration. (alembic revision --autogenerate)"""
|
|
15
|
+
typer.echo(f"Generating migration: {message}")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command()
|
|
19
|
+
def rollback():
|
|
20
|
+
"""Rollback last migration. (alembic downgrade -1)"""
|
|
21
|
+
typer.echo("Rolling back last migration...")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command()
|
|
25
|
+
def status():
|
|
26
|
+
"""Show current migration status. (alembic current)"""
|
|
27
|
+
typer.echo("Checking migration status...")
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
app = typer.Typer(help="Create a new FastKit project.")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@app.callback(invoke_without_command=True)
|
|
7
|
+
def create(name: str = typer.Argument(..., help="Project name")):
|
|
8
|
+
"""Create a new FastKit project."""
|
|
9
|
+
typer.echo(f"Creating new FastKit project: {name}")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
app = typer.Typer(help="Database seeding commands.")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@app.callback(invoke_without_command=True)
|
|
7
|
+
def seed(seeder: str = typer.Argument(None, help="Specific seeder class to run")):
|
|
8
|
+
"""Run database seeders."""
|
|
9
|
+
if seeder:
|
|
10
|
+
typer.echo(f"Running seeder: {seeder}")
|
|
11
|
+
else:
|
|
12
|
+
typer.echo("Running all seeders...")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
app = typer.Typer(help="Server commands.")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@app.callback(invoke_without_command=True)
|
|
7
|
+
def start(
|
|
8
|
+
host: str = typer.Option("0.0.0.0", "--host", "-h", help="Host to bind"),
|
|
9
|
+
port: int = typer.Option(8000, "--port", "-p", help="Port to bind"),
|
|
10
|
+
reload: bool = typer.Option(True, "--reload/--no-reload", help="Enable auto-reload"),
|
|
11
|
+
):
|
|
12
|
+
"""Start the FastAPI development server."""
|
|
13
|
+
typer.echo(f"Starting server on {host}:{port}...")
|
fastkit_cli/main.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from fastkit_cli.commands import make, migrate, seed, server, new
|
|
3
|
+
|
|
4
|
+
app = typer.Typer(
|
|
5
|
+
name="fastkit",
|
|
6
|
+
help="FastKit CLI - FastAPI with structure and developer experience.",
|
|
7
|
+
no_args_is_help=True,
|
|
8
|
+
pretty_exceptions_enable=False,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# Register command groups
|
|
12
|
+
app.add_typer(make.app, name="make")
|
|
13
|
+
app.add_typer(migrate.app, name="migrate")
|
|
14
|
+
app.add_typer(seed.app, name="db")
|
|
15
|
+
app.add_typer(server.app, name="server")
|
|
16
|
+
app.add_typer(new.app, name="new")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
app()
|
|
File without changes
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastkit-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastKit CLI is a code generation tool for the fastkit core packge.
|
|
5
|
+
Project-URL: Homepage, https://github.com/codevelo-pub/fastkit-cli
|
|
6
|
+
Project-URL: Documentation, https://github.com/codevelo-pub/fastkit-cli#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/codevelo-pub/fastkit-cli
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: fastkit-core>=0.3.5
|
|
11
|
+
Requires-Dist: jinja2>=3.1.6
|
|
12
|
+
Requires-Dist: typer>=0.24.1
|
|
13
|
+
|
|
14
|
+
<div align="center">
|
|
15
|
+
<h1>FastKit CLI</h1>
|
|
16
|
+
|
|
17
|
+
[](https://pypi.org/project/fastkit-cli/)
|
|
18
|
+
[](https://www.python.org/downloads/)
|
|
19
|
+
[](https://github.com/codevelo-pub/fastkit-cli/actions/workflows/tests.yml)
|
|
20
|
+
[](https://opensource.org/licenses/MIT)
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
**FastAPI with structure and developer experience.**
|
|
25
|
+
|
|
26
|
+
FastKit CLI is a code generation tool for the [FastKit](https://github.com/codevelo-pub/fastkit-core) ecosystem. It generates complete, production-ready modules for FastAPI projects — models, schemas, repositories, services, and routers — in seconds.
|
|
27
|
+
|
|
28
|
+
> Inspired by Laravel's `php artisan`, built for FastAPI developers who want structure without the overhead.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- Python 3.12
|
|
35
|
+
- [fastkit-core](https://pypi.org/project/fastkit-core/)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install fastkit-cli
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or with [uv](https://github.com/astral-sh/uv) (recommended):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv add fastkit-cli
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Verify the installation:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
fastkit --help
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Quickstart
|
|
60
|
+
|
|
61
|
+
### Generate a complete module
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
fastkit make module Invoice
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This generates the following structure:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
modules/
|
|
71
|
+
└── invoices/
|
|
72
|
+
├── __init__.py
|
|
73
|
+
├── models.py
|
|
74
|
+
├── schemas.py
|
|
75
|
+
├── repository.py
|
|
76
|
+
├── service.py
|
|
77
|
+
└── router.py
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
With a confirmation and next steps:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
Generating module: Invoice
|
|
84
|
+
Location : modules/invoices/
|
|
85
|
+
Model : Invoice
|
|
86
|
+
Table : invoices
|
|
87
|
+
Mode : sync
|
|
88
|
+
|
|
89
|
+
✓ __init__.py
|
|
90
|
+
✓ models.py
|
|
91
|
+
✓ schemas.py
|
|
92
|
+
✓ repository.py
|
|
93
|
+
✓ service.py
|
|
94
|
+
✓ router.py
|
|
95
|
+
|
|
96
|
+
✓ Registered model in alembic/env.py
|
|
97
|
+
|
|
98
|
+
Done! Next steps:
|
|
99
|
+
1. Define your fields in modules/invoices/models.py
|
|
100
|
+
2. Add schemas in modules/invoices/schemas.py
|
|
101
|
+
3. Run: fastkit migrate make -m 'create_invoices'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Generate an async module
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
fastkit make module Invoice --async
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Generates the same structure but with async repository, service, and router using `AsyncSession` and `get_async_db`.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## make
|
|
115
|
+
|
|
116
|
+
### `fastkit make module`
|
|
117
|
+
|
|
118
|
+
Generates a complete module with all layers.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
fastkit make module <Name> [OPTIONS]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
| Option | Short | Default | Description |
|
|
125
|
+
|--------|-------|---------|-------------|
|
|
126
|
+
| `--dir` | `-d` | `modules` | Root directory for modules |
|
|
127
|
+
| `--async` | `-a` | `False` | Use async repository, service, and router |
|
|
128
|
+
| `--force` | `-f` | `False` | Overwrite existing files |
|
|
129
|
+
|
|
130
|
+
**Examples:**
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Basic usage
|
|
134
|
+
fastkit make module Invoice
|
|
135
|
+
|
|
136
|
+
# Async mode
|
|
137
|
+
fastkit make module Invoice --async
|
|
138
|
+
|
|
139
|
+
# Custom directory
|
|
140
|
+
fastkit make module Invoice --dir src/modules
|
|
141
|
+
|
|
142
|
+
# Compound name (automatically converted)
|
|
143
|
+
fastkit make module InvoiceItem
|
|
144
|
+
|
|
145
|
+
# Overwrite existing files
|
|
146
|
+
fastkit make module Invoice --force
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### `fastkit make model`
|
|
152
|
+
|
|
153
|
+
Generates only the SQLAlchemy model file.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
fastkit make model <Name> [OPTIONS]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
| Option | Short | Default | Description |
|
|
160
|
+
|--------|-------|---------|-------------|
|
|
161
|
+
| `--path` | `-p` | `.` | Target directory |
|
|
162
|
+
| `--force` | `-f` | `False` | Overwrite existing file |
|
|
163
|
+
|
|
164
|
+
**Examples:**
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
fastkit make model Invoice
|
|
168
|
+
fastkit make model Invoice --path modules/invoices
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Generated `models.py`:**
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from fastkit_core.database import BaseWithTimestamps, IntIdMixin
|
|
175
|
+
# from fastkit_core.database import UUIDMixin, SoftDeleteMixin, SlugMixin
|
|
176
|
+
|
|
177
|
+
class Invoice(BaseWithTimestamps, IntIdMixin):
|
|
178
|
+
__tablename__ = "invoices"
|
|
179
|
+
# Define your fields here
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### `fastkit make schema`
|
|
185
|
+
|
|
186
|
+
Generates only the Pydantic schemas file.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
fastkit make schema <Name> [OPTIONS]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
| Option | Short | Default | Description |
|
|
193
|
+
|--------|-------|---------|-------------|
|
|
194
|
+
| `--path` | `-p` | `.` | Target directory |
|
|
195
|
+
| `--force` | `-f` | `False` | Overwrite existing file |
|
|
196
|
+
|
|
197
|
+
**Examples:**
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
fastkit make schema Invoice
|
|
201
|
+
fastkit make schema Invoice --path modules/invoices
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Generated `schemas.py`:**
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from fastkit_core.validation import BaseSchema
|
|
208
|
+
|
|
209
|
+
class InvoiceCreate(BaseSchema):
|
|
210
|
+
pass # Define your fields here
|
|
211
|
+
|
|
212
|
+
class InvoiceUpdate(BaseSchema):
|
|
213
|
+
pass # All fields optional for partial updates
|
|
214
|
+
|
|
215
|
+
class InvoiceResponse(BaseSchema):
|
|
216
|
+
id: int
|
|
217
|
+
model_config = {"from_attributes": True}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
### `fastkit make repository`
|
|
223
|
+
|
|
224
|
+
Generates only the repository file.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
fastkit make repository <Name> [OPTIONS]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
| Option | Short | Default | Description |
|
|
231
|
+
|--------|-------|---------|-------------|
|
|
232
|
+
| `--path` | `-p` | `.` | Target directory |
|
|
233
|
+
| `--async` | `-a` | `False` | Use async repository |
|
|
234
|
+
| `--force` | `-f` | `False` | Overwrite existing file |
|
|
235
|
+
|
|
236
|
+
**Examples:**
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
fastkit make repository Invoice
|
|
240
|
+
fastkit make repository Invoice --async
|
|
241
|
+
fastkit make repository Invoice --path modules/invoices
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
### `fastkit make service`
|
|
247
|
+
|
|
248
|
+
Generates only the service file.
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
fastkit make service <Name> [OPTIONS]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
| Option | Short | Default | Description |
|
|
255
|
+
|--------|-------|---------|-------------|
|
|
256
|
+
| `--path` | `-p` | `.` | Target directory |
|
|
257
|
+
| `--async` | `-a` | `False` | Use async service |
|
|
258
|
+
| `--force` | `-f` | `False` | Overwrite existing file |
|
|
259
|
+
|
|
260
|
+
**Examples:**
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
fastkit make service Invoice
|
|
264
|
+
fastkit make service Invoice --async
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
### `fastkit make router`
|
|
270
|
+
|
|
271
|
+
Generates only the router file with full CRUD endpoints.
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
fastkit make router <Name> [OPTIONS]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
| Option | Short | Default | Description |
|
|
278
|
+
|--------|-------|---------|-------------|
|
|
279
|
+
| `--path` | `-p` | `.` | Target directory |
|
|
280
|
+
| `--async` | `-a` | `False` | Use async router |
|
|
281
|
+
| `--force` | `-f` | `False` | Overwrite existing file |
|
|
282
|
+
|
|
283
|
+
**Examples:**
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
fastkit make router Invoice
|
|
287
|
+
fastkit make router Invoice --async
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Generated endpoints:**
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
GET /invoices → index (paginated list)
|
|
294
|
+
GET /invoices/{id} → show
|
|
295
|
+
POST /invoices → store
|
|
296
|
+
PUT /invoices/{id} → update
|
|
297
|
+
DELETE /invoices/{id} → destroy
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## migrate
|
|
303
|
+
|
|
304
|
+
Wrapper around [Alembic](https://alembic.sqlalchemy.org/) migrations.
|
|
305
|
+
|
|
306
|
+
### `fastkit migrate run`
|
|
307
|
+
|
|
308
|
+
Run all pending migrations.
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
fastkit migrate run
|
|
312
|
+
# Equivalent to: alembic upgrade head
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### `fastkit migrate make`
|
|
316
|
+
|
|
317
|
+
Generate a new migration based on model changes.
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
fastkit migrate make -m "create_invoices"
|
|
321
|
+
# Equivalent to: alembic revision --autogenerate -m "create_invoices"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
| Option | Short | Required | Description |
|
|
325
|
+
|--------|-------|----------|-------------|
|
|
326
|
+
| `--message` | `-m` | Yes | Migration description |
|
|
327
|
+
|
|
328
|
+
### `fastkit migrate rollback`
|
|
329
|
+
|
|
330
|
+
Rollback the last migration.
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
fastkit migrate rollback
|
|
334
|
+
# Equivalent to: alembic downgrade -1
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### `fastkit migrate status`
|
|
338
|
+
|
|
339
|
+
Show the current migration status.
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
fastkit migrate status
|
|
343
|
+
# Equivalent to: alembic current
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## db seed
|
|
349
|
+
|
|
350
|
+
Run database seeders.
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
# Run all seeders
|
|
354
|
+
fastkit db seed
|
|
355
|
+
|
|
356
|
+
# Run a specific seeder
|
|
357
|
+
fastkit db seed UserSeeder
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## server
|
|
363
|
+
|
|
364
|
+
Start the FastAPI development server.
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
fastkit server
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
| Option | Short | Default | Description |
|
|
371
|
+
|--------|-------|---------|-------------|
|
|
372
|
+
| `--host` | `-h` | `0.0.0.0` | Host to bind |
|
|
373
|
+
| `--port` | `-p` | `8000` | Port to bind |
|
|
374
|
+
| `--reload / --no-reload` | | `True` | Enable auto-reload |
|
|
375
|
+
|
|
376
|
+
**Examples:**
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
# Default
|
|
380
|
+
fastkit server
|
|
381
|
+
|
|
382
|
+
# Custom host and port
|
|
383
|
+
fastkit server --host 127.0.0.1 --port 9000
|
|
384
|
+
|
|
385
|
+
# Without auto-reload
|
|
386
|
+
fastkit server --no-reload
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## new
|
|
392
|
+
|
|
393
|
+
Create a new FastKit project from a template.
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
fastkit new my-project
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Naming Conventions
|
|
402
|
+
|
|
403
|
+
FastKit CLI automatically handles naming conversions regardless of how you pass the module name:
|
|
404
|
+
|
|
405
|
+
| Input | Model | Snake | Table | Folder |
|
|
406
|
+
|-------|-------|-------|-------|--------|
|
|
407
|
+
| `Invoice` | `Invoice` | `invoice` | `invoices` | `invoices` |
|
|
408
|
+
| `invoice` | `Invoice` | `invoice` | `invoices` | `invoices` |
|
|
409
|
+
| `InvoiceItem` | `InvoiceItem` | `invoice_item` | `invoice_items` | `invoice_items` |
|
|
410
|
+
| `invoice_item` | `InvoiceItem` | `invoice_item` | `invoice_items` | `invoice_items` |
|
|
411
|
+
| `Category` | `Category` | `category` | `categories` | `categories` |
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## Typical Workflow
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
# 1. Generate a new module
|
|
419
|
+
fastkit make module Invoice --async
|
|
420
|
+
|
|
421
|
+
# 2. Define your model fields
|
|
422
|
+
# Edit modules/invoices/models.py
|
|
423
|
+
|
|
424
|
+
# 3. Define your schemas
|
|
425
|
+
# Edit modules/invoices/schemas.py
|
|
426
|
+
|
|
427
|
+
# 4. Generate and run migration
|
|
428
|
+
fastkit migrate make -m "create_invoices"
|
|
429
|
+
fastkit migrate run
|
|
430
|
+
|
|
431
|
+
# 5. Register the router in your main app
|
|
432
|
+
# In app.py:
|
|
433
|
+
# from modules.invoices.router import router as invoices_router
|
|
434
|
+
# app.include_router(invoices_router, prefix="/api/v1")
|
|
435
|
+
|
|
436
|
+
# 6. Start the server
|
|
437
|
+
fastkit server
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Related Packages
|
|
443
|
+
|
|
444
|
+
- [fastkit-core](https://pypi.org/project/fastkit-core/) — Base classes, repository pattern, validation, i18n
|
|
445
|
+
- [mailbridge](https://pypi.org/project/mailbridge/) — Email delivery abstraction
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## License
|
|
450
|
+
|
|
451
|
+
FastKit Core is open-source software licensed under the [MIT License](https://opensource.org/license/MIT).
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Built by CodeVelo
|
|
456
|
+
|
|
457
|
+
FastKit is developed and maintained by [Codevelo](https://codevelo.io) for the FastAPI community.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
fastkit_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
fastkit_cli/main.py,sha256=MVCbiuhOWz9I2FfKP6KU4qjHBINznR_ekb4OJYKAdo4,514
|
|
3
|
+
fastkit_cli/commands/__init__.py,sha256=O2DOG56rHVwANWjyy6hsA3sP3BHtA86_cQqoXcAlyXc,246
|
|
4
|
+
fastkit_cli/commands/make.py,sha256=MRxDqAOvHcgLcKCwwBfVYgH5xtoj9MlTkiO8HYlQxF0,13051
|
|
5
|
+
fastkit_cli/commands/migrate.py,sha256=oJHDi4I6PWLvOcSXs9v9vYv6SUxZs2DwydPHMJe96Y0,688
|
|
6
|
+
fastkit_cli/commands/new.py,sha256=wKgbwCijTL202Oi68tSjRm_dOwlPKFjC2YguR8r45js,276
|
|
7
|
+
fastkit_cli/commands/seed.py,sha256=ThRGBxgM-ciU19th0Ccsker6UWYT890Ocnb1XxJkOnc,344
|
|
8
|
+
fastkit_cli/commands/server.py,sha256=QaClYex641TI8Ua5L9CN6lqE2ArPL5t0wMjf1jZMfes,459
|
|
9
|
+
fastkit_cli/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
fastkit_cli-0.1.0.dist-info/METADATA,sha256=OPvkrG2NVXQdrTxgQJLvQJVkw6IxN4MYiEjln16YUb4,9835
|
|
11
|
+
fastkit_cli-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
12
|
+
fastkit_cli-0.1.0.dist-info/entry_points.txt,sha256=9bLU81EMcn8V0JrhwAyGn4CDPp8yDEeRgCjppHbZC2Y,49
|
|
13
|
+
fastkit_cli-0.1.0.dist-info/top_level.txt,sha256=OWFS0N5oLU8nzQkoMXL9W3vTrhGutPKYVppxwH8IOPo,12
|
|
14
|
+
fastkit_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fastkit_cli
|