fastgen-cli 0.4.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.
fastgen/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.4.0"
fastgen/cli.py ADDED
@@ -0,0 +1,80 @@
1
+ """fastgen CLI entrypoint."""
2
+
3
+ # ruff: noqa: B008 (Typer's idiomatic Option/Argument-in-default pattern)
4
+
5
+ from __future__ import annotations
6
+
7
+ from pathlib import Path
8
+
9
+ import typer
10
+ from rich.table import Table
11
+
12
+ from . import __version__
13
+ from .generators.core import generate_core
14
+ from .generators.module import generate_module
15
+ from .generators.registry import list_registered, register_module
16
+ from .writers import console, report
17
+
18
+ app = typer.Typer(
19
+ name="fastgen",
20
+ help="FastAPI feature-based module manager (nest-cli style).",
21
+ no_args_is_help=True,
22
+ )
23
+ make_app = typer.Typer(help="Scaffold modules and core infrastructure.", no_args_is_help=True)
24
+ app.add_typer(make_app, name="make")
25
+
26
+
27
+ @app.callback(invoke_without_command=True)
28
+ def main(
29
+ version: bool = typer.Option(False, "--version", "-V", help="Show version and exit."),
30
+ ) -> None:
31
+ if version:
32
+ typer.echo(f"fastgen {__version__}")
33
+ raise typer.Exit()
34
+
35
+
36
+ @make_app.command("module")
37
+ def make_module(
38
+ feature: str = typer.Argument(..., help="Feature name, e.g. user"),
39
+ directory: Path = typer.Option(Path.cwd(), "--dir", "-d", help="Target project root."),
40
+ dry_run: bool = typer.Option(False, "--dry-run", help="Preview files without writing."),
41
+ force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files."),
42
+ ) -> None:
43
+ """Scaffold a feature module and register it.
44
+
45
+ Generates a minimal module skeleton (schemas / service / router /
46
+ __init__) that outlines the module's shape; fill in the entity fields and
47
+ business logic yourself. The shared app/core/ database scaffolding and the
48
+ module registry are created automatically.
49
+ """
50
+ files = generate_module(feature, directory, force=force, dry_run=dry_run)
51
+ files += generate_core(directory, dry_run=dry_run)
52
+ files.append(register_module(directory, feature, dry_run=dry_run))
53
+ report(files)
54
+ skipped = [f for f in files if f.status == "skipped"]
55
+ if skipped and not force:
56
+ typer.secho(
57
+ f"{len(skipped)} file(s) already exist. Re-run with --force to overwrite.",
58
+ fg=typer.colors.YELLOW,
59
+ )
60
+
61
+
62
+ @app.command("list")
63
+ def list_modules(
64
+ directory: Path = typer.Option(Path.cwd(), "--dir", "-d", help="Target project root."),
65
+ ) -> None:
66
+ """List registered modules and their boundaries."""
67
+ rows = list_registered(directory)
68
+ table = Table(title="Registered modules")
69
+ table.add_column("module", style="cyan")
70
+ table.add_column("path", style="magenta")
71
+ table.add_column("description")
72
+ for name, import_path, doc in rows:
73
+ table.add_row(name, import_path, doc)
74
+ console.print(table)
75
+ if not rows:
76
+ typer.secho("No modules registered yet. Run `fastgen make module <name>`.", fg="yellow")
77
+
78
+
79
+ if __name__ == "__main__":
80
+ app()
File without changes
@@ -0,0 +1,45 @@
1
+ """Jinja2 rendering helpers shared by all generators."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from jinja2 import Environment, FileSystemLoader, StrictUndefined
8
+
9
+ from ..writers import GeneratedFile, write_file
10
+
11
+ TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "templates"
12
+
13
+
14
+ def _env(template_dir: str) -> Environment:
15
+ return Environment(
16
+ loader=FileSystemLoader(TEMPLATES_DIR / template_dir),
17
+ trim_blocks=True,
18
+ lstrip_blocks=True,
19
+ undefined=StrictUndefined,
20
+ )
21
+
22
+
23
+ def render_template(template_dir: str, name: str, context: dict) -> str:
24
+ """Render a single ``*.j2`` file without writing anything."""
25
+ return _env(template_dir).get_template(name).render(**context)
26
+
27
+
28
+ def render_tree(
29
+ template_dir: str,
30
+ context: dict,
31
+ dest_dir: Path,
32
+ *,
33
+ force: bool = False,
34
+ dry_run: bool = False,
35
+ ) -> list[GeneratedFile]:
36
+ """Render every ``*.j2`` file in ``template_dir`` into ``dest_dir``."""
37
+ src = TEMPLATES_DIR / template_dir
38
+ env = _env(template_dir)
39
+ files: list[GeneratedFile] = []
40
+ for tmpl in sorted(src.rglob("*.j2")):
41
+ rel = tmpl.relative_to(src)
42
+ rendered = env.get_template(str(rel)).render(**context)
43
+ target = dest_dir / rel.with_suffix("")
44
+ files.append(write_file(target, rendered, force=force, dry_run=dry_run))
45
+ return files
@@ -0,0 +1,43 @@
1
+ """Core scaffolding generator: app/core/ (config.py + database.py).
2
+
3
+ Files are only generated when they are missing or empty; existing code is
4
+ never overwritten (``force`` does not apply here).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+
11
+ from ..writers import GeneratedFile, write_file
12
+ from .base import render_template
13
+
14
+ CORE_TEMPLATE = "core"
15
+
16
+
17
+ def _has_code(path: Path) -> bool:
18
+ return path.exists() and bool(path.read_text(encoding="utf-8").strip())
19
+
20
+
21
+ def generate_core(
22
+ project_root: Path,
23
+ *,
24
+ dry_run: bool = False,
25
+ ) -> list[GeneratedFile]:
26
+ core_dir = project_root / "app" / "core"
27
+ files: list[GeneratedFile] = []
28
+
29
+ init_py = core_dir / "__init__.py"
30
+ if not init_py.exists():
31
+ files.append(write_file(init_py, "", dry_run=dry_run))
32
+
33
+ config_py = core_dir / "config.py"
34
+ if not _has_code(config_py):
35
+ content = render_template(CORE_TEMPLATE, "config.py.j2", {})
36
+ files.append(write_file(config_py, content, force=True, dry_run=dry_run))
37
+
38
+ database_py = core_dir / "database.py"
39
+ if not _has_code(database_py):
40
+ content = render_template(CORE_TEMPLATE, "database.py.j2", {})
41
+ files.append(write_file(database_py, content, force=True, dry_run=dry_run))
42
+
43
+ return files
@@ -0,0 +1,40 @@
1
+ """Feature-module generator: schemas.py + service.py + router.py + __init__.py.
2
+
3
+ The skeleton only sketches the module's shape (entity, business layer, API
4
+ boundary, shared session dependency) so AI agents can reason about it; real
5
+ business code is filled in by the developer.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ from ..naming import to_kebab, to_pascal, to_plural, to_snake
13
+ from ..writers import GeneratedFile
14
+ from .base import render_tree
15
+
16
+ MODULE_TEMPLATE = "module"
17
+
18
+
19
+ def module_context(feature: str) -> dict[str, str]:
20
+ snake = to_snake(feature)
21
+ return {
22
+ "pascal": to_pascal(feature),
23
+ "snake": snake,
24
+ "kebab": to_kebab(feature),
25
+ "plural": to_plural(snake),
26
+ }
27
+
28
+
29
+ def generate_module(
30
+ feature: str,
31
+ project_root: Path,
32
+ *,
33
+ force: bool = False,
34
+ dry_run: bool = False,
35
+ ) -> list[GeneratedFile]:
36
+ snake = to_snake(feature)
37
+ module_dir = project_root / "app" / "modules" / snake
38
+ return render_tree(
39
+ MODULE_TEMPLATE, module_context(feature), module_dir, force=force, dry_run=dry_run
40
+ )
@@ -0,0 +1,91 @@
1
+ """Module registry: an auto-maintained ``app/modules/__init__.py``.
2
+
3
+ The registry maps each module name to its import path so AI agents (and the
4
+ ``fastgen list`` command) can discover the project's module structure at a
5
+ glance without scanning the filesystem.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ from pathlib import Path
12
+
13
+ from ..naming import to_snake
14
+ from ..writers import GeneratedFile, write_file
15
+
16
+ REGISTRY_HEADER = (
17
+ '"""Module registry - auto-generated by fastgen. Do not edit by hand."""'
18
+ )
19
+
20
+ _MODULES_VAR = "modules"
21
+
22
+
23
+ def registry_path(project_root: Path) -> Path:
24
+ return project_root / "app" / "modules" / "__init__.py"
25
+
26
+
27
+ def read_registry(project_root: Path) -> dict[str, str]:
28
+ """Read the ``modules`` mapping from ``app/modules/__init__.py``."""
29
+ path = registry_path(project_root)
30
+ if not path.exists():
31
+ return {}
32
+ tree = ast.parse(path.read_text(encoding="utf-8"))
33
+ for node in tree.body:
34
+ if isinstance(node, ast.Assign):
35
+ targets = node.targets
36
+ elif isinstance(node, ast.AnnAssign):
37
+ targets = (node.target,) if node.value is not None else ()
38
+ else:
39
+ continue
40
+ if any(isinstance(t, ast.Name) and t.id == _MODULES_VAR for t in targets):
41
+ value = ast.literal_eval(node.value)
42
+ return {name: path for name, path in value.items()}
43
+ return {}
44
+
45
+
46
+ def registry_content(modules: dict[str, str]) -> str:
47
+ body = "\n".join(f' "{name}": "{path}",' for name, path in sorted(modules.items()))
48
+ return (
49
+ f"{REGISTRY_HEADER}\n\n"
50
+ f"{_MODULES_VAR}: dict[str, str] = {{\n{body}\n}}\n\n"
51
+ '__all__ = ["' + _MODULES_VAR + '"]\n'
52
+ )
53
+
54
+
55
+ def register_module(
56
+ project_root: Path,
57
+ feature: str,
58
+ *,
59
+ dry_run: bool = False,
60
+ ) -> GeneratedFile:
61
+ """Add ``feature`` to the registry and rewrite ``app/modules/__init__.py``."""
62
+ name = to_snake(feature)
63
+ modules = read_registry(project_root)
64
+ modules[name] = f"app.modules.{name}"
65
+ content = registry_content(modules)
66
+ path = registry_path(project_root)
67
+ if path.exists() and path.read_text(encoding="utf-8").rstrip("\n") == content.rstrip("\n"):
68
+ return GeneratedFile(path=path, content=content, status="skipped")
69
+ return write_file(path, content, force=True, dry_run=dry_run)
70
+
71
+
72
+ def list_registered(project_root: Path) -> list[tuple[str, str, str]]:
73
+ """Return (name, import path, one-line docstring) for every module."""
74
+ modules = read_registry(project_root)
75
+ rows: list[tuple[str, str, str]] = []
76
+ for name, import_path in modules.items():
77
+ doc = _module_doc(project_root / "app" / "modules" / name / "__init__.py")
78
+ rows.append((name, import_path, doc))
79
+ return rows
80
+
81
+
82
+ def _module_doc(init_file: Path) -> str:
83
+ if not init_file.exists():
84
+ return ""
85
+ try:
86
+ tree = ast.parse(init_file.read_text(encoding="utf-8"))
87
+ except SyntaxError:
88
+ return ""
89
+ doc = ast.get_docstring(tree) or ""
90
+ first_line = doc.strip().splitlines()
91
+ return first_line[0] if first_line else ""
fastgen/naming.py ADDED
@@ -0,0 +1,41 @@
1
+ """Name transformation helpers (snake_case / PascalCase / kebab-case + pluralization)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ _WORD_SPLIT_RE = re.compile(r"[_\-\s]+")
8
+
9
+
10
+ def _split(name: str) -> list[str]:
11
+ parts: list[str] = []
12
+ for chunk in _WORD_SPLIT_RE.split(name):
13
+ if not chunk:
14
+ continue
15
+ parts.extend(re.findall(r"[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|\d+", chunk))
16
+ return parts
17
+
18
+
19
+ def to_snake(name: str) -> str:
20
+ return "_".join(p.lower() for p in _split(name))
21
+
22
+
23
+ def to_pascal(name: str) -> str:
24
+ return "".join(p.capitalize() for p in _split(name))
25
+
26
+
27
+ def to_kebab(name: str) -> str:
28
+ return "-".join(p.lower() for p in _split(name))
29
+
30
+
31
+ def to_plural(word: str) -> str:
32
+ """Simple English pluralization."""
33
+ if word.endswith(("s", "x", "z", "ch", "sh")):
34
+ return f"{word}es"
35
+ if word.endswith("y") and len(word) > 1 and word[-2] not in "aeiou":
36
+ return f"{word[:-1]}ies"
37
+ if word.endswith("f"):
38
+ return f"{word[:-1]}ves"
39
+ if word.endswith("fe"):
40
+ return f"{word[:-2]}ves"
41
+ return f"{word}s"
@@ -0,0 +1,12 @@
1
+ from pydantic_settings import BaseSettings, SettingsConfigDict
2
+
3
+
4
+ class Settings(BaseSettings):
5
+ """Application settings, loaded from environment / .env."""
6
+
7
+ model_config = SettingsConfigDict(env_file=".env", extra="ignore")
8
+
9
+ database_url: str = "sqlite+aiosqlite:///./app.db"
10
+
11
+
12
+ settings = Settings()
@@ -0,0 +1,24 @@
1
+ from collections.abc import AsyncGenerator
2
+
3
+ from sqlalchemy.ext.asyncio import (
4
+ AsyncAttrs,
5
+ AsyncSession,
6
+ async_sessionmaker,
7
+ create_async_engine,
8
+ )
9
+ from sqlalchemy.orm import DeclarativeBase
10
+
11
+ from app.core.config import settings
12
+
13
+
14
+ class Base(AsyncAttrs, DeclarativeBase):
15
+ """Declarative base shared by all ORM models."""
16
+
17
+
18
+ engine = create_async_engine(settings.database_url, echo=False)
19
+ async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
20
+
21
+
22
+ async def get_session() -> AsyncGenerator[AsyncSession, None]:
23
+ async with async_session() as session:
24
+ yield session
@@ -0,0 +1,4 @@
1
+ """{{ pascal }} module."""
2
+ from .router import router
3
+
4
+ __all__ = ["router"]
@@ -0,0 +1,10 @@
1
+ from typing import Annotated
2
+
3
+ from fastapi import APIRouter, Depends
4
+ from sqlalchemy.ext.asyncio import AsyncSession
5
+
6
+ from app.core.database import get_session
7
+
8
+ SessionDep = Annotated[AsyncSession, Depends(get_session)]
9
+
10
+ router = APIRouter(prefix="/{{ plural }}", tags=["{{ plural }}"])
@@ -0,0 +1,5 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class {{ pascal }}(BaseModel):
5
+ pass
@@ -0,0 +1,2 @@
1
+ class {{ pascal }}Service:
2
+ """Business logic layer for {{ kebab }}."""
fastgen/writers.py ADDED
@@ -0,0 +1,43 @@
1
+ """File writing helpers with dry-run and overwrite protection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+
8
+ from rich.console import Console
9
+
10
+ console = Console()
11
+
12
+
13
+ @dataclass
14
+ class GeneratedFile:
15
+ path: Path
16
+ content: str
17
+ status: str = field(default="created")
18
+
19
+
20
+ def write_file(
21
+ path: Path, content: str, *, force: bool = False, dry_run: bool = False
22
+ ) -> GeneratedFile:
23
+ if dry_run:
24
+ return GeneratedFile(path=path, content=content, status="dry-run")
25
+ path.parent.mkdir(parents=True, exist_ok=True)
26
+ if path.exists() and not force:
27
+ return GeneratedFile(path=path, content=content, status="skipped")
28
+ path.write_text(content if content.endswith("\n") else content + "\n")
29
+ return GeneratedFile(path=path, content=content, status="created")
30
+
31
+
32
+ def report(files: list[GeneratedFile], *, dry_run: bool = False) -> None:
33
+ prefix = "[yellow][dry-run][/yellow] " if dry_run else ""
34
+ for f in files:
35
+ status_style = {
36
+ "created": "green",
37
+ "skipped": "yellow",
38
+ "dry-run": "cyan",
39
+ }[f.status]
40
+ if f.status == "skipped":
41
+ console.print(f"{prefix}[{status_style}][skipped][/] {f.path}")
42
+ else:
43
+ console.print(f"{prefix}[{status_style}][{f.status}][/] {f.path}")
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastgen-cli
3
+ Version: 0.4.0
4
+ Summary: FastAPI feature-based module manager (nest-cli style for FastAPI)
5
+ Author-email: YIbaikaishui <162815827+YIbaikaishui@users.noreply.github.com>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: cli,code-generator,fastapi,nest-cli,scaffold
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Software Development :: Code Generators
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: jinja2>=3.1
19
+ Requires-Dist: rich>=13.7
20
+ Requires-Dist: typer>=0.12
21
+ Description-Content-Type: text/markdown
22
+
23
+ # fastgen
24
+
25
+ A `nest cli`-style module manager for FastAPI: it scaffolds a minimal,
26
+ zero-config module skeleton (entity, service, router, shared session
27
+ dependency) and keeps a module registry so AI agents and developers can see
28
+ the project structure at a glance. Fill in the business logic yourself.
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ pip install fastgen-cli
34
+ # or
35
+ uv add fastgen-cli
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```bash
41
+ fastgen --help
42
+
43
+ # Scaffold a feature module and register it
44
+ fastgen make module user
45
+
46
+ # List registered modules and their boundaries
47
+ fastgen list
48
+ ```
49
+
50
+ Both commands support `--dir <root>` (project root, defaults to cwd),
51
+ `--dry-run` (preview without writing) and `--force` (overwrite existing
52
+ skeleton files).
53
+
54
+ Running `fastgen make module <feature>` generates:
55
+
56
+ ```
57
+ app/
58
+ ├── core/ # auto-created on first use
59
+ │ ├── config.py # pydantic-settings Settings (DATABASE_URL in .env)
60
+ │ └── database.py # Base (AsyncAttrs), async engine, get_session dependency
61
+ └── modules/
62
+ ├── __init__.py # module registry (auto-maintained, do not edit)
63
+ └── user/
64
+ ├── __init__.py # re-exports router
65
+ ├── schemas.py # Pydantic entity skeleton: class User(BaseModel): pass
66
+ ├── service.py # business layer boundary: class UserService
67
+ └── router.py # APIRouter + SessionDep (Annotated[AsyncSession, Depends(get_session)])
68
+ ```
69
+
70
+ Existing code is never overwritten: `app/core/config.py` / `database.py` are
71
+ only generated when missing or empty.
72
+
73
+ ## Conventions (fixed)
74
+
75
+ - Modules live in `app/modules/<feature>/`, one business unit per folder.
76
+ - The router exposes `prefix="/<plural>"` and tags; the shared session
77
+ dependency is imported from `app.core.database`.
78
+ - `app/modules/__init__.py` is a fastgen-maintained registry
79
+ (`modules: dict[str, str]`) mapping module name to import path; `fastgen list`
80
+ reads it (and each module's docstring) to show the whole project.
81
+
82
+ ## Roadmap
83
+
84
+ - Phase 1: `make module` (schemas + service skeleton) ✅
85
+ - Phase 2: module registry + `fastgen list` ✅
86
+ - Phase 3: `make resource` (full CRUD router), Alembic migration hints
@@ -0,0 +1,20 @@
1
+ fastgen/__init__.py,sha256=42STGor_9nKYXumfeV5tiyD_M8VdcddX7CEexmibPBk,22
2
+ fastgen/cli.py,sha256=-G3i7J6HgSyreftG43Yoel-xH270D45A2bXJRlhQcAw,2821
3
+ fastgen/naming.py,sha256=yPriPDE1rgtqrZm1cEhfVB-dm3C0FX-YWn64aJz88SI,1105
4
+ fastgen/writers.py,sha256=kawNNLuA0AnRCDL0Rt8xxdGhG3OwzXeMQik2DwzKH8c,1348
5
+ fastgen/generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ fastgen/generators/base.py,sha256=mql3DZdz-AsywBCXzgQTtL5nGxNORBQI0eld-EYjmSY,1376
7
+ fastgen/generators/core.py,sha256=quUG_-71zAb6s4jDy6ccE5NWWbJ4k8mi--Pt3VNpOtI,1274
8
+ fastgen/generators/module.py,sha256=249apegLDxVoSh9d_wmkknWTQ1A9_K6RJEf_lwaTz0c,1096
9
+ fastgen/generators/registry.py,sha256=_ER5EYA0FW4a170dHDiSc_cg3ixaeHdNODBptx47E14,3086
10
+ fastgen/templates/core/config.py.j2,sha256=_mznhIq2ELNyNpuo9L8FOjE4sxNLBXfnnERiJVsIuzY,311
11
+ fastgen/templates/core/database.py.j2,sha256=3YWUKP424lDWVJCQhQqLzLAZ4glBmwZlb5aJ8s2mRIY,627
12
+ fastgen/templates/module/__init__.py.j2,sha256=40K6rOER2Wo-rE31JRikdXf4hMpFn0dnx9K1imWXDTg,76
13
+ fastgen/templates/module/router.py.j2,sha256=l31yT7EYg7nlwcWWXC630aW6nGiCND29dUVZTae5Mdw,287
14
+ fastgen/templates/module/schemas.py.j2,sha256=9CuJoFm18VD9gXKJ6rxNAEUm8Ivjn69CqLreSX_9vcE,73
15
+ fastgen/templates/module/service.py.j2,sha256=JgiKlvBGI6A2KsOEfYsNrTxpQ6ZUB03SpG8FqREmVWw,75
16
+ fastgen_cli-0.4.0.dist-info/METADATA,sha256=FbjJQ7FDIUvwjTSNHPUGeCuFOzLp1tFuCxxLnY4vO0Y,3050
17
+ fastgen_cli-0.4.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
18
+ fastgen_cli-0.4.0.dist-info/entry_points.txt,sha256=Y_2HaNCGPIGsXOt83jVeCAsXF3sDUsiANHbs_Lya_xI,44
19
+ fastgen_cli-0.4.0.dist-info/licenses/LICENSE,sha256=fHNgnZDcmGAp7TgjvoB_62S0vvqUsT3YNwu3uV5-BXg,1069
20
+ fastgen_cli-0.4.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ fastgen = fastgen.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 YIbaikaishui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.