backend-dash 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 (49) hide show
  1. backend_dash-0.1.0.dist-info/METADATA +178 -0
  2. backend_dash-0.1.0.dist-info/RECORD +49 -0
  3. backend_dash-0.1.0.dist-info/WHEEL +4 -0
  4. backend_dash-0.1.0.dist-info/entry_points.txt +2 -0
  5. backend_dash-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. fastapi_scaffolder/__init__.py +1 -0
  7. fastapi_scaffolder/cli.py +117 -0
  8. fastapi_scaffolder/templates/db_migrations/alembic/README +1 -0
  9. fastapi_scaffolder/templates/db_migrations/alembic/env.py +52 -0
  10. fastapi_scaffolder/templates/db_migrations/alembic/script.py.mako +28 -0
  11. fastapi_scaffolder/templates/db_migrations/alembic/versions/.gitkeep +0 -0
  12. fastapi_scaffolder/templates/db_migrations/alembic.ini +149 -0
  13. fastapi_scaffolder/templates/fastapi_sqlite/.env.example.j2 +63 -0
  14. fastapi_scaffolder/templates/fastapi_sqlite/README.md.j2 +40 -0
  15. fastapi_scaffolder/templates/fastapi_sqlite/app/__init__.py +0 -0
  16. fastapi_scaffolder/templates/fastapi_sqlite/app/api/__init__.py +0 -0
  17. fastapi_scaffolder/templates/fastapi_sqlite/app/api/deps.py.j2 +48 -0
  18. fastapi_scaffolder/templates/fastapi_sqlite/app/api/v1/__init__.py +0 -0
  19. fastapi_scaffolder/templates/fastapi_sqlite/app/api/v1/api.py.j2 +14 -0
  20. fastapi_scaffolder/templates/fastapi_sqlite/app/api/v1/routes_item.py +42 -0
  21. fastapi_scaffolder/templates/fastapi_sqlite/app/core/__init__.py +0 -0
  22. fastapi_scaffolder/templates/fastapi_sqlite/app/core/config.py.j2 +49 -0
  23. fastapi_scaffolder/templates/fastapi_sqlite/app/core/exceptions.py +41 -0
  24. fastapi_scaffolder/templates/fastapi_sqlite/app/crud/__init__.py +0 -0
  25. fastapi_scaffolder/templates/fastapi_sqlite/app/crud/item.py +38 -0
  26. fastapi_scaffolder/templates/fastapi_sqlite/app/db/__init__.py +0 -0
  27. fastapi_scaffolder/templates/fastapi_sqlite/app/db/base.py.j2 +9 -0
  28. fastapi_scaffolder/templates/fastapi_sqlite/app/db/base_class.py +10 -0
  29. fastapi_scaffolder/templates/fastapi_sqlite/app/db/init_db.py +11 -0
  30. fastapi_scaffolder/templates/fastapi_sqlite/app/db/session.py +28 -0
  31. fastapi_scaffolder/templates/fastapi_sqlite/app/main.py.j2 +44 -0
  32. fastapi_scaffolder/templates/fastapi_sqlite/app/models/__init__.py +0 -0
  33. fastapi_scaffolder/templates/fastapi_sqlite/app/models/item.py +17 -0
  34. fastapi_scaffolder/templates/fastapi_sqlite/app/schemas/__init__.py +0 -0
  35. fastapi_scaffolder/templates/fastapi_sqlite/app/schemas/item.py +27 -0
  36. fastapi_scaffolder/templates/fastapi_sqlite/app/utils/__init__.py +0 -0
  37. fastapi_scaffolder/templates/fastapi_sqlite/requirements.txt.j2 +19 -0
  38. fastapi_scaffolder/templates/fastapi_sqlite/tests/__init__.py +0 -0
  39. fastapi_scaffolder/templates/fastapi_sqlite/tests/test_items.py +48 -0
  40. fastapi_scaffolder/templates/fastapi_sqlite_auth/app/api/v1/routes_auth.py +57 -0
  41. fastapi_scaffolder/templates/fastapi_sqlite_auth/app/core/security.py +35 -0
  42. fastapi_scaffolder/templates/fastapi_sqlite_auth/app/models/user.py +20 -0
  43. fastapi_scaffolder/templates/fastapi_sqlite_auth/app/repositories/__init__.py +0 -0
  44. fastapi_scaffolder/templates/fastapi_sqlite_auth/app/repositories/base.py +46 -0
  45. fastapi_scaffolder/templates/fastapi_sqlite_auth/app/repositories/user_repository.py +15 -0
  46. fastapi_scaffolder/templates/fastapi_sqlite_auth/app/schemas/user.py +30 -0
  47. fastapi_scaffolder/templates/fastapi_sqlite_auth/app/services/__init__.py +0 -0
  48. fastapi_scaffolder/templates/fastapi_sqlite_auth/app/services/auth_service.py +108 -0
  49. fastapi_scaffolder/templates/fastapi_sqlite_auth/tests/test_auth.py +85 -0
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: backend-dash
3
+ Version: 0.1.0
4
+ Summary: Scaffold a FastAPI + SQLAlchemy backend with SQLite/Postgres/MySQL and optional JWT + Google OAuth auth
5
+ Project-URL: Homepage, https://github.com/prathamdmehta/backend-dash
6
+ Project-URL: Repository, https://github.com/prathamdmehta/backend-dash
7
+ Author: Pratham
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: backend,boilerplate,cli,fastapi,scaffold,sqlalchemy
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Classifier: Topic :: Software Development :: Code Generators
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: jinja2>=3.1
23
+ Requires-Dist: typer>=0.12
24
+ Description-Content-Type: text/markdown
25
+
26
+ # backend-dash
27
+
28
+ A CLI that generates ready-to-code FastAPI backend projects with SQLAlchemy, SQLite, PostgreSQL, MySQL, Alembic migrations, CRUD examples, tests, and optional JWT + Google OAuth authentication.
29
+
30
+ ## Features
31
+
32
+ - Scaffold a complete FastAPI backend in seconds.
33
+ - Supports `--db=sqlite|postgres|mysql` with SQLite as the default.
34
+ - Generates a working SQLAlchemy setup and DB session wiring.
35
+ - Adds an example CRUD resource so you can start from real code, not boilerplate.
36
+ - Includes tests for the generated project.
37
+ - Adds Alembic migrations for PostgreSQL and MySQL.
38
+ - Keeps SQLite simple with `create_all` for zero-config local development.
39
+ - Optional `--auth` mode adds JWT + Google OAuth auth support.
40
+ - Clear validation for unsupported database values.
41
+ - Clean `.env.example` setup for local development and migrations.
42
+
43
+ ## Installation
44
+
45
+ ### One-off usage
46
+
47
+ ```bash
48
+ uvx backend-dash init "My API"
49
+ ```
50
+
51
+ ### Install locally
52
+
53
+ ```bash
54
+ pip install backend-dash
55
+ backend-dash init "My API"
56
+ ```
57
+
58
+ ### Editable install for development
59
+
60
+ ```bash
61
+ git clone https://github.com/prathamdmehta/backend-dash.git
62
+ cd backend-dash
63
+ pip install -e . --break-system-packages
64
+ ```
65
+
66
+ ## Usage
67
+
68
+ Create a new project:
69
+
70
+ ```bash
71
+ backend-dash init "Demo Project"
72
+ ```
73
+
74
+ Create a project with auth:
75
+
76
+ ```bash
77
+ backend-dash init "Demo Project Auth" --auth
78
+ ```
79
+
80
+ Choose a database:
81
+
82
+ ```bash
83
+ backend-dash init "My API" --db=sqlite
84
+ backend-dash init "My API" --db=postgres
85
+ backend-dash init "My API" --db=mysql
86
+ ```
87
+
88
+ Combine database and auth:
89
+
90
+ ```bash
91
+ backend-dash init "My API" --db=postgres --auth
92
+ ```
93
+
94
+ ## What gets generated
95
+
96
+ ### Base project
97
+
98
+ - FastAPI app structure.
99
+ - SQLAlchemy session and base model wiring.
100
+ - Example item resource with CRUD routes.
101
+ - Pydantic schemas and models.
102
+ - Pytest test setup.
103
+
104
+ ### With `--auth`
105
+
106
+ - Repository layer with a generic `BaseRepository` and `UserRepository`.
107
+ - Auth service for registration, login, JWT issuance, and Google token exchange.
108
+ - Validation with proper schema constraints.
109
+ - Centralized error handling with a consistent error response shape.
110
+ - Auth routes for register, login, me, Google login, and Google callback.
111
+
112
+ ## Database support
113
+
114
+ ### SQLite
115
+
116
+ SQLite is the default and uses `create_all` for a simple local setup.
117
+
118
+ ### PostgreSQL
119
+
120
+ For PostgreSQL, the scaffold uses Alembic migrations and real schema management.
121
+
122
+ ### MySQL
123
+
124
+ For MySQL, the scaffold also uses Alembic migrations and the correct MySQL driver setup.
125
+
126
+ ## Migrations
127
+
128
+ For PostgreSQL and MySQL, the project generates Alembic configuration and an `env.py` wired to `DATABASE_URL` and your app metadata.
129
+
130
+ Typical workflow:
131
+
132
+ ```bash
133
+ alembic revision --autogenerate -m "create initial tables"
134
+ alembic upgrade head
135
+ ```
136
+
137
+ The generated `.env.example` includes the right migration flow so you can move from scaffold to production-style schema management quickly.
138
+
139
+ ## Project structure
140
+
141
+ ```bash
142
+ my_api/
143
+ ├── app/
144
+ ├── tests/
145
+ ├── alembic/
146
+ ├── alembic.ini
147
+ └── .env.example
148
+ ```
149
+
150
+ If `--auth` is enabled, additional auth-related modules are included inside the app structure.
151
+
152
+ ## Example
153
+
154
+ Generate a new API project:
155
+
156
+ ```bash
157
+ backend-dash init "Orders API" --db=postgres --auth
158
+ ```
159
+
160
+ This creates a FastAPI backend with database wiring, migrations, tests, and authentication already in place, so you can start on business logic immediately.
161
+
162
+ ## Development
163
+
164
+ ```bash
165
+ git clone https://github.com/prathamdmehta/backend-dash.git
166
+ cd backend-dash
167
+ pip install -e . --break-system-packages
168
+ ```
169
+
170
+ Then run the CLI locally:
171
+
172
+ ```bash
173
+ backend-dash init "Demo Project"
174
+ ```
175
+
176
+ ## License
177
+
178
+ MIT
@@ -0,0 +1,49 @@
1
+ fastapi_scaffolder/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ fastapi_scaffolder/cli.py,sha256=wzm5YuYWtZYO8QxRLSQz9HxncHEtCXMkGlBwPgoJIt4,4146
3
+ fastapi_scaffolder/templates/db_migrations/alembic.ini,sha256=HeRE5SAnRyRnGMP4AmTI_mTqvGlyZ4aWcHce2xQAUP0,5008
4
+ fastapi_scaffolder/templates/db_migrations/alembic/README,sha256=MVlc9TYmr57RbhXET6QxgyCcwWP7w-vLkEsirENqiIQ,38
5
+ fastapi_scaffolder/templates/db_migrations/alembic/env.py,sha256=BphCR-n56m5bjbEFcY777rID22jVUzRlqPYmaEzKRG8,1410
6
+ fastapi_scaffolder/templates/db_migrations/alembic/script.py.mako,sha256=04kgeBtNMa4cCnG8CfQcKt6P6rnloIfj8wy0u_DBydM,704
7
+ fastapi_scaffolder/templates/db_migrations/alembic/versions/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ fastapi_scaffolder/templates/fastapi_sqlite/.env.example.j2,sha256=ixc6F9RVF70r9sbATCwQpY4m7DO5pKEUFpkfaNGbxJE,2588
9
+ fastapi_scaffolder/templates/fastapi_sqlite/README.md.j2,sha256=s5ASJ5d8wGwu_f9FqRe-DMP6WOqztBtASjvpFzpPdro,1338
10
+ fastapi_scaffolder/templates/fastapi_sqlite/requirements.txt.j2,sha256=qucRy-l9uqVSplXumr1X9CeEbWniHpWnZqZmv78unOo,333
11
+ fastapi_scaffolder/templates/fastapi_sqlite/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ fastapi_scaffolder/templates/fastapi_sqlite/app/main.py.j2,sha256=R68YUmDap9CezazOd42gJyy-j6Aq0nUcntr_B6Kmxb0,1386
13
+ fastapi_scaffolder/templates/fastapi_sqlite/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ fastapi_scaffolder/templates/fastapi_sqlite/app/api/deps.py.j2,sha256=o7Cepj_hgXjz9OusTtoVrbi_bTwgU_iU7TgLj18JnVo,1626
15
+ fastapi_scaffolder/templates/fastapi_sqlite/app/api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ fastapi_scaffolder/templates/fastapi_sqlite/app/api/v1/api.py.j2,sha256=2AqwfN8Wz0kbUSSocoaWL9x7-uf6cbAyCrAsKNdySqM,396
17
+ fastapi_scaffolder/templates/fastapi_sqlite/app/api/v1/routes_item.py,sha256=XZph_eE0Cp00YB8hBR9ueOs3n6VjBv2P9PQ15kAKhiM,1470
18
+ fastapi_scaffolder/templates/fastapi_sqlite/app/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ fastapi_scaffolder/templates/fastapi_sqlite/app/core/config.py.j2,sha256=pDpv4n3bkuFsHpACGyfH6QlenlU39XThQnsxdQ3wLCs,1873
20
+ fastapi_scaffolder/templates/fastapi_sqlite/app/core/exceptions.py,sha256=BCymjibQ_xJB7D4EDuOr5_hRnq2SGZuxeaD5q_yO5I4,1027
21
+ fastapi_scaffolder/templates/fastapi_sqlite/app/crud/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ fastapi_scaffolder/templates/fastapi_sqlite/app/crud/item.py,sha256=JwvIhUcQLHOtnYVIA9GIi9cd-a3d7JqDVDD_Y58kjLo,1007
23
+ fastapi_scaffolder/templates/fastapi_sqlite/app/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ fastapi_scaffolder/templates/fastapi_sqlite/app/db/base.py.j2,sha256=wWHftpL1UuPco9dbmv0MQwIItTVDttDqqLKGe878SdI,320
25
+ fastapi_scaffolder/templates/fastapi_sqlite/app/db/base_class.py,sha256=Z9IrLqt-nc3JvhphZ3tShWsWyt5X8ZbdQ8znM-YhwTA,211
26
+ fastapi_scaffolder/templates/fastapi_sqlite/app/db/init_db.py,sha256=J1Ln9ZWY7Bhp9-fUR6O8SvP-xTCyxH9TvArwwWgb4uM,341
27
+ fastapi_scaffolder/templates/fastapi_sqlite/app/db/session.py,sha256=ww3Ci2kJUEqtpWKP_YEjMhdgvzTs0QkzbzQv4ITHkKA,795
28
+ fastapi_scaffolder/templates/fastapi_sqlite/app/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ fastapi_scaffolder/templates/fastapi_sqlite/app/models/item.py,sha256=EHHBQaKRJAnbaH1Q_mxzNRJm5kT9ShAxLVtg2H7j32Y,542
30
+ fastapi_scaffolder/templates/fastapi_sqlite/app/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ fastapi_scaffolder/templates/fastapi_sqlite/app/schemas/item.py,sha256=ZiU_R3M4SwHByFCnwjkH8RTeCqNgdKB_CGHDrIWm3J8,509
32
+ fastapi_scaffolder/templates/fastapi_sqlite/app/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ fastapi_scaffolder/templates/fastapi_sqlite/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ fastapi_scaffolder/templates/fastapi_sqlite/tests/test_items.py,sha256=KgPU_31AIrNLqdQ7Cz9sJp4VxVTnxIgoC91tTcmmudk,1272
35
+ fastapi_scaffolder/templates/fastapi_sqlite_auth/app/api/v1/routes_auth.py,sha256=W5gOESUiQUhTVBPSQ-IFczzweiMqo-oIdU2y5otBqCs,2067
36
+ fastapi_scaffolder/templates/fastapi_sqlite_auth/app/core/security.py,sha256=pjs-yXQZHNq8AnBgXTaV3ajnuk42ZeF8nUN_0z8wVEE,1189
37
+ fastapi_scaffolder/templates/fastapi_sqlite_auth/app/models/user.py,sha256=ith5OgahtQiKKlsRiPefpcQgLLwt6PZ1RmDbnNKrWzA,807
38
+ fastapi_scaffolder/templates/fastapi_sqlite_auth/app/repositories/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
+ fastapi_scaffolder/templates/fastapi_sqlite_auth/app/repositories/base.py,sha256=Ab6BGUc0ju9EgTNdOn1L9thJ5Bfqppj1zvO-mNm72yA,1384
40
+ fastapi_scaffolder/templates/fastapi_sqlite_auth/app/repositories/user_repository.py,sha256=WvswWWeVYJtiOpR8xzzSzV8K5RH0IayxrDij82iNFRE,510
41
+ fastapi_scaffolder/templates/fastapi_sqlite_auth/app/schemas/user.py,sha256=b-1g7YMxoL0nGS_icDzgEVnOX6uwAgimddXvkGt4CB8,628
42
+ fastapi_scaffolder/templates/fastapi_sqlite_auth/app/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
+ fastapi_scaffolder/templates/fastapi_sqlite_auth/app/services/auth_service.py,sha256=6L9HO5XKmioNEEa3jS4e2gSUG0zcLgWaEiBdeyPp31E,3887
44
+ fastapi_scaffolder/templates/fastapi_sqlite_auth/tests/test_auth.py,sha256=xSF_OsWGRrLRWUmbyUhQpkpSpxH8gU6KoLLyGJ6Wk1A,2707
45
+ backend_dash-0.1.0.dist-info/METADATA,sha256=4zrqv7ZpHRC39W8LeKmbheAAKBlHXmeCya3siAVHDxI,4614
46
+ backend_dash-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
47
+ backend_dash-0.1.0.dist-info/entry_points.txt,sha256=vJt6n0Jf_H6VmGyeAgfAIRs050jMWbY1q4FOnmF7Nj0,61
48
+ backend_dash-0.1.0.dist-info/licenses/LICENSE,sha256=z7OWR_tNA6V2Ct2-qkJzGye8ekWd40XASgOI7UBXh5c,1064
49
+ backend_dash-0.1.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
+ backend-dash = fastapi_scaffolder.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pratham
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
+ __version__ = "0.1.0"
@@ -0,0 +1,117 @@
1
+ import re
2
+ import shutil
3
+ from pathlib import Path
4
+
5
+ import typer
6
+ from jinja2 import Environment, FileSystemLoader
7
+
8
+ app = typer.Typer(
9
+ help="Scaffold a FastAPI + SQLAlchemy backend so you can skip the boilerplate."
10
+ )
11
+
12
+ TEMPLATES_ROOT = Path(__file__).parent / "templates"
13
+ VALID_DBS = ("sqlite", "postgres", "mysql")
14
+
15
+
16
+ def slugify(name: str) -> str:
17
+ """Turn 'My Cool API' into 'my_cool_api' for use as a folder name."""
18
+ slug = re.sub(r"[^a-zA-Z0-9]+", "_", name.strip()).strip("_").lower()
19
+ return slug or "backend_project"
20
+
21
+
22
+ def render_template(template_dir: Path, target_dir: Path, context: dict) -> None:
23
+ env = Environment(loader=FileSystemLoader(str(template_dir)), keep_trailing_newline=True)
24
+
25
+ for src_path in sorted(template_dir.rglob("*")):
26
+ if src_path.is_dir():
27
+ continue
28
+
29
+ rel_path = src_path.relative_to(template_dir)
30
+ rel_str = rel_path.as_posix()
31
+ is_template = rel_str.endswith(".j2")
32
+ out_rel = Path(rel_str[:-3]) if is_template else rel_path
33
+ out_path = target_dir / out_rel
34
+ out_path.parent.mkdir(parents=True, exist_ok=True)
35
+
36
+ if is_template:
37
+ rendered = env.get_template(rel_str).render(**context)
38
+ out_path.write_text(rendered, encoding="utf-8")
39
+ else:
40
+ shutil.copy2(src_path, out_path)
41
+
42
+
43
+ @app.command()
44
+ def init(
45
+ project_name: str = typer.Argument(..., help="Human-readable project name, e.g. 'My API'"),
46
+ directory: str = typer.Option(
47
+ None,
48
+ "--dir",
49
+ help="Folder to create the project in. Defaults to a slugified version of the project name.",
50
+ ),
51
+ auth: bool = typer.Option(
52
+ False,
53
+ "--auth",
54
+ help="Include JWT + Google OAuth authentication (repository pattern, service layer, "
55
+ "centralized error handling, login/register/me routes).",
56
+ ),
57
+ db: str = typer.Option(
58
+ "sqlite",
59
+ "--db",
60
+ help="Database backend: sqlite (default, zero setup), postgres, or mysql "
61
+ "(both include Alembic migrations instead of auto-created tables).",
62
+ ),
63
+ ):
64
+ """
65
+ Generate a new FastAPI + SQLAlchemy + SQLite backend project.
66
+ """
67
+ db = db.lower()
68
+ if db not in VALID_DBS:
69
+ typer.secho(f"--db must be one of {VALID_DBS}, got '{db}'.", fg=typer.colors.RED)
70
+ raise typer.Exit(code=1)
71
+
72
+ project_slug = slugify(directory or project_name)
73
+ target_dir = Path.cwd() / project_slug
74
+
75
+ if target_dir.exists() and any(target_dir.iterdir()):
76
+ typer.secho(f"'{project_slug}' already exists and is not empty. Aborting.", fg=typer.colors.RED)
77
+ raise typer.Exit(code=1)
78
+
79
+ context = {"project_name": project_name, "project_slug": project_slug, "auth": auth, "db": db}
80
+
81
+ render_template(TEMPLATES_ROOT / "fastapi_sqlite", target_dir, context)
82
+ if auth:
83
+ render_template(TEMPLATES_ROOT / "fastapi_sqlite_auth", target_dir, context)
84
+ if db in ("postgres", "mysql"):
85
+ render_template(TEMPLATES_ROOT / "db_migrations", target_dir, context)
86
+
87
+ typer.secho(f"\n✅ Created '{project_name}' in ./{project_slug}\n", fg=typer.colors.GREEN, bold=True)
88
+ typer.echo("Next steps:")
89
+ typer.echo(f" cd {project_slug}")
90
+ typer.echo(" pip install -r requirements.txt # or: uv pip install -r requirements.txt")
91
+ typer.echo(" cp .env.example .env")
92
+ if db in ("postgres", "mysql"):
93
+ typer.echo(f" # edit .env with your real {db} credentials, then:")
94
+ typer.echo(' alembic revision --autogenerate -m "initial tables"')
95
+ typer.echo(" alembic upgrade head")
96
+ if auth:
97
+ typer.echo(" # (optional) set GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET in .env for Google login")
98
+ typer.echo(" uvicorn app.main:app --reload")
99
+ if auth:
100
+ typer.echo("\nAuth endpoints: POST /api/v1/auth/register, POST /api/v1/auth/login, "
101
+ "GET /api/v1/auth/me, GET /api/v1/auth/google/login")
102
+
103
+
104
+ @app.command()
105
+ def version():
106
+ """Print the fastapi-scaffolder version."""
107
+ from fastapi_scaffolder import __version__
108
+
109
+ typer.echo(__version__)
110
+
111
+
112
+ def main():
113
+ app()
114
+
115
+
116
+ if __name__ == "__main__":
117
+ main()
@@ -0,0 +1 @@
1
+ Generic single-database configuration.
@@ -0,0 +1,52 @@
1
+ from logging.config import fileConfig
2
+
3
+ from sqlalchemy import engine_from_config
4
+ from sqlalchemy import pool
5
+
6
+ from alembic import context
7
+
8
+ # Pull the connection URL and model metadata from the app itself,
9
+ # so alembic.ini never needs a hardcoded/duplicated DATABASE_URL.
10
+ from app.core.config import settings
11
+ from app.db.base import Base # noqa: F401 (imports every model)
12
+
13
+ config = context.config
14
+ config.set_main_option("sqlalchemy.url", settings.DATABASE_URL)
15
+
16
+ if config.config_file_name is not None:
17
+ fileConfig(config.config_file_name)
18
+
19
+ target_metadata = Base.metadata
20
+
21
+
22
+ def run_migrations_offline() -> None:
23
+ url = config.get_main_option("sqlalchemy.url")
24
+ context.configure(
25
+ url=url,
26
+ target_metadata=target_metadata,
27
+ literal_binds=True,
28
+ dialect_opts={"paramstyle": "named"},
29
+ )
30
+
31
+ with context.begin_transaction():
32
+ context.run_migrations()
33
+
34
+
35
+ def run_migrations_online() -> None:
36
+ connectable = engine_from_config(
37
+ config.get_section(config.config_ini_section, {}),
38
+ prefix="sqlalchemy.",
39
+ poolclass=pool.NullPool,
40
+ )
41
+
42
+ with connectable.connect() as connection:
43
+ context.configure(connection=connection, target_metadata=target_metadata)
44
+
45
+ with context.begin_transaction():
46
+ context.run_migrations()
47
+
48
+
49
+ if context.is_offline_mode():
50
+ run_migrations_offline()
51
+ else:
52
+ run_migrations_online()
@@ -0,0 +1,28 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ ${upgrades if upgrades else "pass"}
24
+
25
+
26
+ def downgrade() -> None:
27
+ """Downgrade schema."""
28
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,149 @@
1
+ # A generic, single database configuration.
2
+
3
+ [alembic]
4
+ # path to migration scripts.
5
+ # this is typically a path given in POSIX (e.g. forward slashes)
6
+ # format, relative to the token %(here)s which refers to the location of this
7
+ # ini file
8
+ script_location = %(here)s/alembic
9
+
10
+ # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
11
+ # Uncomment the line below if you want the files to be prepended with date and time
12
+ # see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
13
+ # for all available tokens
14
+ # file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
15
+ # Or organize into date-based subdirectories (requires recursive_version_locations = true)
16
+ # file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s
17
+
18
+ # sys.path path, will be prepended to sys.path if present.
19
+ # defaults to the current working directory. for multiple paths, the path separator
20
+ # is defined by "path_separator" below.
21
+ prepend_sys_path = .
22
+
23
+
24
+ # timezone to use when rendering the date within the migration file
25
+ # as well as the filename.
26
+ # If specified, requires the tzdata library which can be installed by adding
27
+ # `alembic[tz]` to the pip requirements.
28
+ # string value is passed to ZoneInfo()
29
+ # leave blank for localtime
30
+ # timezone =
31
+
32
+ # max length of characters to apply to the "slug" field
33
+ # truncate_slug_length = 40
34
+
35
+ # set to 'true' to run the environment during
36
+ # the 'revision' command, regardless of autogenerate
37
+ # revision_environment = false
38
+
39
+ # set to 'true' to allow .pyc and .pyo files without
40
+ # a source .py file to be detected as revisions in the
41
+ # versions/ directory
42
+ # sourceless = false
43
+
44
+ # version location specification; This defaults
45
+ # to <script_location>/versions. When using multiple version
46
+ # directories, initial revisions must be specified with --version-path.
47
+ # The path separator used here should be the separator specified by "path_separator"
48
+ # below.
49
+ # version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
50
+
51
+ # path_separator; This indicates what character is used to split lists of file
52
+ # paths, including version_locations and prepend_sys_path within configparser
53
+ # files such as alembic.ini.
54
+ # The default rendered in new alembic.ini files is "os", which uses os.pathsep
55
+ # to provide os-dependent path splitting.
56
+ #
57
+ # Note that in order to support legacy alembic.ini files, this default does NOT
58
+ # take place if path_separator is not present in alembic.ini. If this
59
+ # option is omitted entirely, fallback logic is as follows:
60
+ #
61
+ # 1. Parsing of the version_locations option falls back to using the legacy
62
+ # "version_path_separator" key, which if absent then falls back to the legacy
63
+ # behavior of splitting on spaces and/or commas.
64
+ # 2. Parsing of the prepend_sys_path option falls back to the legacy
65
+ # behavior of splitting on spaces, commas, or colons.
66
+ #
67
+ # Valid values for path_separator are:
68
+ #
69
+ # path_separator = :
70
+ # path_separator = ;
71
+ # path_separator = space
72
+ # path_separator = newline
73
+ #
74
+ # Use os.pathsep. Default configuration used for new projects.
75
+ path_separator = os
76
+
77
+ # set to 'true' to search source files recursively
78
+ # in each "version_locations" directory
79
+ # new in Alembic version 1.10
80
+ # recursive_version_locations = false
81
+
82
+ # the output encoding used when revision files
83
+ # are written from script.py.mako
84
+ # output_encoding = utf-8
85
+
86
+ # database URL. This is consumed by the user-maintained env.py script only.
87
+ # other means of configuring database URLs may be customized within the env.py
88
+ # file.
89
+ sqlalchemy.url = driver://user:pass@localhost/dbname
90
+
91
+
92
+ [post_write_hooks]
93
+ # post_write_hooks defines scripts or Python functions that are run
94
+ # on newly generated revision scripts. See the documentation for further
95
+ # detail and examples
96
+
97
+ # format using "black" - use the console_scripts runner, against the "black" entrypoint
98
+ # hooks = black
99
+ # black.type = console_scripts
100
+ # black.entrypoint = black
101
+ # black.options = -l 79 REVISION_SCRIPT_FILENAME
102
+
103
+ # lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
104
+ # hooks = ruff
105
+ # ruff.type = module
106
+ # ruff.module = ruff
107
+ # ruff.options = check --fix REVISION_SCRIPT_FILENAME
108
+
109
+ # Alternatively, use the exec runner to execute a binary found on your PATH
110
+ # hooks = ruff
111
+ # ruff.type = exec
112
+ # ruff.executable = ruff
113
+ # ruff.options = check --fix REVISION_SCRIPT_FILENAME
114
+
115
+ # Logging configuration. This is also consumed by the user-maintained
116
+ # env.py script only.
117
+ [loggers]
118
+ keys = root,sqlalchemy,alembic
119
+
120
+ [handlers]
121
+ keys = console
122
+
123
+ [formatters]
124
+ keys = generic
125
+
126
+ [logger_root]
127
+ level = WARNING
128
+ handlers = console
129
+ qualname =
130
+
131
+ [logger_sqlalchemy]
132
+ level = WARNING
133
+ handlers =
134
+ qualname = sqlalchemy.engine
135
+
136
+ [logger_alembic]
137
+ level = INFO
138
+ handlers =
139
+ qualname = alembic
140
+
141
+ [handler_console]
142
+ class = StreamHandler
143
+ args = (sys.stderr,)
144
+ level = NOTSET
145
+ formatter = generic
146
+
147
+ [formatter_generic]
148
+ format = %(levelname)-5.5s [%(name)s] %(message)s
149
+ datefmt = %H:%M:%S
@@ -0,0 +1,63 @@
1
+ PROJECT_NAME="{{ project_name }}"
2
+ {% if db == "postgres" -%}
3
+ # Update user/password/host/port/dbname for your actual Postgres instance.
4
+ DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/{{ project_slug }}
5
+ {% elif db == "mysql" -%}
6
+ # Update user/password/host/port/dbname for your actual MySQL instance.
7
+ DATABASE_URL=mysql+pymysql://root:root@localhost:3306/{{ project_slug }}
8
+ {% else -%}
9
+ DATABASE_URL=sqlite:///./app.db
10
+ {% endif -%}
11
+ DEBUG=True
12
+ {% if db in ("postgres", "mysql") -%}
13
+
14
+ # --- Migrations ---
15
+ # This project's schema is managed by Alembic, not auto-created on
16
+ # startup. Before running the app for the first time:
17
+ # 1. Make sure the database above exists and is reachable.
18
+ # 2. alembic revision --autogenerate -m "initial tables"
19
+ # 3. alembic upgrade head
20
+ # Whenever you change a model, repeat steps 2-3 to add a new migration.
21
+ {% endif -%}
22
+ {% if auth -%}
23
+
24
+ # --- Auth (JWT) ---
25
+
26
+ # Signs and verifies your JWTs. Never commit a real value — this
27
+ # placeholder is fine for local dev only.
28
+ # Generate one with:
29
+ # python -c "import secrets; print(secrets.token_urlsafe(32))"
30
+ SECRET_KEY=dev-secret-change-me-this-is-not-secure-32chars
31
+
32
+ # HMAC algorithm used to sign the JWT. HS256 is fine for most apps
33
+ # (single-server secret). Leave as-is unless you have a specific reason.
34
+ ALGORITHM=HS256
35
+
36
+ # How long an access token stays valid, in minutes. 1440 = 24 hours.
37
+ ACCESS_TOKEN_EXPIRE_MINUTES=1440
38
+
39
+ # --- Google OAuth ---
40
+ # Needed only for the "Log in with Google" flow (/auth/google/login).
41
+ # Password-based register/login works without any of this.
42
+ #
43
+ # How to get these values:
44
+ # 1. Go to https://console.cloud.google.com/apis/credentials
45
+ # 2. Create a project (or select an existing one).
46
+ # 3. Click "Create Credentials" -> "OAuth client ID".
47
+ # 4. If prompted, configure the "OAuth consent screen" first
48
+ # (External user type is fine for testing; add your own email
49
+ # as a test user so you can log in before the app is verified).
50
+ # 5. Application type: "Web application".
51
+ # 6. Under "Authorized redirect URIs", add the exact value of
52
+ # GOOGLE_REDIRECT_URI below (must match exactly, including the
53
+ # port, or Google will reject the callback).
54
+ # 7. Click "Create" - Google shows you the Client ID and Client
55
+ # Secret. Paste them below.
56
+ GOOGLE_CLIENT_ID=
57
+ GOOGLE_CLIENT_SECRET=
58
+
59
+ # Must exactly match an "Authorized redirect URI" configured in step 6
60
+ # above. Update the host/port here if you deploy somewhere other than
61
+ # localhost:8000.
62
+ GOOGLE_REDIRECT_URI=http://127.0.0.1:8000/api/v1/auth/google/callback
63
+ {% endif -%}