fastscaff 0.1.0__tar.gz
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.
- fastscaff-0.1.0/PKG-INFO +155 -0
- fastscaff-0.1.0/README.md +130 -0
- fastscaff-0.1.0/fastscaff/__init__.py +1 -0
- fastscaff-0.1.0/fastscaff/cli.py +128 -0
- fastscaff-0.1.0/fastscaff/generator.py +258 -0
- fastscaff-0.1.0/fastscaff/templates/app/__init__.py.jinja2 +0 -0
- fastscaff-0.1.0/fastscaff/templates/app/api/__init__.py.jinja2 +0 -0
- fastscaff-0.1.0/fastscaff/templates/app/api/v1/__init__.py.jinja2 +0 -0
- fastscaff-0.1.0/fastscaff/templates/app/api/v1/endpoints/__init__.py.jinja2 +0 -0
- fastscaff-0.1.0/fastscaff/templates/app/api/v1/endpoints/auth.py.jinja2 +26 -0
- fastscaff-0.1.0/fastscaff/templates/app/api/v1/endpoints/health.py.jinja2 +15 -0
- fastscaff-0.1.0/fastscaff/templates/app/api/v1/endpoints/users.py.jinja2 +20 -0
- fastscaff-0.1.0/fastscaff/templates/app/api/v1/router.py.jinja2 +9 -0
- fastscaff-0.1.0/fastscaff/templates/app/core/__init__.py.jinja2 +5 -0
- fastscaff-0.1.0/fastscaff/templates/app/core/config.py.jinja2 +50 -0
- fastscaff-0.1.0/fastscaff/templates/app/core/database_sqlalchemy.py.jinja2 +105 -0
- fastscaff-0.1.0/fastscaff/templates/app/core/database_tortoise.py.jinja2 +69 -0
- fastscaff-0.1.0/fastscaff/templates/app/core/lifespan.py.jinja2 +38 -0
- fastscaff-0.1.0/fastscaff/templates/app/core/logger.py.jinja2 +157 -0
- fastscaff-0.1.0/fastscaff/templates/app/core/rbac.py.jinja2 +486 -0
- fastscaff-0.1.0/fastscaff/templates/app/core/redis.py.jinja2 +251 -0
- fastscaff-0.1.0/fastscaff/templates/app/core/security.py.jinja2 +69 -0
- fastscaff-0.1.0/fastscaff/templates/app/core/singleton.py.jinja2 +32 -0
- fastscaff-0.1.0/fastscaff/templates/app/exceptions/__init__.py.jinja2 +21 -0
- fastscaff-0.1.0/fastscaff/templates/app/exceptions/base.py.jinja2 +34 -0
- fastscaff-0.1.0/fastscaff/templates/app/exceptions/handlers.py.jinja2 +82 -0
- fastscaff-0.1.0/fastscaff/templates/app/main.py.jinja2 +61 -0
- fastscaff-0.1.0/fastscaff/templates/app/middleware/__init__.py.jinja2 +15 -0
- fastscaff-0.1.0/fastscaff/templates/app/middleware/cors.py.jinja2 +16 -0
- fastscaff-0.1.0/fastscaff/templates/app/middleware/jwt.py.jinja2 +102 -0
- fastscaff-0.1.0/fastscaff/templates/app/middleware/logging.py.jinja2 +119 -0
- fastscaff-0.1.0/fastscaff/templates/app/middleware/security.py.jinja2 +60 -0
- fastscaff-0.1.0/fastscaff/templates/app/middleware/sign.py.jinja2 +198 -0
- fastscaff-0.1.0/fastscaff/templates/app/middleware/tracing.py.jinja2 +87 -0
- fastscaff-0.1.0/fastscaff/templates/app/models/__init__.py.jinja2 +3 -0
- fastscaff-0.1.0/fastscaff/templates/app/models/base_sqlalchemy.py.jinja2 +23 -0
- fastscaff-0.1.0/fastscaff/templates/app/models/base_tortoise.py.jinja2 +13 -0
- fastscaff-0.1.0/fastscaff/templates/app/models/user_sqlalchemy.py.jinja2 +13 -0
- fastscaff-0.1.0/fastscaff/templates/app/models/user_tortoise.py.jinja2 +14 -0
- fastscaff-0.1.0/fastscaff/templates/app/repositories/__init__.py.jinja2 +3 -0
- fastscaff-0.1.0/fastscaff/templates/app/repositories/base_sqlalchemy.py.jinja2 +99 -0
- fastscaff-0.1.0/fastscaff/templates/app/repositories/base_tortoise.py.jinja2 +61 -0
- fastscaff-0.1.0/fastscaff/templates/app/repositories/user_sqlalchemy.py.jinja2 +58 -0
- fastscaff-0.1.0/fastscaff/templates/app/repositories/user_tortoise.py.jinja2 +45 -0
- fastscaff-0.1.0/fastscaff/templates/app/schemas/__init__.py.jinja2 +4 -0
- fastscaff-0.1.0/fastscaff/templates/app/schemas/auth.py.jinja2 +19 -0
- fastscaff-0.1.0/fastscaff/templates/app/schemas/base.py.jinja2 +21 -0
- fastscaff-0.1.0/fastscaff/templates/app/schemas/user.py.jinja2 +25 -0
- fastscaff-0.1.0/fastscaff/templates/app/services/__init___sqlalchemy.py.jinja2 +36 -0
- fastscaff-0.1.0/fastscaff/templates/app/services/__init___tortoise.py.jinja2 +36 -0
- fastscaff-0.1.0/fastscaff/templates/app/services/auth_sqlalchemy.py.jinja2 +45 -0
- fastscaff-0.1.0/fastscaff/templates/app/services/auth_tortoise.py.jinja2 +46 -0
- fastscaff-0.1.0/fastscaff/templates/app/services/user_sqlalchemy.py.jinja2 +34 -0
- fastscaff-0.1.0/fastscaff/templates/app/services/user_tortoise.py.jinja2 +35 -0
- fastscaff-0.1.0/fastscaff/templates/app/utils/__init__.py.jinja2 +26 -0
- fastscaff-0.1.0/fastscaff/templates/app/utils/auth.py.jinja2 +145 -0
- fastscaff-0.1.0/fastscaff/templates/app/utils/cache.py.jinja2 +529 -0
- fastscaff-0.1.0/fastscaff/templates/app/utils/rate_limiter.py.jinja2 +315 -0
- fastscaff-0.1.0/fastscaff/templates/app/utils/snowflake.py.jinja2 +71 -0
- fastscaff-0.1.0/fastscaff/templates/app/utils/sort_helper.py.jinja2 +77 -0
- fastscaff-0.1.0/fastscaff/templates/base/Dockerfile.jinja2 +30 -0
- fastscaff-0.1.0/fastscaff/templates/base/Makefile.jinja2 +53 -0
- fastscaff-0.1.0/fastscaff/templates/base/README.md.jinja2 +63 -0
- fastscaff-0.1.0/fastscaff/templates/base/docker-compose.yml.jinja2 +73 -0
- fastscaff-0.1.0/fastscaff/templates/base/env.example.jinja2 +32 -0
- fastscaff-0.1.0/fastscaff/templates/base/gitignore.jinja2 +59 -0
- fastscaff-0.1.0/fastscaff/templates/base/pre-commit-config.yaml.jinja2 +17 -0
- fastscaff-0.1.0/fastscaff/templates/base/pyproject.toml.jinja2 +41 -0
- fastscaff-0.1.0/fastscaff/templates/base/requirements.txt.jinja2 +29 -0
- fastscaff-0.1.0/fastscaff/templates/tests/__init__.py.jinja2 +0 -0
- fastscaff-0.1.0/fastscaff/templates/tests/api/__init__.py.jinja2 +0 -0
- fastscaff-0.1.0/fastscaff/templates/tests/api/test_health.py.jinja2 +22 -0
- fastscaff-0.1.0/fastscaff/templates/tests/conftest.py.jinja2 +15 -0
- fastscaff-0.1.0/fastscaff.egg-info/PKG-INFO +155 -0
- fastscaff-0.1.0/fastscaff.egg-info/SOURCES.txt +79 -0
- fastscaff-0.1.0/fastscaff.egg-info/dependency_links.txt +1 -0
- fastscaff-0.1.0/fastscaff.egg-info/entry_points.txt +2 -0
- fastscaff-0.1.0/fastscaff.egg-info/requires.txt +7 -0
- fastscaff-0.1.0/fastscaff.egg-info/top_level.txt +1 -0
- fastscaff-0.1.0/pyproject.toml +54 -0
- fastscaff-0.1.0/setup.cfg +4 -0
fastscaff-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastscaff
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastAPI project scaffolding tool
|
|
5
|
+
Author: FastScaff
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: fastapi,scaffold,cli,project-generator
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Framework :: FastAPI
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: typer>=0.9.0
|
|
20
|
+
Requires-Dist: rich>=13.0.0
|
|
21
|
+
Requires-Dist: jinja2>=3.1.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
25
|
+
|
|
26
|
+
# FastScaff
|
|
27
|
+
|
|
28
|
+
FastAPI project scaffolding tool - quickly create standardized FastAPI project structures.
|
|
29
|
+
|
|
30
|
+
[中文文档](README_CN.md)
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- One-click creation of standardized FastAPI project structure
|
|
35
|
+
- Multiple ORM support (Tortoise ORM / SQLAlchemy)
|
|
36
|
+
- Built-in JWT authentication
|
|
37
|
+
- Structured logging with structlog
|
|
38
|
+
- Unified exception handling
|
|
39
|
+
- Docker deployment support
|
|
40
|
+
- Testing framework integration
|
|
41
|
+
- SQLite by default (zero configuration)
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Install from source
|
|
47
|
+
cd FastScaff
|
|
48
|
+
pip install -e .
|
|
49
|
+
|
|
50
|
+
# Or install dependencies directly
|
|
51
|
+
pip install -r requirements.txt
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
### Create a New Project
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Using SQLAlchemy (with SQLite by default)
|
|
60
|
+
fastscaff new myproject --orm sqlalchemy
|
|
61
|
+
|
|
62
|
+
# Using Tortoise ORM
|
|
63
|
+
fastscaff new myproject --orm tortoise
|
|
64
|
+
|
|
65
|
+
# Specify output directory
|
|
66
|
+
fastscaff new myproject --output /path/to/dir
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Generated Project Structure
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
myproject/
|
|
73
|
+
├── app/
|
|
74
|
+
│ ├── main.py
|
|
75
|
+
│ ├── core/
|
|
76
|
+
│ │ ├── config.py
|
|
77
|
+
│ │ ├── database.py
|
|
78
|
+
│ │ ├── logger.py
|
|
79
|
+
│ │ ├── lifespan.py
|
|
80
|
+
│ │ ├── security.py
|
|
81
|
+
│ │ └── redis.py
|
|
82
|
+
│ ├── api/
|
|
83
|
+
│ │ └── v1/
|
|
84
|
+
│ │ ├── router.py
|
|
85
|
+
│ │ ├── deps.py
|
|
86
|
+
│ │ └── endpoints/
|
|
87
|
+
│ ├── models/
|
|
88
|
+
│ ├── schemas/
|
|
89
|
+
│ ├── repositories/
|
|
90
|
+
│ ├── services/
|
|
91
|
+
│ ├── middleware/
|
|
92
|
+
│ ├── exceptions/
|
|
93
|
+
│ └── utils/
|
|
94
|
+
├── tests/
|
|
95
|
+
├── .env.example
|
|
96
|
+
├── Dockerfile
|
|
97
|
+
├── docker-compose.yml
|
|
98
|
+
├── Makefile
|
|
99
|
+
└── requirements.txt
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Run the Generated Project
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
cd myproject
|
|
106
|
+
pip install -r requirements.txt
|
|
107
|
+
make dev
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The project runs out of the box with SQLite - no configuration needed.
|
|
111
|
+
|
|
112
|
+
## CLI Reference
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Show help
|
|
116
|
+
fastscaff --help
|
|
117
|
+
|
|
118
|
+
# Create new project
|
|
119
|
+
fastscaff new <project_name>
|
|
120
|
+
--orm ORM choice: tortoise or sqlalchemy (default: tortoise)
|
|
121
|
+
--output Output directory (default: current directory)
|
|
122
|
+
--force Force overwrite existing directory
|
|
123
|
+
--with-rbac Enable RBAC with Casbin
|
|
124
|
+
|
|
125
|
+
# Show version
|
|
126
|
+
fastscaff version
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Project Layers
|
|
130
|
+
|
|
131
|
+
| Layer | Directory | Responsibility |
|
|
132
|
+
|-------|-----------|----------------|
|
|
133
|
+
| API | `api/` | HTTP request handling, parameter validation |
|
|
134
|
+
| Service | `services/` | Business logic, transaction management |
|
|
135
|
+
| Repository | `repositories/` | Data access, CRUD operations |
|
|
136
|
+
| Model | `models/` | Database table definitions |
|
|
137
|
+
| Schema | `schemas/` | Request/response data structures |
|
|
138
|
+
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Install dev dependencies
|
|
143
|
+
pip install -e ".[dev]"
|
|
144
|
+
|
|
145
|
+
# Run tests
|
|
146
|
+
pytest
|
|
147
|
+
|
|
148
|
+
# Code formatting
|
|
149
|
+
ruff check --fix .
|
|
150
|
+
ruff format .
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# FastScaff
|
|
2
|
+
|
|
3
|
+
FastAPI project scaffolding tool - quickly create standardized FastAPI project structures.
|
|
4
|
+
|
|
5
|
+
[中文文档](README_CN.md)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- One-click creation of standardized FastAPI project structure
|
|
10
|
+
- Multiple ORM support (Tortoise ORM / SQLAlchemy)
|
|
11
|
+
- Built-in JWT authentication
|
|
12
|
+
- Structured logging with structlog
|
|
13
|
+
- Unified exception handling
|
|
14
|
+
- Docker deployment support
|
|
15
|
+
- Testing framework integration
|
|
16
|
+
- SQLite by default (zero configuration)
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Install from source
|
|
22
|
+
cd FastScaff
|
|
23
|
+
pip install -e .
|
|
24
|
+
|
|
25
|
+
# Or install dependencies directly
|
|
26
|
+
pip install -r requirements.txt
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### Create a New Project
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Using SQLAlchemy (with SQLite by default)
|
|
35
|
+
fastscaff new myproject --orm sqlalchemy
|
|
36
|
+
|
|
37
|
+
# Using Tortoise ORM
|
|
38
|
+
fastscaff new myproject --orm tortoise
|
|
39
|
+
|
|
40
|
+
# Specify output directory
|
|
41
|
+
fastscaff new myproject --output /path/to/dir
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Generated Project Structure
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
myproject/
|
|
48
|
+
├── app/
|
|
49
|
+
│ ├── main.py
|
|
50
|
+
│ ├── core/
|
|
51
|
+
│ │ ├── config.py
|
|
52
|
+
│ │ ├── database.py
|
|
53
|
+
│ │ ├── logger.py
|
|
54
|
+
│ │ ├── lifespan.py
|
|
55
|
+
│ │ ├── security.py
|
|
56
|
+
│ │ └── redis.py
|
|
57
|
+
│ ├── api/
|
|
58
|
+
│ │ └── v1/
|
|
59
|
+
│ │ ├── router.py
|
|
60
|
+
│ │ ├── deps.py
|
|
61
|
+
│ │ └── endpoints/
|
|
62
|
+
│ ├── models/
|
|
63
|
+
│ ├── schemas/
|
|
64
|
+
│ ├── repositories/
|
|
65
|
+
│ ├── services/
|
|
66
|
+
│ ├── middleware/
|
|
67
|
+
│ ├── exceptions/
|
|
68
|
+
│ └── utils/
|
|
69
|
+
├── tests/
|
|
70
|
+
├── .env.example
|
|
71
|
+
├── Dockerfile
|
|
72
|
+
├── docker-compose.yml
|
|
73
|
+
├── Makefile
|
|
74
|
+
└── requirements.txt
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Run the Generated Project
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cd myproject
|
|
81
|
+
pip install -r requirements.txt
|
|
82
|
+
make dev
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The project runs out of the box with SQLite - no configuration needed.
|
|
86
|
+
|
|
87
|
+
## CLI Reference
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Show help
|
|
91
|
+
fastscaff --help
|
|
92
|
+
|
|
93
|
+
# Create new project
|
|
94
|
+
fastscaff new <project_name>
|
|
95
|
+
--orm ORM choice: tortoise or sqlalchemy (default: tortoise)
|
|
96
|
+
--output Output directory (default: current directory)
|
|
97
|
+
--force Force overwrite existing directory
|
|
98
|
+
--with-rbac Enable RBAC with Casbin
|
|
99
|
+
|
|
100
|
+
# Show version
|
|
101
|
+
fastscaff version
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Project Layers
|
|
105
|
+
|
|
106
|
+
| Layer | Directory | Responsibility |
|
|
107
|
+
|-------|-----------|----------------|
|
|
108
|
+
| API | `api/` | HTTP request handling, parameter validation |
|
|
109
|
+
| Service | `services/` | Business logic, transaction management |
|
|
110
|
+
| Repository | `repositories/` | Data access, CRUD operations |
|
|
111
|
+
| Model | `models/` | Database table definitions |
|
|
112
|
+
| Schema | `schemas/` | Request/response data structures |
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Install dev dependencies
|
|
118
|
+
pip install -e ".[dev]"
|
|
119
|
+
|
|
120
|
+
# Run tests
|
|
121
|
+
pytest
|
|
122
|
+
|
|
123
|
+
# Code formatting
|
|
124
|
+
ruff check --fix .
|
|
125
|
+
ruff format .
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
|
|
8
|
+
from fastscaff import __version__
|
|
9
|
+
from fastscaff.generator import ProjectGenerator
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(
|
|
12
|
+
name="fastscaff",
|
|
13
|
+
help="FastAPI project scaffolding tool",
|
|
14
|
+
add_completion=False,
|
|
15
|
+
)
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _version_callback(value: bool) -> None:
|
|
20
|
+
if value:
|
|
21
|
+
console.print(f"FastScaff version: {__version__}")
|
|
22
|
+
raise typer.Exit()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.callback()
|
|
26
|
+
def main(
|
|
27
|
+
_version: bool = typer.Option(
|
|
28
|
+
None,
|
|
29
|
+
"--version",
|
|
30
|
+
"-v",
|
|
31
|
+
help="Show version",
|
|
32
|
+
callback=_version_callback,
|
|
33
|
+
is_eager=True,
|
|
34
|
+
),
|
|
35
|
+
) -> None:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command()
|
|
40
|
+
def new(
|
|
41
|
+
project_name: str = typer.Argument(..., help="Project name"),
|
|
42
|
+
orm: str = typer.Option(
|
|
43
|
+
"tortoise",
|
|
44
|
+
"--orm",
|
|
45
|
+
"-o",
|
|
46
|
+
help="ORM choice: tortoise or sqlalchemy",
|
|
47
|
+
),
|
|
48
|
+
output: Optional[Path] = typer.Option(
|
|
49
|
+
None,
|
|
50
|
+
"--output",
|
|
51
|
+
"-d",
|
|
52
|
+
help="Output directory",
|
|
53
|
+
),
|
|
54
|
+
with_rbac: bool = typer.Option(
|
|
55
|
+
False,
|
|
56
|
+
"--with-rbac",
|
|
57
|
+
help="Include Casbin RBAC support",
|
|
58
|
+
),
|
|
59
|
+
force: bool = typer.Option(
|
|
60
|
+
False,
|
|
61
|
+
"--force",
|
|
62
|
+
"-f",
|
|
63
|
+
help="Overwrite existing directory",
|
|
64
|
+
),
|
|
65
|
+
) -> None:
|
|
66
|
+
if orm not in ("tortoise", "sqlalchemy"):
|
|
67
|
+
console.print(f"[red]Error: ORM must be 'tortoise' or 'sqlalchemy', got '{orm}'[/red]")
|
|
68
|
+
raise typer.Exit(1)
|
|
69
|
+
|
|
70
|
+
if not project_name.replace("_", "").replace("-", "").isalnum():
|
|
71
|
+
console.print(
|
|
72
|
+
"[red]Error: Project name can only contain alphanumeric, underscores and hyphens[/red]"
|
|
73
|
+
)
|
|
74
|
+
raise typer.Exit(1)
|
|
75
|
+
|
|
76
|
+
output_path = output or Path.cwd()
|
|
77
|
+
project_path = output_path / project_name
|
|
78
|
+
|
|
79
|
+
if project_path.exists() and not force:
|
|
80
|
+
console.print(
|
|
81
|
+
f"[red]Error: Directory '{project_path}' already exists. Use --force to overwrite[/red]"
|
|
82
|
+
)
|
|
83
|
+
raise typer.Exit(1)
|
|
84
|
+
|
|
85
|
+
features = []
|
|
86
|
+
if with_rbac:
|
|
87
|
+
features.append("RBAC (Casbin)")
|
|
88
|
+
|
|
89
|
+
console.print(Panel.fit(
|
|
90
|
+
f"[bold green]Creating project[/bold green]\n\n"
|
|
91
|
+
f"Name: [cyan]{project_name}[/cyan]\n"
|
|
92
|
+
f"ORM: [cyan]{orm}[/cyan]\n"
|
|
93
|
+
f"Features: [cyan]{', '.join(features) if features else 'None'}[/cyan]\n"
|
|
94
|
+
f"Path: [cyan]{project_path}[/cyan]",
|
|
95
|
+
title="FastScaff",
|
|
96
|
+
border_style="blue",
|
|
97
|
+
))
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
generator = ProjectGenerator(
|
|
101
|
+
project_name=project_name,
|
|
102
|
+
orm=orm,
|
|
103
|
+
output_path=project_path,
|
|
104
|
+
with_rbac=with_rbac,
|
|
105
|
+
)
|
|
106
|
+
generator.generate()
|
|
107
|
+
|
|
108
|
+
console.print("\n[bold green]Project created successfully.[/bold green]\n")
|
|
109
|
+
console.print(Panel.fit(
|
|
110
|
+
f"cd {project_name}\n"
|
|
111
|
+
f"pip install -r requirements.txt\n"
|
|
112
|
+
f"make dev",
|
|
113
|
+
title="Next steps",
|
|
114
|
+
border_style="green",
|
|
115
|
+
))
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
119
|
+
raise typer.Exit(1) from None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@app.command()
|
|
123
|
+
def version() -> None:
|
|
124
|
+
console.print(f"FastScaff version: {__version__}")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
app()
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from jinja2 import Environment, FileSystemLoader
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ProjectGenerator:
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
project_name: str,
|
|
17
|
+
orm: str,
|
|
18
|
+
output_path: Path,
|
|
19
|
+
with_rbac: bool = False,
|
|
20
|
+
) -> None:
|
|
21
|
+
self.project_name = project_name
|
|
22
|
+
self.orm = orm
|
|
23
|
+
self.output_path = output_path
|
|
24
|
+
self.with_rbac = with_rbac
|
|
25
|
+
|
|
26
|
+
self.env = Environment(
|
|
27
|
+
loader=FileSystemLoader(str(TEMPLATES_DIR)),
|
|
28
|
+
keep_trailing_newline=True,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
self.context: dict[str, Any] = {
|
|
32
|
+
"project_name": project_name,
|
|
33
|
+
"project_name_snake": project_name.replace("-", "_"),
|
|
34
|
+
"orm": orm,
|
|
35
|
+
"is_tortoise": orm == "tortoise",
|
|
36
|
+
"is_sqlalchemy": orm == "sqlalchemy",
|
|
37
|
+
"with_rbac": with_rbac,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def generate(self) -> None:
|
|
41
|
+
with Progress(
|
|
42
|
+
SpinnerColumn(),
|
|
43
|
+
TextColumn("[progress.description]{task.description}"),
|
|
44
|
+
console=console,
|
|
45
|
+
) as progress:
|
|
46
|
+
task = progress.add_task("Generating project...", total=None)
|
|
47
|
+
|
|
48
|
+
self.output_path.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
|
|
50
|
+
progress.update(task, description="Generating base files...")
|
|
51
|
+
self._generate_base_files()
|
|
52
|
+
|
|
53
|
+
progress.update(task, description="Generating application code...")
|
|
54
|
+
self._generate_app_structure()
|
|
55
|
+
|
|
56
|
+
progress.update(task, description="Generating tests...")
|
|
57
|
+
self._generate_tests()
|
|
58
|
+
|
|
59
|
+
progress.update(task, description="Done")
|
|
60
|
+
|
|
61
|
+
def _generate_base_files(self) -> None:
|
|
62
|
+
base_files = [
|
|
63
|
+
("base/env.example.jinja2", ".env.example"),
|
|
64
|
+
("base/gitignore.jinja2", ".gitignore"),
|
|
65
|
+
("base/pre-commit-config.yaml.jinja2", ".pre-commit-config.yaml"),
|
|
66
|
+
("base/Dockerfile.jinja2", "Dockerfile"),
|
|
67
|
+
("base/docker-compose.yml.jinja2", "docker-compose.yml"),
|
|
68
|
+
("base/Makefile.jinja2", "Makefile"),
|
|
69
|
+
("base/pyproject.toml.jinja2", "pyproject.toml"),
|
|
70
|
+
("base/requirements.txt.jinja2", "requirements.txt"),
|
|
71
|
+
("base/README.md.jinja2", "README.md"),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
for template_path, output_name in base_files:
|
|
75
|
+
self._render_template(template_path, output_name)
|
|
76
|
+
|
|
77
|
+
def _generate_app_structure(self) -> None:
|
|
78
|
+
app_dir = self.output_path / "app"
|
|
79
|
+
app_dir.mkdir(exist_ok=True)
|
|
80
|
+
|
|
81
|
+
self._render_template("app/__init__.py.jinja2", "app/__init__.py")
|
|
82
|
+
self._render_template("app/main.py.jinja2", "app/main.py")
|
|
83
|
+
|
|
84
|
+
self._generate_core()
|
|
85
|
+
self._generate_api()
|
|
86
|
+
self._generate_models()
|
|
87
|
+
self._generate_schemas()
|
|
88
|
+
self._generate_repositories()
|
|
89
|
+
self._generate_services()
|
|
90
|
+
self._generate_middleware()
|
|
91
|
+
self._generate_exceptions()
|
|
92
|
+
self._generate_utils()
|
|
93
|
+
|
|
94
|
+
def _generate_core(self) -> None:
|
|
95
|
+
core_dir = self.output_path / "app" / "core"
|
|
96
|
+
core_dir.mkdir(exist_ok=True)
|
|
97
|
+
|
|
98
|
+
core_files = [
|
|
99
|
+
("app/core/__init__.py.jinja2", "app/core/__init__.py"),
|
|
100
|
+
("app/core/config.py.jinja2", "app/core/config.py"),
|
|
101
|
+
("app/core/logger.py.jinja2", "app/core/logger.py"),
|
|
102
|
+
("app/core/lifespan.py.jinja2", "app/core/lifespan.py"),
|
|
103
|
+
("app/core/security.py.jinja2", "app/core/security.py"),
|
|
104
|
+
("app/core/singleton.py.jinja2", "app/core/singleton.py"),
|
|
105
|
+
("app/core/redis.py.jinja2", "app/core/redis.py"),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
for template_path, output_name in core_files:
|
|
109
|
+
self._render_template(template_path, output_name)
|
|
110
|
+
|
|
111
|
+
if self.orm == "tortoise":
|
|
112
|
+
self._render_template("app/core/database_tortoise.py.jinja2", "app/core/database.py")
|
|
113
|
+
else:
|
|
114
|
+
self._render_template("app/core/database_sqlalchemy.py.jinja2", "app/core/database.py")
|
|
115
|
+
|
|
116
|
+
if self.with_rbac:
|
|
117
|
+
self._render_template("app/core/rbac.py.jinja2", "app/core/rbac.py")
|
|
118
|
+
|
|
119
|
+
def _generate_api(self) -> None:
|
|
120
|
+
api_v1_dir = self.output_path / "app" / "api" / "v1" / "endpoints"
|
|
121
|
+
api_v1_dir.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
|
|
123
|
+
api_files = [
|
|
124
|
+
("app/api/__init__.py.jinja2", "app/api/__init__.py"),
|
|
125
|
+
("app/api/v1/__init__.py.jinja2", "app/api/v1/__init__.py"),
|
|
126
|
+
("app/api/v1/router.py.jinja2", "app/api/v1/router.py"),
|
|
127
|
+
("app/api/v1/endpoints/__init__.py.jinja2", "app/api/v1/endpoints/__init__.py"),
|
|
128
|
+
("app/api/v1/endpoints/health.py.jinja2", "app/api/v1/endpoints/health.py"),
|
|
129
|
+
("app/api/v1/endpoints/auth.py.jinja2", "app/api/v1/endpoints/auth.py"),
|
|
130
|
+
("app/api/v1/endpoints/users.py.jinja2", "app/api/v1/endpoints/users.py"),
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
for template_path, output_name in api_files:
|
|
134
|
+
self._render_template(template_path, output_name)
|
|
135
|
+
|
|
136
|
+
def _generate_models(self) -> None:
|
|
137
|
+
models_dir = self.output_path / "app" / "models"
|
|
138
|
+
models_dir.mkdir(exist_ok=True)
|
|
139
|
+
|
|
140
|
+
self._render_template("app/models/__init__.py.jinja2", "app/models/__init__.py")
|
|
141
|
+
|
|
142
|
+
if self.orm == "tortoise":
|
|
143
|
+
self._render_template("app/models/base_tortoise.py.jinja2", "app/models/base.py")
|
|
144
|
+
self._render_template("app/models/user_tortoise.py.jinja2", "app/models/user.py")
|
|
145
|
+
else:
|
|
146
|
+
self._render_template("app/models/base_sqlalchemy.py.jinja2", "app/models/base.py")
|
|
147
|
+
self._render_template("app/models/user_sqlalchemy.py.jinja2", "app/models/user.py")
|
|
148
|
+
|
|
149
|
+
def _generate_schemas(self) -> None:
|
|
150
|
+
schemas_dir = self.output_path / "app" / "schemas"
|
|
151
|
+
schemas_dir.mkdir(exist_ok=True)
|
|
152
|
+
|
|
153
|
+
schema_files = [
|
|
154
|
+
("app/schemas/__init__.py.jinja2", "app/schemas/__init__.py"),
|
|
155
|
+
("app/schemas/base.py.jinja2", "app/schemas/base.py"),
|
|
156
|
+
("app/schemas/auth.py.jinja2", "app/schemas/auth.py"),
|
|
157
|
+
("app/schemas/user.py.jinja2", "app/schemas/user.py"),
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
for template_path, output_name in schema_files:
|
|
161
|
+
self._render_template(template_path, output_name)
|
|
162
|
+
|
|
163
|
+
def _generate_repositories(self) -> None:
|
|
164
|
+
repo_dir = self.output_path / "app" / "repositories"
|
|
165
|
+
repo_dir.mkdir(exist_ok=True)
|
|
166
|
+
|
|
167
|
+
self._render_template("app/repositories/__init__.py.jinja2", "app/repositories/__init__.py")
|
|
168
|
+
|
|
169
|
+
if self.orm == "tortoise":
|
|
170
|
+
self._render_template("app/repositories/base_tortoise.py.jinja2", "app/repositories/base.py")
|
|
171
|
+
self._render_template("app/repositories/user_tortoise.py.jinja2", "app/repositories/user.py")
|
|
172
|
+
else:
|
|
173
|
+
self._render_template("app/repositories/base_sqlalchemy.py.jinja2", "app/repositories/base.py")
|
|
174
|
+
self._render_template("app/repositories/user_sqlalchemy.py.jinja2", "app/repositories/user.py")
|
|
175
|
+
|
|
176
|
+
def _generate_services(self) -> None:
|
|
177
|
+
services_dir = self.output_path / "app" / "services"
|
|
178
|
+
services_dir.mkdir(exist_ok=True)
|
|
179
|
+
|
|
180
|
+
if self.orm == "tortoise":
|
|
181
|
+
self._render_template("app/services/__init___tortoise.py.jinja2", "app/services/__init__.py")
|
|
182
|
+
self._render_template("app/services/auth_tortoise.py.jinja2", "app/services/auth.py")
|
|
183
|
+
self._render_template("app/services/user_tortoise.py.jinja2", "app/services/user.py")
|
|
184
|
+
else:
|
|
185
|
+
self._render_template("app/services/__init___sqlalchemy.py.jinja2", "app/services/__init__.py")
|
|
186
|
+
self._render_template("app/services/auth_sqlalchemy.py.jinja2", "app/services/auth.py")
|
|
187
|
+
self._render_template("app/services/user_sqlalchemy.py.jinja2", "app/services/user.py")
|
|
188
|
+
|
|
189
|
+
def _generate_middleware(self) -> None:
|
|
190
|
+
middleware_dir = self.output_path / "app" / "middleware"
|
|
191
|
+
middleware_dir.mkdir(exist_ok=True)
|
|
192
|
+
|
|
193
|
+
middleware_files = [
|
|
194
|
+
("app/middleware/__init__.py.jinja2", "app/middleware/__init__.py"),
|
|
195
|
+
("app/middleware/logging.py.jinja2", "app/middleware/logging.py"),
|
|
196
|
+
("app/middleware/cors.py.jinja2", "app/middleware/cors.py"),
|
|
197
|
+
("app/middleware/security.py.jinja2", "app/middleware/security.py"),
|
|
198
|
+
("app/middleware/jwt.py.jinja2", "app/middleware/jwt.py"),
|
|
199
|
+
("app/middleware/sign.py.jinja2", "app/middleware/sign.py"),
|
|
200
|
+
("app/middleware/tracing.py.jinja2", "app/middleware/tracing.py"),
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
for template_path, output_name in middleware_files:
|
|
204
|
+
self._render_template(template_path, output_name)
|
|
205
|
+
|
|
206
|
+
def _generate_exceptions(self) -> None:
|
|
207
|
+
exceptions_dir = self.output_path / "app" / "exceptions"
|
|
208
|
+
exceptions_dir.mkdir(exist_ok=True)
|
|
209
|
+
|
|
210
|
+
exception_files = [
|
|
211
|
+
("app/exceptions/__init__.py.jinja2", "app/exceptions/__init__.py"),
|
|
212
|
+
("app/exceptions/base.py.jinja2", "app/exceptions/base.py"),
|
|
213
|
+
("app/exceptions/handlers.py.jinja2", "app/exceptions/handlers.py"),
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
for template_path, output_name in exception_files:
|
|
217
|
+
self._render_template(template_path, output_name)
|
|
218
|
+
|
|
219
|
+
def _generate_utils(self) -> None:
|
|
220
|
+
utils_dir = self.output_path / "app" / "utils"
|
|
221
|
+
utils_dir.mkdir(exist_ok=True)
|
|
222
|
+
|
|
223
|
+
utils_files = [
|
|
224
|
+
("app/utils/__init__.py.jinja2", "app/utils/__init__.py"),
|
|
225
|
+
("app/utils/snowflake.py.jinja2", "app/utils/snowflake.py"),
|
|
226
|
+
("app/utils/sort_helper.py.jinja2", "app/utils/sort_helper.py"),
|
|
227
|
+
("app/utils/rate_limiter.py.jinja2", "app/utils/rate_limiter.py"),
|
|
228
|
+
("app/utils/auth.py.jinja2", "app/utils/auth.py"),
|
|
229
|
+
("app/utils/cache.py.jinja2", "app/utils/cache.py"),
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
for template_path, output_name in utils_files:
|
|
233
|
+
self._render_template(template_path, output_name)
|
|
234
|
+
|
|
235
|
+
def _generate_tests(self) -> None:
|
|
236
|
+
tests_dir = self.output_path / "tests"
|
|
237
|
+
tests_dir.mkdir(exist_ok=True)
|
|
238
|
+
|
|
239
|
+
api_tests_dir = tests_dir / "api"
|
|
240
|
+
api_tests_dir.mkdir(exist_ok=True)
|
|
241
|
+
|
|
242
|
+
test_files = [
|
|
243
|
+
("tests/__init__.py.jinja2", "tests/__init__.py"),
|
|
244
|
+
("tests/conftest.py.jinja2", "tests/conftest.py"),
|
|
245
|
+
("tests/api/__init__.py.jinja2", "tests/api/__init__.py"),
|
|
246
|
+
("tests/api/test_health.py.jinja2", "tests/api/test_health.py"),
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
for template_path, output_name in test_files:
|
|
250
|
+
self._render_template(template_path, output_name)
|
|
251
|
+
|
|
252
|
+
def _render_template(self, template_path: str, output_name: str) -> None:
|
|
253
|
+
template = self.env.get_template(template_path)
|
|
254
|
+
content = template.render(**self.context)
|
|
255
|
+
|
|
256
|
+
output_file = self.output_path / output_name
|
|
257
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
258
|
+
output_file.write_text(content, encoding="utf-8")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|