Skip to content

Commit 3a2542d

Browse files
authored
Cleaned up UI + replaced json config files with toml + fixed kafka local runs. (#249)
1 parent fed86a3 commit 3a2542d

11 files changed

Lines changed: 365 additions & 335 deletions

File tree

fastapi_template/__main__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from pathlib import Path
22

3-
from cookiecutter.exceptions import (FailedHookException,
4-
OutputDirExistsException)
3+
from cookiecutter.exceptions import FailedHookException, OutputDirExistsException
54
from cookiecutter.main import cookiecutter
65
from termcolor import cprint
76

@@ -21,7 +20,7 @@ def generate_project(context: BuilderContext) -> None:
2120
cookiecutter(
2221
template=f"{script_dir}/template",
2322
extra_context=context.dict(),
24-
default_config=BuilderContext().dict(),
23+
default_config=True,
2524
no_input=True,
2625
overwrite_if_exists=context.force,
2726
)

fastapi_template/template/hooks/post_gen_project.py

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
11
#!/usr/bin/env python
2-
import json
3-
import os
42
import shutil
53
import subprocess
4+
import tomllib
5+
import shlex
66

77
from termcolor import cprint, colored
88
from pathlib import Path
99

10-
CONDITIONAL_MANIFEST = "conditional_files.json"
11-
REPLACE_MANIFEST = "replaceable_files.json"
10+
CONDITIONAL_MANIFEST = Path("conditional_files.toml")
11+
REPLACE_MANIFEST = Path("replaceable_files.toml")
1212

1313

14-
def delete_resource(resource):
15-
if os.path.isfile(resource):
16-
os.remove(resource)
17-
elif os.path.isdir(resource):
14+
def delete_resource(resource: Path):
15+
if resource.is_file():
16+
resource.unlink()
17+
elif resource.is_dir():
1818
shutil.rmtree(resource)
1919

2020

2121
def delete_resources_for_disabled_features():
22-
with open(CONDITIONAL_MANIFEST) as manifest_file:
23-
manifest = json.load(manifest_file)
24-
for feature_name, feature in manifest.items():
25-
if feature["enabled"].lower() != "true":
22+
with CONDITIONAL_MANIFEST.open("rb") as manifest_file:
23+
manifest = tomllib.load(manifest_file)
24+
25+
for feature in manifest["features"]:
26+
enabled = feature["enabled"].lower() != "true"
27+
name = feature["name"]
28+
resources = feature["resources"]
29+
if enabled:
2630
text = "{} resources for disabled feature {}...".format(
2731
colored("Removing", color="red"),
28-
colored(feature_name, color="magenta", attrs=["underline"]),
32+
colored(name, color="magenta", attrs=["underline"]),
2933
)
3034
print(text)
31-
for resource in feature["resources"]:
32-
delete_resource(resource)
35+
for resource in resources:
36+
delete_resource(Path(resource))
3337
delete_resource(CONDITIONAL_MANIFEST)
3438
cprint("cleanup complete!", color="green")
3539

@@ -40,14 +44,15 @@ def replace_resources():
4044
colored("resources", color="green"), colored("new project", color="blue")
4145
)
4246
)
43-
with open(REPLACE_MANIFEST) as replace_manifest:
44-
manifest = json.load(replace_manifest)
45-
for target, replaces in manifest.items():
46-
target_path = Path(target)
47-
delete_resource(target_path)
48-
for src_file in map(Path, replaces):
47+
with REPLACE_MANIFEST.open("rb") as replace_manifest:
48+
manifest = tomllib.load(replace_manifest)
49+
for substitution in manifest["sub"]:
50+
target = Path(substitution["target"])
51+
replaces = [Path(path) for path in substitution["replaces"]]
52+
delete_resource(target)
53+
for src_file in replaces:
4954
if src_file.exists():
50-
shutil.move(src_file, target_path)
55+
shutil.move(src_file, target)
5156
delete_resource(REPLACE_MANIFEST)
5257
print(
5358
"Resources are happy to be where {}.".format(
@@ -56,17 +61,41 @@ def replace_resources():
5661
)
5762

5863

64+
def run_cmd(cmd: str, ignore_error: bool = False):
65+
out = subprocess.run(
66+
shlex.split(cmd),
67+
stdout=subprocess.PIPE,
68+
stderr=subprocess.PIPE,
69+
)
70+
if out.returncode != 0 and not ignore_error:
71+
cprint(" WARNING ".center(50, "="))
72+
cprint(
73+
f"[WARN] Command `{cmd}` was not successfull. Check output below.",
74+
"yellow",
75+
)
76+
cprint(
77+
"However, the project was generated. So it could be a false-positive.",
78+
"yellow",
79+
)
80+
cprint(out.stdout.decode(), "red")
81+
cprint(out.stderr.decode(), "red")
82+
exit(1)
83+
84+
5985
def init_repo():
60-
subprocess.run(["git", "init"], stdout=subprocess.PIPE)
61-
cprint("Git repository initialized.", "green")
62-
subprocess.run(["git", "add", "."], stdout=subprocess.PIPE)
63-
cprint("Added files to index.", "green")
64-
subprocess.run(["uv", "sync"])
65-
subprocess.run(["uv", "run", "pre-commit", "install"])
66-
cprint("pre-commit installed.", "green")
67-
subprocess.run(["uv", "run", "pre-commit", "run", "-a"])
68-
subprocess.run(["git", "add", "."], stdout=subprocess.PIPE)
69-
subprocess.run(["git", "commit", "-m", "Initial commit"], stdout=subprocess.PIPE)
86+
run_cmd("git init")
87+
cprint(" Git repository initialized", "green")
88+
run_cmd("git add .")
89+
cprint("🐍 Installing python dpendencies with UV", "green")
90+
run_cmd("uv sync")
91+
run_cmd("uv run pre-commit install")
92+
cprint("📚🖌️📄📏 Tidying up the project", "green")
93+
for _ in range(2):
94+
run_cmd("uv run pre-commit run -a", ignore_error=True)
95+
run_cmd("git add .")
96+
cprint("🚀Creating your first commit", "green")
97+
run_cmd("git commit -m 'Initial commit'")
98+
7099

71100
if __name__ == "__main__":
72101
delete_resources_for_disabled_features()

fastapi_template/template/{{cookiecutter.project_name}}/.env

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66
{%- endif %}
77
{%- if cookiecutter.add_users == "True" %}
88
USERS_SECRET=""
9-
{%- endif %}
9+
{%- endif %}
10+
{%- if cookiecutter.enable_kafka == "True" %}
11+
{{cookiecutter.project_name | upper}}_KAFKA_BOOTSTRAP_SERVERS='["localhost:9094"]'
12+
{%- endif %}

fastapi_template/template/{{cookiecutter.project_name}}/README.md

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,15 @@ You can read more about uv here: https://docs.astral.sh/ruff/
2525
You can start the project with docker using this command:
2626

2727
```bash
28-
docker-compose up --build
29-
```
30-
31-
If you want to develop in docker with autoreload and exposed ports add `-f deploy/docker-compose.dev.yml` to your docker command.
32-
Like this:
33-
34-
```bash
35-
docker-compose -f docker-compose.yml -f deploy/docker-compose.dev.yml --project-directory . up --build
28+
docker compose up --build
3629
```
3730

3831
This command exposes the web application on port 8000, mounts current directory and enables autoreload.
3932

4033
But you have to rebuild image every time you modify `uv.lock` or `pyproject.toml` with this command:
4134

4235
```bash
43-
docker-compose build
36+
docker compose build
4437
```
4538

4639
## Project structure
@@ -98,12 +91,11 @@ you can add `-f ./deploy/docker-compose.otlp.yml` to your docker command.
9891
Like this:
9992
10093
```bash
101-
docker-compose -f docker-compose.yml -f deploy/docker-compose.otlp.yml --project-directory . up
94+
docker compose -f docker-compose.yml -f deploy/docker-compose.otlp.yml --project-directory . up
10295
```
10396
104-
This command will start OpenTelemetry collector and jaeger.
105-
After sending a requests you can see traces in jaeger's UI
106-
at http://localhost:16686/.
97+
This command will start grafana with full opentelemetry stack at http://localhost:3000/.
98+
After sending a requests you can see traces at explore tab in drilldown.
10799
108100
This docker configuration is not supposed to be used in production.
109101
It's only for demo purpose.
@@ -189,30 +181,35 @@ aerich migrate
189181
If you want to run it in docker, simply run:
190182
191183
```bash
192-
docker-compose run --build --rm api pytest -vv .
193-
docker-compose down
184+
docker compose run --build --rm api pytest -vv .
185+
docker compose down
194186
```
195187
196188
For running tests on your local machine.
197189
198-
{%- if cookiecutter.db_info.name != "none" %}
199-
{%- if cookiecutter.db_info.name != "sqlite" %}
200-
1. you need to start a database.
190+
{%- if ((cookiecutter.db_info.name != "none" and cookiecutter.db_info.name != "sqlite") or
191+
(cookiecutter.enable_redis == "True") or
192+
(cookiecutter.enable_rmq == "True") or
193+
(cookiecutter.enable_kafka == "True") or
194+
(cookiecutter.enable_nats == "True")
195+
) %}
196+
1. you need to start all aux services.
201197
202-
I prefer doing it with docker:
203-
```
204-
{%- if cookiecutter.db_info.name == "postgresql" %}
205-
docker run -p "{{cookiecutter.db_info.port}}:{{cookiecutter.db_info.port}}" -e "POSTGRES_PASSWORD={{cookiecutter.project_name}}" -e "POSTGRES_USER={{cookiecutter.project_name}}" -e "POSTGRES_DB={{cookiecutter.project_name}}" {{cookiecutter.db_info.image}}
206-
{%- endif %}
207-
{%- if cookiecutter.db_info.name == "mysql" %}
208-
docker run -p "{{cookiecutter.db_info.port}}:{{cookiecutter.db_info.port}}" -e "MYSQL_PASSWORD={{cookiecutter.project_name}}" -e "MYSQL_USER={{cookiecutter.project_name}}" -e "MYSQL_DATABASE={{cookiecutter.project_name}}" -e ALLOW_EMPTY_PASSWORD=yes {{cookiecutter.db_info.image}}
209-
{%- endif %}
198+
We can do so by using our `docker-compose.yaml` configuration. It already has everything we need.
199+
200+
```bash
201+
docker compose up -d --wait{%- if cookiecutter.db_info.name != 'none' %} db{%- endif %}{%- if cookiecutter.enable_redis == "True" %} redis{%- endif %}{%- if cookiecutter.enable_rmq == "True" %} rmq{%- endif %}{%- if cookiecutter.enable_kafka == "True" %} kafka{%- endif %}{%- if cookiecutter.enable_nats == "True" %} nats{%- endif %}
210202
```
211-
{%- endif %}
212-
{%- endif %}
213203
204+
2. Run tests.
205+
```bash
206+
pytest -vv .
207+
```
208+
{%- else %}
209+
Simply run
214210
215-
2. Run the pytest.
216211
```bash
217212
pytest -vv .
218213
```
214+
{%- endif %}
215+

0 commit comments

Comments
 (0)