fastapi-templatekit 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.
Files changed (47) hide show
  1. fastapi_templatekit/__init__.py +1 -0
  2. fastapi_templatekit/cli.py +33 -0
  3. fastapi_templatekit/commands/__init__.py +1 -0
  4. fastapi_templatekit/commands/addhealthprobes.py +68 -0
  5. fastapi_templatekit/commands/help.py +70 -0
  6. fastapi_templatekit/commands/project.py +22 -0
  7. fastapi_templatekit/commands/rendering.py +15 -0
  8. fastapi_templatekit/commands/startapp.py +170 -0
  9. fastapi_templatekit/commands/startproject.py +82 -0
  10. fastapi_templatekit/commands/validation.py +17 -0
  11. fastapi_templatekit/templates/__init__.py +1 -0
  12. fastapi_templatekit/templates/app/__init__.py.tpl +1 -0
  13. fastapi_templatekit/templates/app/endpoints/__init__.py.tpl +1 -0
  14. fastapi_templatekit/templates/app/endpoints/api.py.tpl +14 -0
  15. fastapi_templatekit/templates/app/models/__init__.py.tpl +1 -0
  16. fastapi_templatekit/templates/app/router.py.tpl +9 -0
  17. fastapi_templatekit/templates/app/schemas/__init__.py.tpl +1 -0
  18. fastapi_templatekit/templates/app/schemas/validator.py.tpl +7 -0
  19. fastapi_templatekit/templates/app/service/__init__.py.tpl +1 -0
  20. fastapi_templatekit/templates/app/service/app_service.py.tpl +5 -0
  21. fastapi_templatekit/templates/app/websocket/__init__.py.tpl +1 -0
  22. fastapi_templatekit/templates/app/websocket/router.py.tpl +25 -0
  23. fastapi_templatekit/templates/healthprobes/__init__.py.tpl +1 -0
  24. fastapi_templatekit/templates/healthprobes/endpoints/__init__.py.tpl +1 -0
  25. fastapi_templatekit/templates/healthprobes/endpoints/api.py.tpl +19 -0
  26. fastapi_templatekit/templates/healthprobes/models/__init__.py.tpl +1 -0
  27. fastapi_templatekit/templates/healthprobes/router.py.tpl +9 -0
  28. fastapi_templatekit/templates/healthprobes/schemas/__init__.py.tpl +1 -0
  29. fastapi_templatekit/templates/healthprobes/schemas/validator.py.tpl +7 -0
  30. fastapi_templatekit/templates/healthprobes/service/__init__.py.tpl +1 -0
  31. fastapi_templatekit/templates/healthprobes/service/healthprobes_service.py.tpl +5 -0
  32. fastapi_templatekit/templates/project/README.md.tpl +12 -0
  33. fastapi_templatekit/templates/project/env.example.tpl +4 -0
  34. fastapi_templatekit/templates/project/fastapi_templatekit.toml.tpl +5 -0
  35. fastapi_templatekit/templates/project/gitignore.tpl +9 -0
  36. fastapi_templatekit/templates/project/package/__init__.py.tpl +1 -0
  37. fastapi_templatekit/templates/project/package/cli.py.tpl +88 -0
  38. fastapi_templatekit/templates/project/package/config.py.tpl +15 -0
  39. fastapi_templatekit/templates/project/package/main.py.tpl +15 -0
  40. fastapi_templatekit/templates/project/package/router.py.tpl +6 -0
  41. fastapi_templatekit/templates/project/pyproject.toml.tpl +26 -0
  42. fastapi_templatekit-0.1.0.dist-info/METADATA +163 -0
  43. fastapi_templatekit-0.1.0.dist-info/RECORD +47 -0
  44. fastapi_templatekit-0.1.0.dist-info/WHEEL +5 -0
  45. fastapi_templatekit-0.1.0.dist-info/entry_points.txt +2 -0
  46. fastapi_templatekit-0.1.0.dist-info/licenses/LICENSE +21 -0
  47. fastapi_templatekit-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ from fastapi_templatekit.commands.addhealthprobes import add_addhealthprobes_parser
6
+ from fastapi_templatekit.commands.help import add_help_parser
7
+ from fastapi_templatekit.commands.startapp import add_startapp_parser
8
+ from fastapi_templatekit.commands.startproject import add_startproject_parser
9
+
10
+
11
+ def build_parser() -> argparse.ArgumentParser:
12
+ parser = argparse.ArgumentParser(
13
+ prog="fastapi-templatekit",
14
+ description="Create FastAPI projects and apps.",
15
+ )
16
+ subparsers = parser.add_subparsers(dest="command", required=True)
17
+
18
+ add_startproject_parser(subparsers)
19
+ add_startapp_parser(subparsers)
20
+ add_addhealthprobes_parser(subparsers)
21
+ add_help_parser(subparsers)
22
+
23
+ return parser
24
+
25
+
26
+ def main() -> None:
27
+ parser = build_parser()
28
+ args = parser.parse_args()
29
+ args.handler(args)
30
+
31
+
32
+ if __name__ == "__main__":
33
+ main()
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+
6
+ from fastapi_templatekit.commands.project import load_project_config
7
+ from fastapi_templatekit.commands.rendering import render_template
8
+
9
+
10
+ HEALTHPROBES_APP_NAME = "healthprobes"
11
+
12
+ HEALTHPROBES_TEMPLATES = (
13
+ ("healthprobes/__init__.py.tpl", "healthprobes/__init__.py"),
14
+ ("healthprobes/router.py.tpl", "healthprobes/router.py"),
15
+ ("healthprobes/endpoints/__init__.py.tpl", "healthprobes/endpoints/__init__.py"),
16
+ ("healthprobes/endpoints/api.py.tpl", "healthprobes/endpoints/api.py"),
17
+ ("healthprobes/schemas/__init__.py.tpl", "healthprobes/schemas/__init__.py"),
18
+ ("healthprobes/schemas/validator.py.tpl", "healthprobes/schemas/validator.py"),
19
+ ("healthprobes/service/__init__.py.tpl", "healthprobes/service/__init__.py"),
20
+ ("healthprobes/service/healthprobes_service.py.tpl", "healthprobes/service/healthprobes_service.py"),
21
+ ("healthprobes/models/__init__.py.tpl", "healthprobes/models/__init__.py"),
22
+ )
23
+
24
+
25
+ def add_addhealthprobes_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
26
+ parser = subparsers.add_parser(
27
+ "addhealthprobes",
28
+ help="Add healthz and livez endpoints to an existing generated project.",
29
+ )
30
+ parser.add_argument(
31
+ "--force",
32
+ action="store_true",
33
+ help="Overwrite existing healthprobes files without prompting.",
34
+ )
35
+ parser.set_defaults(handler=handle_addhealthprobes)
36
+
37
+
38
+ def handle_addhealthprobes(args: argparse.Namespace) -> None:
39
+ project_root = Path.cwd()
40
+ project = load_project_config(project_root)
41
+ apps_dir = project_root / project.get("apps_dir", ".")
42
+ app_dir = apps_dir / HEALTHPROBES_APP_NAME
43
+
44
+ context = {
45
+ "app_name": HEALTHPROBES_APP_NAME,
46
+ "route_prefix": HEALTHPROBES_APP_NAME,
47
+ }
48
+ destinations = [
49
+ apps_dir / destination_pattern.format(**context)
50
+ for _, destination_pattern in HEALTHPROBES_TEMPLATES
51
+ ]
52
+ existing_paths = [destination for destination in destinations if destination.exists()]
53
+
54
+ if existing_paths and not args.force:
55
+ conflicts = "\n".join(f" {path}" for path in existing_paths)
56
+ print(f"Healthprobes files already exist:\n{conflicts}")
57
+ answer = input("Overwrite these files? [y/N]: ").strip().lower()
58
+ if answer not in {"y", "yes"}:
59
+ raise SystemExit("Aborted.")
60
+
61
+ for template_path, destination_pattern in HEALTHPROBES_TEMPLATES:
62
+ destination = apps_dir / destination_pattern.format(**context)
63
+ render_template(template_path, destination, context)
64
+
65
+ print(f"Created health probes app at {app_dir}")
66
+ print("Register the router in your project router:")
67
+ print(" from healthprobes.router import router as healthprobes_router")
68
+ print(" api_router.include_router(healthprobes_router, tags=[\"healthprobes\"])")
@@ -0,0 +1,70 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+
6
+ HELP_TEXT = """fastapi-templatekit
7
+
8
+ Available commands:
9
+
10
+ startproject <name> [directory]
11
+ Create a new FastAPI project.
12
+
13
+ Examples:
14
+ fastapi-templatekit startproject myproject
15
+ fastapi-templatekit startproject myproject .
16
+ fastapi-templatekit startproject myproject ../services/myproject
17
+
18
+ Details:
19
+ - Without directory, creates ./<name>/.
20
+ - With '.', creates project files in the current directory.
21
+ - With a custom directory, creates project files at that path.
22
+ - If generated files already exist, asks before overwriting.
23
+ - Use --force to overwrite generated files without prompting.
24
+
25
+ startapp <name>
26
+ Create a root-level app inside an existing generated project.
27
+
28
+ Example:
29
+ fastapi-templatekit startapp users
30
+ fastapi-templatekit startapp users --with-websockets --with-database
31
+
32
+ Details:
33
+ - Must be run from a directory containing fastapi_templatekit.toml.
34
+ - Creates endpoints, schemas, service, and router.py.
35
+ - Includes a hello-world GET endpoint.
36
+ - Asks whether to include websockets.
37
+ - Asks whether to include database/tables.
38
+ - Use --with-websockets to add websocket scaffolding without prompting for it.
39
+ - Use --with-database to add the models folder without prompting for it.
40
+ - Prints the router registration lines for your project router.py.
41
+
42
+ addhealthprobes
43
+ Create a root-level healthprobes app inside an existing generated project.
44
+
45
+ Example:
46
+ fastapi-templatekit addhealthprobes
47
+
48
+ Details:
49
+ - Must be run from a directory containing fastapi_templatekit.toml.
50
+ - Creates /healthz and /livez GET endpoints.
51
+ - Prints the router registration lines for your project router.py.
52
+ - Use --force to overwrite generated healthprobes files without prompting.
53
+
54
+ help
55
+ Show this command summary.
56
+
57
+ Use 'fastapi-templatekit <command> --help' for argparse usage details.
58
+ """
59
+
60
+
61
+ def add_help_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
62
+ parser = subparsers.add_parser(
63
+ "help",
64
+ help="Show available commands and details.",
65
+ )
66
+ parser.set_defaults(handler=handle_help)
67
+
68
+
69
+ def handle_help(_: argparse.Namespace) -> None:
70
+ print(HELP_TEXT)
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ import tomllib
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+
8
+ def load_project_config(project_root: Path) -> dict[str, Any]:
9
+ marker_path = project_root / "fastapi_templatekit.toml"
10
+
11
+ if not marker_path.exists():
12
+ raise SystemExit(
13
+ "Error: command must be run inside a fastapi-templatekit project.\n"
14
+ "Run: fastapi-templatekit startproject <project_name>"
15
+ )
16
+
17
+ marker = tomllib.loads(marker_path.read_text(encoding="utf-8"))
18
+ project = marker.get("project", {})
19
+ if not isinstance(project, dict):
20
+ raise SystemExit(f"Error: invalid project config: {marker_path}")
21
+
22
+ return project
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import resources
4
+ from pathlib import Path
5
+ from string import Template
6
+
7
+
8
+ TEMPLATE_ROOT = "fastapi_templatekit.templates"
9
+
10
+
11
+ def render_template(template_path: str, destination: Path, context: dict[str, str]) -> None:
12
+ template = resources.files(TEMPLATE_ROOT).joinpath(template_path)
13
+ content = Template(template.read_text(encoding="utf-8")).safe_substitute(context)
14
+ destination.parent.mkdir(parents=True, exist_ok=True)
15
+ destination.write_text(content, encoding="utf-8")
@@ -0,0 +1,170 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import tomllib
5
+ from pathlib import Path
6
+
7
+ from fastapi_templatekit.commands.project import load_project_config
8
+ from fastapi_templatekit.commands.rendering import render_template
9
+ from fastapi_templatekit.commands.validation import normalize_package_name, validate_package_name
10
+
11
+
12
+ BASE_APP_TEMPLATES = (
13
+ ("app/__init__.py.tpl", "{app_name}/__init__.py"),
14
+ ("app/router.py.tpl", "{app_name}/router.py"),
15
+ ("app/endpoints/__init__.py.tpl", "{app_name}/endpoints/__init__.py"),
16
+ ("app/endpoints/api.py.tpl", "{app_name}/endpoints/api.py"),
17
+ ("app/schemas/__init__.py.tpl", "{app_name}/schemas/__init__.py"),
18
+ ("app/schemas/validator.py.tpl", "{app_name}/schemas/validator.py"),
19
+ ("app/service/__init__.py.tpl", "{app_name}/service/__init__.py"),
20
+ ("app/service/app_service.py.tpl", "{app_name}/service/{app_name}_service.py"),
21
+ )
22
+
23
+ DATABASE_APP_TEMPLATES = (
24
+ ("app/models/__init__.py.tpl", "{app_name}/models/__init__.py"),
25
+ )
26
+
27
+ WEBSOCKET_APP_TEMPLATES = (
28
+ ("app/websocket/__init__.py.tpl", "{app_name}/websocket/__init__.py"),
29
+ ("app/websocket/router.py.tpl", "{app_name}/websocket/router.py"),
30
+ )
31
+
32
+ WEBSOCKET_DEPENDENCY = "fastapi-websockets"
33
+ WEBSOCKET_CONFIG_DOCS_URL = "https://github.com/Amogha-Hegde/fastapi-websockets"
34
+
35
+
36
+ def add_startapp_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
37
+ parser = subparsers.add_parser(
38
+ "startapp",
39
+ help="Create a FastAPI app inside an existing generated project.",
40
+ )
41
+ parser.add_argument("name", help="App name.")
42
+ parser.add_argument(
43
+ "--with-websockets",
44
+ action="store_true",
45
+ help="Add a websocket folder with a sample websocket route.",
46
+ )
47
+ parser.add_argument(
48
+ "--with-database",
49
+ action="store_true",
50
+ help="Add a models folder for database tables.",
51
+ )
52
+ parser.set_defaults(handler=handle_startapp)
53
+
54
+
55
+ def handle_startapp(args: argparse.Namespace) -> None:
56
+ project_root = Path.cwd()
57
+ project = load_project_config(project_root)
58
+ apps_dir = project_root / project.get("apps_dir", ".")
59
+
60
+ app_name = validate_package_name(
61
+ normalize_package_name(args.name),
62
+ "App name",
63
+ )
64
+ app_dir = apps_dir / app_name
65
+
66
+ if app_dir.exists():
67
+ raise SystemExit(f"Error: app already exists: {app_dir}")
68
+
69
+ include_websockets = args.with_websockets or ask_yes_no("Do you want to include websockets?")
70
+ include_database = args.with_database or ask_yes_no("Do you want to include database/tables?")
71
+
72
+ context = {
73
+ "app_name": app_name,
74
+ "app_class_name": "".join(part.capitalize() for part in app_name.split("_")),
75
+ "route_prefix": app_name.replace("_", "-"),
76
+ }
77
+
78
+ templates = list(BASE_APP_TEMPLATES)
79
+ if include_database:
80
+ templates.extend(DATABASE_APP_TEMPLATES)
81
+ if include_websockets:
82
+ templates.extend(WEBSOCKET_APP_TEMPLATES)
83
+
84
+ for template_path, destination_pattern in templates:
85
+ destination = apps_dir / destination_pattern.format(**context)
86
+ render_template(template_path, destination, context)
87
+
88
+ if include_websockets:
89
+ ensure_project_dependency(project_root, WEBSOCKET_DEPENDENCY)
90
+
91
+ print(f"Created FastAPI app '{app_name}' at {app_dir}")
92
+ print("Register the router in your project API router:")
93
+ print(f" from {app_name}.router import router as {app_name}_router")
94
+ print(f" api_router.include_router({app_name}_router, prefix=\"/{context['route_prefix']}\", tags=[\"{app_name}\"])")
95
+ if include_websockets:
96
+ print("Register the websocket router in your project API router:")
97
+ print(f" from {app_name}.websocket.router import router as {app_name}_websocket_router")
98
+ print(f" api_router.include_router({app_name}_websocket_router, prefix=\"/{context['route_prefix']}\")")
99
+ print(
100
+ "Info: for websocket-related environment configuration, refer to "
101
+ f"{WEBSOCKET_CONFIG_DOCS_URL}"
102
+ )
103
+
104
+
105
+ def ask_yes_no(question: str) -> bool:
106
+ try:
107
+ answer = input(f"{question} [y/N]: ").strip().lower()
108
+ except EOFError:
109
+ return False
110
+ return answer in {"y", "yes"}
111
+
112
+
113
+ def ensure_project_dependency(project_root: Path, dependency: str) -> None:
114
+ pyproject_path = project_root / "pyproject.toml"
115
+ if not pyproject_path.exists():
116
+ raise SystemExit(f"Error: missing project config: {pyproject_path}")
117
+
118
+ content = pyproject_path.read_text(encoding="utf-8")
119
+ parsed = tomllib.loads(content)
120
+ dependencies = parsed.get("project", {}).get("dependencies", [])
121
+ if not isinstance(dependencies, list):
122
+ dependencies = []
123
+
124
+ dependency_name = normalize_dependency_name(dependency)
125
+ if any(normalize_dependency_name(item) == dependency_name for item in dependencies):
126
+ return
127
+
128
+ lines = content.splitlines(keepends=True)
129
+ dependencies_start = next(
130
+ (index for index, line in enumerate(lines) if line.strip() == "dependencies = ["),
131
+ None,
132
+ )
133
+
134
+ if dependencies_start is None:
135
+ project_start = next(
136
+ (index for index, line in enumerate(lines) if line.strip() == "[project]"),
137
+ None,
138
+ )
139
+ if project_start is None:
140
+ raise SystemExit(f"Error: invalid project config: {pyproject_path}")
141
+ lines[project_start + 1 : project_start + 1] = [
142
+ "dependencies = [\n",
143
+ f' "{dependency}",\n',
144
+ "]\n",
145
+ ]
146
+ pyproject_path.write_text("".join(lines), encoding="utf-8")
147
+ return
148
+
149
+ dependencies_end = next(
150
+ (
151
+ index
152
+ for index in range(dependencies_start + 1, len(lines))
153
+ if lines[index].strip() == "]"
154
+ ),
155
+ None,
156
+ )
157
+ if dependencies_end is None: # pragma: no cover
158
+ raise SystemExit(f"Error: invalid dependencies array: {pyproject_path}")
159
+
160
+ lines.insert(dependencies_end, f' "{dependency}",\n')
161
+ pyproject_path.write_text("".join(lines), encoding="utf-8")
162
+
163
+
164
+ def normalize_dependency_name(dependency: object) -> str:
165
+ if not isinstance(dependency, str):
166
+ return ""
167
+ name = dependency
168
+ for separator in ("[", "<", ">", "=", "~", "!", ";"):
169
+ name = name.split(separator, 1)[0]
170
+ return name.strip().lower().replace("_", "-")
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+
6
+ from fastapi_templatekit.commands.rendering import render_template
7
+ from fastapi_templatekit.commands.validation import normalize_package_name, validate_package_name
8
+
9
+
10
+ PROJECT_TEMPLATES = (
11
+ ("project/pyproject.toml.tpl", "pyproject.toml"),
12
+ ("project/gitignore.tpl", ".gitignore"),
13
+ ("project/fastapi_templatekit.toml.tpl", "fastapi_templatekit.toml"),
14
+ ("project/env.example.tpl", ".env.example"),
15
+ ("project/README.md.tpl", "README.md"),
16
+ ("project/package/__init__.py.tpl", "{package_name}/__init__.py"),
17
+ ("project/package/cli.py.tpl", "{package_name}/cli.py"),
18
+ ("project/package/main.py.tpl", "{package_name}/main.py"),
19
+ ("project/package/config.py.tpl", "{package_name}/config.py"),
20
+ ("project/package/router.py.tpl", "{package_name}/router.py"),
21
+ )
22
+
23
+
24
+ def add_startproject_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
25
+ parser = subparsers.add_parser(
26
+ "startproject",
27
+ help="Create a new FastAPI project.",
28
+ )
29
+ parser.add_argument("name", help="Project name.")
30
+ parser.add_argument(
31
+ "directory",
32
+ nargs="?",
33
+ help="Optional destination directory. Use '.' to create files in the current directory.",
34
+ )
35
+ parser.add_argument(
36
+ "--force",
37
+ action="store_true",
38
+ help="Overwrite existing project files without prompting.",
39
+ )
40
+ parser.set_defaults(handler=handle_startproject)
41
+
42
+
43
+ def handle_startproject(args: argparse.Namespace) -> None:
44
+ project_name = args.name.strip()
45
+ package_name = validate_package_name(
46
+ normalize_package_name(project_name),
47
+ "Project name",
48
+ )
49
+ target_dir = Path(args.directory or project_name).resolve()
50
+
51
+ if target_dir.exists() and not target_dir.is_dir():
52
+ raise SystemExit(f"Error: target path exists and is not a directory: {target_dir}")
53
+
54
+ context = {
55
+ "project_name": project_name,
56
+ "package_name": package_name,
57
+ }
58
+
59
+ destinations = [
60
+ target_dir / destination_pattern.format(**context)
61
+ for _, destination_pattern in PROJECT_TEMPLATES
62
+ ]
63
+ existing_paths = [destination for destination in destinations if destination.exists()]
64
+
65
+ if existing_paths and not args.force:
66
+ conflicts = "\n".join(f" {path}" for path in existing_paths)
67
+ print(f"Project files already exist:\n{conflicts}")
68
+ answer = input("Overwrite these files? [y/N]: ").strip().lower()
69
+ if answer not in {"y", "yes"}:
70
+ raise SystemExit("Aborted.")
71
+
72
+ for template_path, destination_pattern in PROJECT_TEMPLATES:
73
+ destination = target_dir / destination_pattern.format(**context)
74
+ render_template(template_path, destination, context)
75
+
76
+ print(f"Created FastAPI project '{project_name}' at {target_dir}")
77
+ print(f"Next steps:")
78
+ if target_dir != Path.cwd().resolve():
79
+ print(f" cd {target_dir}")
80
+ print(f" uv sync")
81
+ print(f" uv run uvicorn {package_name}.main:app --reload")
82
+ print(f" uv run {package_name} urls")
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ import keyword
4
+ import re
5
+
6
+
7
+ PACKAGE_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
8
+
9
+
10
+ def normalize_package_name(name: str) -> str:
11
+ return name.strip().replace("-", "_").replace(" ", "_").lower()
12
+
13
+
14
+ def validate_package_name(name: str, label: str) -> str:
15
+ if not PACKAGE_RE.match(name) or keyword.iskeyword(name):
16
+ raise ValueError(f"{label} must be a valid Python package name.")
17
+ return name
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi import APIRouter
4
+
5
+ from $app_name.schemas.validator import HelloWorldResponse
6
+ from $app_name.service.${app_name}_service import get_hello_world_message
7
+
8
+
9
+ router = APIRouter()
10
+
11
+
12
+ @router.get("/", response_model=HelloWorldResponse)
13
+ async def hello_world() -> HelloWorldResponse:
14
+ return HelloWorldResponse(message=get_hello_world_message())
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi import APIRouter
4
+
5
+ from $app_name.endpoints.api import router as api_router
6
+
7
+
8
+ router = APIRouter()
9
+ router.include_router(api_router)
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class HelloWorldResponse(BaseModel):
7
+ message: str
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ def get_hello_world_message() -> str:
5
+ return "Hello from $app_name"
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi import APIRouter, WebSocket
4
+ from fastapi_websockets import AsyncWebSocketConsumer, get_channel_layer
5
+
6
+
7
+ router = APIRouter()
8
+ channel_layer = get_channel_layer()
9
+
10
+
11
+ class EchoConsumer(AsyncWebSocketConsumer):
12
+ async def connect(self) -> None:
13
+ await self.accept()
14
+
15
+ async def receive_text(self, text_data: str) -> None:
16
+ await self.send_json({"message": text_data})
17
+
18
+ async def disconnect(self, close_code: int | None) -> None:
19
+ print("WebSocket disconnected:", close_code)
20
+
21
+
22
+ @router.websocket("/ws")
23
+ async def websocket_endpoint(websocket: WebSocket) -> None:
24
+ consumer = EchoConsumer(layer=channel_layer)
25
+ await consumer(websocket)
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi import APIRouter
4
+
5
+ from healthprobes.schemas.validator import HealthProbeResponse
6
+ from healthprobes.service.healthprobes_service import get_health_status
7
+
8
+
9
+ router = APIRouter()
10
+
11
+
12
+ @router.get("/healthz", response_model=HealthProbeResponse)
13
+ async def healthz() -> HealthProbeResponse:
14
+ return HealthProbeResponse(status=get_health_status())
15
+
16
+
17
+ @router.get("/livez", response_model=HealthProbeResponse)
18
+ async def livez() -> HealthProbeResponse:
19
+ return HealthProbeResponse(status=get_health_status())
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi import APIRouter
4
+
5
+ from healthprobes.endpoints.api import router as api_router
6
+
7
+
8
+ router = APIRouter()
9
+ router.include_router(api_router)
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class HealthProbeResponse(BaseModel):
7
+ status: str
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ def get_health_status() -> str:
5
+ return "ok"
@@ -0,0 +1,12 @@
1
+ # $project_name
2
+
3
+ ```bash
4
+ uv sync
5
+ uv run uvicorn $package_name.main:app --reload
6
+ ```
7
+
8
+ List registered HTTP and websocket routes:
9
+
10
+ ```bash
11
+ uv run $package_name urls
12
+ ```
@@ -0,0 +1,4 @@
1
+ APP_NAME=$project_name
2
+ APP_VERSION=0.1.0
3
+ API_PREFIX=/api/v1
4
+ DEBUG=false
@@ -0,0 +1,5 @@
1
+ [project]
2
+ name = "$project_name"
3
+ package = "$package_name"
4
+ template_version = "0.1.0"
5
+ apps_dir = "."
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .mypy_cache/
8
+ dist/
9
+ build/
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from collections.abc import Iterable
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from fastapi.routing import APIRoute, APIWebSocketRoute
9
+ from starlette.routing import Mount, Route, WebSocketRoute
10
+
11
+ project_root = str(Path.cwd())
12
+ if project_root not in sys.path:
13
+ sys.path.insert(0, project_root)
14
+
15
+ from $package_name.main import app as fastapi_app
16
+
17
+
18
+ app = typer.Typer(help="Project management commands.")
19
+
20
+
21
+ @app.callback()
22
+ def main() -> None:
23
+ """Project management commands."""
24
+
25
+
26
+ @app.command("urls")
27
+ def discover_urls() -> None:
28
+ """List HTTP and websocket routes registered on the FastAPI app."""
29
+ rows = list(iter_routes(fastapi_app.routes))
30
+
31
+ if not rows:
32
+ typer.echo("No routes found.")
33
+ return
34
+
35
+ protocol_width = max(len(row[0]) for row in rows + [("Protocol", "", "")])
36
+ methods_width = max(len(row[1]) for row in rows + [("Protocol", "Methods", "")])
37
+ path_width = max(len(row[2]) for row in rows + [("Protocol", "Methods", "Path")])
38
+
39
+ typer.echo(
40
+ f"{'Protocol'.ljust(protocol_width)} "
41
+ f"{'Methods'.ljust(methods_width)} "
42
+ f"{'Path'.ljust(path_width)}"
43
+ )
44
+ typer.echo(
45
+ f"{'-' * protocol_width} "
46
+ f"{'-' * methods_width} "
47
+ f"{'-' * path_width}"
48
+ )
49
+
50
+ for protocol, methods, path in rows:
51
+ typer.echo(
52
+ f"{protocol.ljust(protocol_width)} "
53
+ f"{methods.ljust(methods_width)} "
54
+ f"{path.ljust(path_width)}"
55
+ )
56
+
57
+
58
+ def iter_routes(routes: Iterable[Route], prefix: str = "") -> Iterable[tuple[str, str, str]]:
59
+ for route in routes:
60
+ path = normalize_path(prefix, getattr(route, "path", ""))
61
+
62
+ if isinstance(route, APIRoute):
63
+ methods = ", ".join(sorted(route.methods or []))
64
+ yield ("HTTP", methods, path)
65
+ continue
66
+
67
+ if isinstance(route, Route):
68
+ methods = ", ".join(sorted(route.methods or []))
69
+ yield ("HTTP", methods, path)
70
+ continue
71
+
72
+ if isinstance(route, APIWebSocketRoute | WebSocketRoute):
73
+ yield ("WEBSOCKET", "WS", path)
74
+ continue
75
+
76
+ if isinstance(route, Mount):
77
+ yield from iter_routes(route.routes, path)
78
+
79
+
80
+ def normalize_path(prefix: str, path: str) -> str:
81
+ combined = f"{prefix.rstrip('/')}/{path.lstrip('/')}"
82
+ if not combined.startswith("/"):
83
+ combined = f"/{combined}"
84
+ return combined.rstrip("/") or "/"
85
+
86
+
87
+ if __name__ == "__main__":
88
+ app()
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic_settings import BaseSettings, SettingsConfigDict
4
+
5
+
6
+ class Settings(BaseSettings):
7
+ app_name: str = "$project_name"
8
+ app_version: str = "0.1.0"
9
+ api_prefix: str = "/api/v1"
10
+ debug: bool = False
11
+
12
+ model_config = SettingsConfigDict(env_file=".env", extra="ignore")
13
+
14
+
15
+ settings = Settings()
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi import FastAPI
4
+
5
+ from $package_name.config import settings
6
+ from $package_name.router import api_router
7
+
8
+
9
+ app = FastAPI(
10
+ title=settings.app_name,
11
+ version=settings.app_version,
12
+ debug=settings.debug,
13
+ )
14
+
15
+ app.include_router(api_router, prefix=settings.api_prefix)
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi import APIRouter
4
+
5
+
6
+ api_router = APIRouter()
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "$project_name"
7
+ version = "0.1.0"
8
+ description = "FastAPI application."
9
+ requires-python = ">=3.11"
10
+ dependencies = [
11
+ "fastapi",
12
+ "typer",
13
+ "uvicorn[standard]",
14
+ "pydantic-settings",
15
+ ]
16
+
17
+ [project.scripts]
18
+ $package_name = "$package_name.cli:app"
19
+
20
+ [dependency-groups]
21
+ dev = [
22
+ "pytest",
23
+ ]
24
+
25
+ [tool.setuptools.packages.find]
26
+ include = ["$package_name*"]
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-templatekit
3
+ Version: 0.1.0
4
+ Summary: FastAPI project generator with a root-level, Django-style app layout
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Dynamic: license-file
9
+
10
+ # fastapi-templatekit
11
+
12
+ [![Tests](https://github.com/Amogha-Hegde/fastapi-templatekit/actions/workflows/ci.yml/badge.svg)](https://github.com/Amogha-Hegde/fastapi-templatekit/actions/workflows/ci.yml)
13
+ [![Coverage](https://codecov.io/gh/Amogha-Hegde/fastapi-templatekit/branch/main/graph/badge.svg)](https://codecov.io/gh/Amogha-Hegde/fastapi-templatekit)
14
+ [![PyPI version](https://img.shields.io/pypi/v/fastapi-templatekit.svg)](https://pypi.org/project/fastapi-templatekit/)
15
+ [![Python versions](https://img.shields.io/pypi/pyversions/fastapi-templatekit.svg)](https://pypi.org/project/fastapi-templatekit/)
16
+ [![PyPI downloads](https://static.pepy.tech/badge/fastapi-templatekit)](https://pepy.tech/project/fastapi-templatekit)
17
+
18
+ A FastAPI TemplateKit with a root-level, Django-style app layout.
19
+
20
+ ## CLI
21
+
22
+ Create a new project without installing this template into that project's
23
+ environment:
24
+
25
+ ```bash
26
+ uvx fastapi-templatekit startproject myproject
27
+ cd myproject
28
+ uvx fastapi-templatekit startapp users
29
+ ```
30
+
31
+ Create the project files directly in the current directory:
32
+
33
+ ```bash
34
+ uvx fastapi-templatekit startproject myproject .
35
+ ```
36
+
37
+ If generated files already exist, the CLI asks before overwriting them. Use
38
+ `--force` to overwrite without a prompt:
39
+
40
+ ```bash
41
+ uvx fastapi-templatekit startproject myproject . --force
42
+ ```
43
+
44
+ Create the project at a custom path:
45
+
46
+ ```bash
47
+ uvx fastapi-templatekit startproject myproject ../services/myproject
48
+ ```
49
+
50
+ For local development from this repository:
51
+
52
+ ```bash
53
+ uvx --from . fastapi-templatekit startproject myproject
54
+ ```
55
+
56
+ Generated projects include a Typer management command to discover registered
57
+ HTTP and websocket routes:
58
+
59
+ ```bash
60
+ uv run myproject urls
61
+ ```
62
+
63
+ Show available commands and details:
64
+
65
+ ```bash
66
+ uvx fastapi-templatekit help
67
+ ```
68
+
69
+ Add health probes to an existing generated project:
70
+
71
+ ```bash
72
+ uvx fastapi-templatekit addhealthprobes
73
+ ```
74
+
75
+ Create an app with optional websocket and database scaffolding:
76
+
77
+ ```bash
78
+ uvx fastapi-templatekit startapp users
79
+ uvx fastapi-templatekit startapp users --with-websockets --with-database
80
+ ```
81
+
82
+ Generated projects keep the main router beside `main.py`:
83
+
84
+ ```text
85
+ myproject/
86
+ ├── pyproject.toml
87
+ ├── fastapi_templatekit.toml
88
+ ├── myproject/
89
+ │ ├── __init__.py
90
+ │ ├── main.py
91
+ │ ├── config.py
92
+ │ └── router.py
93
+ └── users/
94
+ ├── __init__.py
95
+ ├── router.py
96
+ ├── endpoints/
97
+ │ ├── __init__.py
98
+ │ └── api.py
99
+ ├── schemas/
100
+ │ ├── __init__.py
101
+ │ └── validator.py
102
+ ├── service/
103
+ │ ├── __init__.py
104
+ │ └── users_service.py
105
+ ├── models/ # created only with database/tables enabled
106
+ │ └── __init__.py
107
+ └── websocket/ # created only with websockets enabled
108
+ ├── __init__.py
109
+ └── router.py
110
+ ```
111
+
112
+ ## Structure
113
+
114
+ ```text
115
+ .
116
+ ├── api/ # top-level router registration
117
+ ├── health/ # example domain app at project root
118
+ ├── main/
119
+ │ ├── config.py # settings and config live here
120
+ │ └── app.py # FastAPI declaration lives here
121
+ └── pyproject.toml
122
+ ```
123
+
124
+ This follows the same domain-first idea from `fastapi-best-practices`, but without a `src/` directory.
125
+ Feature apps and the shared API router live directly under the project root, similar to Django apps.
126
+
127
+ ## Run
128
+
129
+ ```bash
130
+ uv run uvicorn main.app:app --reload
131
+ ```
132
+
133
+ ## WebSockets
134
+
135
+ WebSocket routes are mounted under the same API prefix, so the default endpoint is:
136
+
137
+ ```text
138
+ /api/v1/ws/{room_name}
139
+ ```
140
+
141
+ There is also a built-in browser test page at:
142
+
143
+ ```text
144
+ /api/v1/ws/test
145
+ ```
146
+
147
+ This app uses `fastapi-websockets` with the package's environment-based channel-layer loader.
148
+ For websocket-related environment configuration, refer to https://github.com/Amogha-Hegde/fastapi-websockets.
149
+
150
+ Tests force `FASTAPI_WEBSOCKETS_BACKEND=inmemory` so they do not depend on a running database.
151
+
152
+ Example session:
153
+
154
+ ```text
155
+ connect ws://127.0.0.1:8000/api/v1/ws/demo
156
+ receive {"event":"connected","room":"demo",...}
157
+ send {"sender":"alice","message":"hello"}
158
+ receive {"event":"message","room":"demo","sender":"alice","message":"hello"}
159
+ ```
160
+
161
+ ## Add a new app
162
+
163
+ Create a root-level package such as `users/` or `orders/`, define its router there, and include it from your project's main `router.py`.
@@ -0,0 +1,47 @@
1
+ fastapi_templatekit/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ fastapi_templatekit/cli.py,sha256=E-yBqmP60HIK2Tx8zH0SzA8gNcwjfzS0NKcLjB-jjtM,918
3
+ fastapi_templatekit/commands/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
4
+ fastapi_templatekit/commands/addhealthprobes.py,sha256=Oj_XoSeTRNAv1FNIzlKZc9xxMakQXaGaseyjJqyxOHw,2881
5
+ fastapi_templatekit/commands/help.py,sha256=ZUN02MesRp0endKkg2oXhE4La_1QaPSeMik_-F-haWk,2318
6
+ fastapi_templatekit/commands/project.py,sha256=KghyK3lhhEaeFudjU4rp_67QJ0PYbVN_HPWPnpb1Q4E,683
7
+ fastapi_templatekit/commands/rendering.py,sha256=_m6GGxktwp1g6k1sF7kvgv3VC1WFDXTG33d7iukLHcs,534
8
+ fastapi_templatekit/commands/startapp.py,sha256=dP_gajuqtZ2r0KBmzUigvIqPXuB8kv24UILZG8qxhog,6324
9
+ fastapi_templatekit/commands/startproject.py,sha256=-BG5N8R3adCTdZWwF5yXIVBxUeSna3ms5eZa2CRmtmE,3099
10
+ fastapi_templatekit/commands/validation.py,sha256=Ib7LVp2eeXBQnU1qR6Hqr9ib9pXzXLhtOrIHTZHcvPU,443
11
+ fastapi_templatekit/templates/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
12
+ fastapi_templatekit/templates/app/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
13
+ fastapi_templatekit/templates/app/router.py.tpl,sha256=khwHC538LNXZLEkU-NVHMvR8vOkI6zVieY0E9vASego,181
14
+ fastapi_templatekit/templates/app/endpoints/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
15
+ fastapi_templatekit/templates/app/endpoints/api.py.tpl,sha256=M3CAWcXcu3MqZciuv0uCTzjWSYiKsdZHvvMdXjJ5JHU,389
16
+ fastapi_templatekit/templates/app/models/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
17
+ fastapi_templatekit/templates/app/schemas/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
18
+ fastapi_templatekit/templates/app/schemas/validator.py.tpl,sha256=A9_APBMG_-egiE5voXPv-PnZteUCAwEMs4y9OWL1TwA,123
19
+ fastapi_templatekit/templates/app/service/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
20
+ fastapi_templatekit/templates/app/service/app_service.py.tpl,sha256=1ze1VEnfV5gw3Mae-hk0RgjA7zsvlvA8qE6s9YZJfQI,109
21
+ fastapi_templatekit/templates/app/websocket/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
22
+ fastapi_templatekit/templates/app/websocket/router.py.tpl,sha256=Kjs1f_2-L1wEpFeNWfahJnXfz-w4tz7L5GXreTUkqSc,716
23
+ fastapi_templatekit/templates/healthprobes/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
24
+ fastapi_templatekit/templates/healthprobes/router.py.tpl,sha256=Vh8uvXopYPwBdjmxWWjyUl75n6MvmH6e7f5u1gcZBGs,184
25
+ fastapi_templatekit/templates/healthprobes/endpoints/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
26
+ fastapi_templatekit/templates/healthprobes/endpoints/api.py.tpl,sha256=zGSbM_Li1n7F34qhXUrz6Bc_hk7GmBu7P4dmb_NtwX8,551
27
+ fastapi_templatekit/templates/healthprobes/models/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
28
+ fastapi_templatekit/templates/healthprobes/schemas/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
29
+ fastapi_templatekit/templates/healthprobes/schemas/validator.py.tpl,sha256=bpfBokixSOMvmf-Qk1BsRK9sGAucKv4Nc0ZoOs58yZ8,123
30
+ fastapi_templatekit/templates/healthprobes/service/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
31
+ fastapi_templatekit/templates/healthprobes/service/healthprobes_service.py.tpl,sha256=n2jldjyLNQqC5xSJwaVjEi-e9MOxFjUzkeSmqGteYhg,85
32
+ fastapi_templatekit/templates/project/README.md.tpl,sha256=zFFT6-KXuwTOJxDPnGTQrtjBLRqoBa0yhtbjmGRFLTM,167
33
+ fastapi_templatekit/templates/project/env.example.tpl,sha256=2LRa8mcouRRFVGTQOc-zmFTh246_wVG4ItumgCRsL_E,72
34
+ fastapi_templatekit/templates/project/fastapi_templatekit.toml.tpl,sha256=45CcoUgvhDuEkKnLeuo2n47IW5FaJLTolGtNld1BFXk,101
35
+ fastapi_templatekit/templates/project/gitignore.tpl,sha256=ic-h9ptvPfgXO4m97l0UUybWoctSeb4YNpuXw9citWU,96
36
+ fastapi_templatekit/templates/project/pyproject.toml.tpl,sha256=D0WgDKyHovAtclWa8B08wKywrhQ22qYd5ryCKlTWET0,464
37
+ fastapi_templatekit/templates/project/package/__init__.py.tpl,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
38
+ fastapi_templatekit/templates/project/package/cli.py.tpl,sha256=Q2SY5mmreUdmtUUMEp4QsP8RTaxNj4tRZKzEy8Aeqtw,2508
39
+ fastapi_templatekit/templates/project/package/config.py.tpl,sha256=nDPOMsU_e4snsjs7yu9nGnGVJXenZZR3_ewyQeUOySk,350
40
+ fastapi_templatekit/templates/project/package/main.py.tpl,sha256=PFB_FFoxffRuJKYWNW50RaLMIGVMlfhMn5tXaaW6_4c,319
41
+ fastapi_templatekit/templates/project/package/router.py.tpl,sha256=nZihMF6NZoN8wWhjIHYKqQ8HT8Vc1WN2pg4EI0vXISo,93
42
+ fastapi_templatekit-0.1.0.dist-info/licenses/LICENSE,sha256=S0B0kuEbFCCQd4DGdkPtR5aRfzxrl5EKv9IWhfvWO0A,1069
43
+ fastapi_templatekit-0.1.0.dist-info/METADATA,sha256=2Aqvd_7a_-F0EXwUYCT5oxdP61JCFRxxi3QdhPly6dw,4627
44
+ fastapi_templatekit-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
45
+ fastapi_templatekit-0.1.0.dist-info/entry_points.txt,sha256=DlCCF6MoNRRAVgLkssLHRpmL_UaFs7d6TsMI-42R6Xo,69
46
+ fastapi_templatekit-0.1.0.dist-info/top_level.txt,sha256=8i6fSXbOuMBu1MY1In0UtDgBrP4_FWCjYvvlVXQUcUc,20
47
+ fastapi_templatekit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ fastapi-templatekit = fastapi_templatekit.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amogha Hegde
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.
@@ -0,0 +1 @@
1
+ fastapi_templatekit