Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 16 additions & 42 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ INFRA_INIT_SERVICES ?=
# -----------------------------
# Internal vars / aliases
# -----------------------------
PYTHON_BIN := python
DOCKER_COMPOSE := docker compose -p $(PROJECT_NAME)
DOCKER_COMPOSE_PRUNE := scripts/makefile/docker_prune.sh
PIP_AUDIT := scripts/makefile/pip_audit.sh
SLOTSCHECK := scripts/makefile/slotscheck.sh
DOCKER_ENV := scripts/makefile/docker_env.sh
LOCAL_ENV := scripts/makefile/local_env.sh
DOCKER_PRUNE := scripts/makefile/docker_prune.sh
PYCACHE_DEL := scripts/makefile/pycache_del.sh
DISHKA_PLOT_DATA := scripts/dishka/plot_dependencies_data.py

# Test stack is isolated by project name
TEST_PROJECT ?= $(PROJECT_NAME)-test
Expand Down Expand Up @@ -52,25 +57,20 @@ PYTEST_ARGS_COV_DOCKER := \
# Safety
.PHONY: pip-audit
pip-audit:
tmp=$$(mktemp -d); trap 'rm -rf "$$tmp"' EXIT; \
uv -qq export --format pylock.toml -o "$$tmp/pylock.toml"; \
pip-audit --locked "$$tmp" \
|| echo "WARNING: pip-audit found vulnerabilities (non-blocking)" >&2
$(PIP_AUDIT)

# Code quality
.PHONY: slotscheck lint test check
slotscheck:
slotscheck $(SLOTSCHECK_TARGET) 2>&1 | tee /dev/stderr \
| { grep -m1 "Failed to import" || true; } | cut -d"'" -f2 \
| xargs -r -n1 $(PYTHON_BIN) -c 'import importlib,sys; importlib.import_module(sys.argv[1])'
$(SLOTSCHECK) src

lint:
ruff check --fix
ruff format
tombi format
tombi lint
deptry
$(MAKE) slotscheck SLOTSCHECK_TARGET=src
$(MAKE) slotscheck
lint-imports
mypy

Expand All @@ -85,33 +85,10 @@ check: lint test
# Docker compose
.PHONY: docker-env local-env upd up upd-local up-local down stop-all
docker-env:
{ \
echo "# This .env file is generated automatically for DOCKER environment by Makefile."; \
echo "# Do not edit it directly; edit env.example / .secrets and Makefile instead."; \
echo; \
cat env.example; \
if [ -f .secrets ]; then \
echo; \
echo "# --- secrets from .secrets (not committed) ---"; \
cat .secrets; \
fi; \
} > .env
$(DOCKER_ENV)

local-env:
{ \
echo "# This .env file is generated automatically for LOCAL environment by Makefile."; \
echo "# Do not edit it directly; edit env.example / .secrets and Makefile instead."; \
echo; \
sed \
-e 's|^EXAMPLE_SERVICE_URL=.*|EXAMPLE_SERVICE_URL=http://127.0.0.1:51999|' \
-e 's|^POSTGRES_HOST=.*|POSTGRES_HOST=127.0.0.1|' \
env.example; \
if [ -f .secrets ]; then \
echo; \
echo "# --- secrets from .secrets (not committed) ---"; \
cat .secrets; \
fi; \
} > .env
$(LOCAL_ENV)

upd: docker-env
$(DOCKER_COMPOSE) up -d --build --force-recreate
Expand Down Expand Up @@ -158,18 +135,15 @@ test-docker: docker-env

.PHONY: prune
prune:
$(DOCKER_COMPOSE_PRUNE)
$(DOCKER_PRUNE)

# Project structure visualization
.PHONY: pycache-del tree plot-data
PYCACHE_DEL := scripts/makefile/pycache_del.sh
DISHKA_PLOT_DATA := scripts/dishka/plot_dependencies_data.py

pycache-del:
@$(PYCACHE_DEL)
$(PYCACHE_DEL)

tree: pycache-del
@tree
tree

plot-data:
@$(PYTHON_BIN) $(DISHKA_PLOT_DATA)
APP_LOGGING_LEVEL=CRITICAL python $(DISHKA_PLOT_DATA)
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Stay tuned. Refactor in progress, see [`legacy-2025`](https://github.com/ivan-borovets/fastapi-clean-example/tree/legacy-2025) branch for architecture docs

TODO:
- [x] Write tests
- [ ] Explain code and patterns in new README
- [ ] Make template project

Expand Down
9 changes: 2 additions & 7 deletions scripts/dishka/plot_dependencies_data.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import asyncio

import dishka.plotter
from dishka import AsyncContainer

from app.main.run import make_app


def generate_d2_dependency_graph(container: AsyncContainer) -> str:
async def main() -> None:
"""
Generates dependency graph for container in `d2` format.
See https://d2lang.com for rendering instructions.
"""
return dishka.plotter.render_d2(container)


async def main() -> None:
app = make_app()
container = app.state.dishka_container
try:
print(generate_d2_dependency_graph(container))
print(dishka.plotter.render_d2(container))
finally:
await container.close()

Expand Down
15 changes: 15 additions & 0 deletions scripts/makefile/docker_env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash
# The script is called from Makefile
set -eu -o pipefail

{
echo "# This .env file is generated automatically for DOCKER environment by Makefile."
echo "# Do not edit it directly; edit env.example / .secrets and Makefile instead."
echo
cat env.example
if [ -f .secrets ]; then
echo
echo "# --- secrets from .secrets (not committed) ---"
cat .secrets
fi
} > .env
2 changes: 2 additions & 0 deletions scripts/makefile/docker_prune.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash
# The script is called from Makefile
set -eu -o pipefail

echo "Warning: This will remove all unused containers, networks, images, and volumes."
echo "Are you sure you want to continue? [y/N]"
read -r response
Expand Down
18 changes: 18 additions & 0 deletions scripts/makefile/local_env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
# The script is called from Makefile
set -eu -o pipefail

{
echo "# This .env file is generated automatically for LOCAL environment by Makefile."
echo "# Do not edit it directly; edit env.example / .secrets and Makefile instead."
echo
sed \
-e 's|^EXAMPLE_SERVICE_URL=.*|EXAMPLE_SERVICE_URL=http://127.0.0.1:51999|' \
-e 's|^POSTGRES_HOST=.*|POSTGRES_HOST=127.0.0.1|' \
env.example
if [ -f .secrets ]; then
echo
echo "# --- secrets from .secrets (not committed) ---"
cat .secrets
fi
} > .env
8 changes: 8 additions & 0 deletions scripts/makefile/pip_audit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash
# The script is called from Makefile
set -eu -o pipefail

tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
uv -qq export --format pylock.toml -o "$tmp/pylock.toml"
pip-audit --locked "$tmp" \
|| echo "WARNING: pip-audit found vulnerabilities (non-blocking)" >&2
7 changes: 5 additions & 2 deletions scripts/makefile/pycache_del.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#!/bin/bash
find . -type d -name '__pycache__' -prune -exec rm -rf {} +; \
find . -type f \( -name '*.pyc' -o -name '*.pyo' \) -delete
# The script is called from Makefile
set -eu -o pipefail

find . -type d -name '__pycache__' -prune -exec rm -rf {} +
find . -type f \( -name '*.pyc' -o -name '*.pyo' \) -delete
7 changes: 7 additions & 0 deletions scripts/makefile/slotscheck.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash
# The script is called from Makefile
set -eu -o pipefail

slotscheck "$1" 2>&1 | tee /dev/stderr \
| { grep -m1 "Failed to import" || true; } | cut -d"'" -f2 \
| xargs -r -n1 python -c 'import importlib,sys; importlib.import_module(sys.argv[1])'
2 changes: 1 addition & 1 deletion src/app/infrastructure/persistence_sqla/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
fileConfig(config.config_file_name, disable_existing_loggers=False)

# add your model's MetaData object here
# for 'autogenerate' support
Expand Down