Skip to content

Commit dafeb7c

Browse files
committed
Fin
1 parent 1a8449d commit dafeb7c

8 files changed

Lines changed: 138 additions & 87 deletions

File tree

fastapi_forge/frontend/panels/project_config_panel.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def _handle_builtin_auth_change(self, event: ValueChangeEventArguments) -> None:
197197
state.render_models_fn()
198198
ui.notify("The 'auth_user' model has been deleted.", type="positive")
199199

200-
async def _warn_override(self) -> bool:
200+
async def _warn_overwrite(self) -> bool:
201201
"""Show a confirmation dialog if the project already exists."""
202202
dialog = ui.dialog()
203203
with dialog, ui.card().classes("w-full max-w-md p-6 text-center"):
@@ -222,8 +222,8 @@ async def _create_project(self) -> None:
222222

223223
if project_path.exists():
224224
try:
225-
override = await self._warn_override()
226-
if not override:
225+
overwrite = await self._warn_overwrite()
226+
if not overwrite:
227227
ui.notify("Project generation cancelled.", type="warning")
228228
return
229229
except Exception as e:

fastapi_forge/template/hooks/post_gen_project.py

Lines changed: 76 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,98 +2,112 @@
22
import yaml
33
import os
44
import shutil
5-
from typing import Any
65
from fastapi_forge.logger import logger
76

87

9-
def git_init() -> None:
10-
subprocess.run(["git", "init"])
11-
subprocess.run(["git", "add", "."])
8+
def run_command(command: list[str]) -> None:
9+
subprocess.run(command, check=True)
1210

1311

1412
def uv_init() -> None:
15-
subprocess.run(["uv", "lock"])
13+
run_command(["uv", "lock"])
1614

1715

1816
def lint() -> None:
19-
subprocess.run(["make", "lint"])
17+
run_command(["make", "lint"])
2018

2119

2220
def make_env() -> None:
23-
subprocess.run(["cp", ".env.example", ".env"])
21+
run_command(["cp", ".env.example", ".env"])
22+
23+
24+
def _process_paths(paths: list[str], cwd: str) -> tuple[list[str], list[str]]:
25+
files = []
26+
folders = []
27+
for path in paths:
28+
if not path:
29+
continue
30+
full_path = os.path.join(cwd, path)
31+
if path.endswith((".py", ".yaml")):
32+
files.append(full_path)
33+
else:
34+
folders.append(full_path)
35+
return files, folders
2436

2537

2638
def _get_delete_flagged() -> tuple[list[str], list[str]]:
27-
conf_fn = "forge-config.yaml"
28-
with open(conf_fn) as stream:
29-
y = yaml.safe_load(stream)
30-
cwd = os.getcwd()
31-
files = [os.path.join(cwd, conf_fn)]
32-
folders = []
33-
config: dict[str, Any] = y["config"]
34-
35-
for _, v in config.items():
36-
item_type = v["type"]
37-
if item_type != "bool":
38-
continue
39-
value: bool = v["value"]
40-
if value is True:
41-
continue
42-
paths: list[str] = v["paths"]
43-
if not paths:
44-
continue
45-
for path in paths:
46-
full_path = os.path.join(cwd, path)
47-
if path.endswith(".py") or path.endswith(".yaml"):
48-
files.append(full_path)
49-
else:
50-
folders.append(full_path)
39+
files = []
40+
folders = []
41+
cwd = os.getcwd()
42+
43+
try:
44+
with open("forge-config.yaml") as stream:
45+
config = yaml.safe_load(stream) or {}
46+
paths_config = config.get("paths", {})
47+
48+
for item in paths_config.values():
49+
if "enabled" in item and not item["enabled"]:
50+
new_files, new_folders = _process_paths(item.get("paths", []), cwd)
51+
files.extend(new_files)
52+
folders.extend(new_folders)
53+
54+
if "requires_all" in item:
55+
conditions_met = all(
56+
paths_config.get(req, {}).get("enabled", False) is False
57+
for req in item["requires_all"]
58+
)
59+
if conditions_met:
60+
new_files, new_folders = _process_paths(
61+
item.get("paths", []), cwd
62+
)
63+
files.extend(new_files)
64+
folders.extend(new_folders)
65+
66+
except Exception as e:
67+
logger.error(f"Error reading config file: {e}")
5168

5269
return files, folders
5370

5471

55-
def delete_empty_init_folders(root_dir: str = "src") -> None:
56-
"""Delete folders that only contain empty __init__.py files."""
57-
for dirpath, dirnames, filenames in os.walk(root_dir, topdown=False):
58-
if dirpath == root_dir:
59-
continue
72+
def _is_empty_init(dirpath: str) -> bool:
73+
init_file = os.path.join(dirpath, "__init__.py")
74+
try:
75+
with open(init_file, "r") as f:
76+
return not any(
77+
line.strip() and not line.strip().startswith("#")
78+
for line in f.read().splitlines()
79+
)
80+
except OSError:
81+
return False
6082

61-
if set(filenames) == {"__init__.py"} and not dirnames:
62-
init_file = os.path.join(dirpath, "__init__.py")
6383

64-
try:
65-
with open(init_file, "r") as f:
66-
content = f.read()
67-
has_code = any(
68-
line.strip() and not line.strip().startswith("#")
69-
for line in content.splitlines()
70-
)
71-
if not has_code:
72-
os.remove(init_file)
73-
os.rmdir(dirpath)
74-
logger.info(f"Deleted empty package: {dirpath}")
75-
else:
76-
logger.info(f"Keeping package with code: {dirpath}")
77-
except OSError as exc:
78-
logger.info(f"Error processing package {dirpath}: {exc}")
84+
def delete_empty_init_folders(root_dir: str = "src") -> None:
85+
for dirpath, dirnames, filenames in os.walk(root_dir, topdown=False):
86+
if dirpath != root_dir and set(filenames) == {"__init__.py"} and not dirnames:
87+
if _is_empty_init(dirpath):
88+
os.remove(os.path.join(dirpath, "__init__.py"))
89+
os.rmdir(dirpath)
90+
logger.info(f"Deleted empty package: {dirpath}")
7991

8092

81-
def cleanup():
93+
def cleanup() -> None:
8294
files, folders = _get_delete_flagged()
8395

84-
for file_path in files:
96+
for path in files:
8597
try:
86-
os.remove(file_path)
87-
logger.info(f"Deleted file: {file_path}")
98+
if os.path.exists(path):
99+
os.remove(path)
100+
logger.info(f"Deleted file: {path}")
88101
except OSError as exc:
89-
logger.info(f"Error deleting file {file_path}: {exc}")
102+
logger.error(f"Error deleting file {path}: {exc}")
90103

91-
for folder_path in folders:
104+
for path in folders:
92105
try:
93-
shutil.rmtree(folder_path)
94-
logger.info(f"Deleted folder: {folder_path}")
106+
if os.path.exists(path):
107+
shutil.rmtree(path)
108+
logger.info(f"Deleted folder: {path}")
95109
except OSError as exc:
96-
logger.info(f"Error deleting folder {folder_path}: {exc}")
110+
logger.error(f"Error deleting folder {path}: {exc}")
97111

98112
delete_empty_init_folders()
99113

@@ -103,4 +117,3 @@ def cleanup():
103117
uv_init()
104118
make_env()
105119
lint()
106-
# git_init()

fastapi_forge/template/{{cookiecutter.project_name}}/docker-compose.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ services:
99
- "8000:8000"
1010
env_file:
1111
- .env
12+
{%- if cookiecutter.use_postgres or cookiecutter.use_redis or cookiecutter.use_rabbitmq %}
1213
depends_on:
1314
{%- if cookiecutter.use_postgres %}
1415
postgres:
@@ -18,6 +19,11 @@ services:
1819
redis:
1920
condition: service_healthy
2021
{%- endif %}
22+
{%- if cookiecutter.use_rabbitmq %}
23+
rabbitmq:
24+
condition: service_healthy
25+
{%- endif %}
26+
{%- endif %}
2127
environment:
2228
{{ cookiecutter.project_name|upper|replace('-', '_') }}_RELOAD: "True"
2329
{{ cookiecutter.project_name|upper|replace('-', '_') }}_SERVER_HOST: 0.0.0.0
@@ -119,6 +125,7 @@ services:
119125
volumes:
120126
- {{ cookiecutter.project_name }}-rabbitmq-data:/var/lib/rabbitmq
121127
{% endif %}
128+
{%- if cookiecutter.use_postgres or cookiecutter.use_redis or cookiecutter.use_rabbitmq %}
122129
volumes:
123130
{%- if cookiecutter.use_postgres %}
124131
{{ cookiecutter.project_name }}-pg-data:
@@ -131,4 +138,5 @@ volumes:
131138
{%- if cookiecutter.use_rabbitmq -%}
132139
{{ cookiecutter.project_name }}-rabbitmq-data:
133140
name: {{ cookiecutter.project_name }}-rabbitmq-data
134-
{% endif %}
141+
{% endif %}
142+
{%- endif %}
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
1-
# Used for post project generation tasks.
1+
# Used for post project generation tasks
22
# You may delete this file!
33

4-
config:
4+
paths:
55
use_postgres:
6-
value: {{cookiecutter.use_postgres | lower}}
7-
type: bool
8-
paths:
9-
6+
enabled: {{cookiecutter.use_postgres | lower}}
7+
paths: []
8+
109
use_alembic:
11-
value: {{cookiecutter.use_alembic | lower}}
12-
type: bool
10+
enabled: {{cookiecutter.use_alembic | lower}}
1311
paths:
1412
- migrations
1513

1614
use_builtin_auth:
17-
value: {{cookiecutter.use_builtin_auth | lower}}
18-
type: bool
15+
enabled: {{cookiecutter.use_builtin_auth | lower}}
1916
paths:
2017
- src/dependencies/auth_dependencies.py
2118
- src/dtos/auth_dtos.py
2219
- src/routes/auth_routes.py
2320
- src/utils/auth_utils.py
24-
- src/constants.py
2521

2622
use_redis:
27-
value: {{cookiecutter.use_redis | lower}}
28-
type: bool
23+
enabled: {{cookiecutter.use_redis | lower}}
2924
paths:
3025
- src/services/redis
3126

3227
use_rabbitmq:
33-
value: {{cookiecutter.use_rabbitmq | lower}}
34-
type: bool
28+
enabled: {{cookiecutter.use_rabbitmq | lower}}
3529
paths:
3630
- src/services/rabbitmq
3731
- src/routes/demo_routes.py
3832

33+
constants:
34+
requires_all:
35+
- use_builtin_auth
36+
- use_rabbitmq
37+
paths:
38+
- src/constants.py

fastapi_forge/template/{{cookiecutter.project_name}}/src/dependencies/auth_dependencies.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{% if cookiecutter.use_builtin_auth %}
12
from typing import Annotated
23

34
from fastapi import Depends, HTTPException, Request
@@ -53,3 +54,4 @@ async def get_current_user(
5354

5455

5556
GetCurrentUser = Annotated[{{ cookiecutter.auth_model.name_cc }}DTO, Depends(get_current_user)]
57+
{% endif %}

fastapi_forge/template/{{cookiecutter.project_name}}/src/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
from src.settings import settings
77
from src.routes import base_router
88
from src.middleware import add_middleware
9-
{%- if cookiecutter.use_postgres %}
9+
{% if cookiecutter.use_postgres %}
1010
from src.db import db_lifetime
1111
{% endif %}
12-
{%- if cookiecutter.use_redis -%}
12+
{% if cookiecutter.use_redis -%}
1313
from src.services.redis import redis_lifetime
1414
{% endif %}
15-
{%- if cookiecutter.use_rabbitmq -%}
15+
{% if cookiecutter.use_rabbitmq -%}
1616
from src.services.rabbitmq import rabbitmq_lifetime
1717
from src.constants import QUEUE_CONFIGS
1818
{% endif %}
Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,45 @@
11
from fastapi import APIRouter
22
from src import exceptions
33
from src.services.redis import GetRedis
4+
{% if cookiecutter.use_rabbitmq %}
5+
from src.services.rabbitmq import GetRabbitMQ
6+
{% endif %}
7+
from pydantic import BaseModel
8+
from typing import Any
49

510
router = APIRouter(prefix="/demo")
611

12+
{% if cookiecutter.use_rabbitmq %}
13+
class RabbitMQDemoMessage(BaseModel):
14+
key: str
15+
value: str
16+
{% endif %}
17+
718
{% if cookiecutter.use_redis %}
819
@router.post("/set-redis")
9-
async def set_redis_value(key: str, value: str, redis: GetRedis) -> dict[str, str]:
20+
async def set_redis_value(key: str, value: str, redis: GetRedis,) -> dict[str, Any]:
1021
await redis.set(key, value)
1122
return {"message": "Value set successfully", "key": key, "value": value}
1223

1324

1425
@router.get("/get-redis")
15-
async def get_redis_value(key: str, redis: GetRedis) -> dict[str, str]:
26+
async def get_redis_value(key: str, redis: GetRedis,) -> dict[str, Any]:
1627
value = await redis.get(key)
1728
if value is None:
1829
raise exceptions.Http404(detail="Key not found in Redis")
1930
return {"key": key, "value": value}
31+
{% endif %}
32+
33+
{% if cookiecutter.use_rabbitmq %}
34+
@router.post("/send-rabbitmq")
35+
async def send_rabbitmq_message(
36+
message: RabbitMQDemoMessage,
37+
rabbitmq: GetRabbitMQ,
38+
) -> dict[str, Any]:
39+
await rabbitmq.send_demo_message(message)
40+
return {
41+
"message": "RabbitMQ message sent successfully",
42+
"key": message.key,
43+
"value": message.value,
44+
}
2045
{% endif %}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from src.services.rabbitmq.rabbitmq_dependencies import GetRabbitMQ
2+
3+
__all__ = ["GetRabbitMQ"]

0 commit comments

Comments
 (0)