@@ -39,14 +39,15 @@ Both models share the same configuration system, logging baseline, metrics, heal
3939- Pre-commit (mypy, ruff, YAML/TOML/JSON checks)
4040- GitHub Actions: lint + test + docker build + smoke test
4141- Dockerfile with build cache support
42+ - Image name for GHCR is derived automatically from the repository name and lowercased in the CI workflow.
4243
4344---
4445
4546# 2. When to use Sync vs. Async?
4647
4748### Use ** sync** when:
4849- You have a simple worker loop ("do something every X seconds").
49- - You use blocking libraries (e.g. ` requests ` , psycopg2) and don’t need concurrency.
50+ - You use blocking libraries (e.g. ` requests ` , psycopg2) and don’t need high concurrency.
5051- You want minimal complexity.
5152
5253### Use ** async** when:
@@ -60,8 +61,7 @@ The boilerplate lets you choose the right model per project without changing the
6061
6162# 3. Directory Structure
6263
63- ```
64-
64+ ``` text
6565src/app
6666│
6767├── config.py # unified configuration loading
@@ -74,26 +74,25 @@ src/app
7474│
7575├── main_sync.py # entry point for sync workers
7676└── main_async.py # entry point for async workers
77-
78- ```
77+ ````
7978
8079---
8180
8281# 4. Running a Synchronous Service
8382
8483Sync entry point is installed as:
8584
86- ```
87-
85+ ```bash
8886poetry run app-sync
87+ ```
8988
90- ````
91-
92- or via Docker:
89+ or via Docker Compose:
9390
9491``` yaml
95- command: ["poetry", "run", "app-sync"]
96- ````
92+ services :
93+ app :
94+ command : ["poetry", "run", "app-sync"]
95+ ` ` `
9796
9897The sync service uses a classic blocking loop:
9998
@@ -110,14 +109,16 @@ service.start()
110109
111110Async entry point is:
112111
113- ```
112+ ``` bash
114113poetry run app-async
115114```
116115
117- or via Docker:
116+ or via Docker Compose :
118117
119118``` yaml
120- command : ["poetry", "run", "app-async"]
119+ services :
120+ app :
121+ command : ["poetry", "run", "app-async"]
121122` ` `
122123
123124The async service runs on asyncio:
@@ -129,7 +130,7 @@ service = AsyncService(config)
129130await service.start()
130131```
131132
132- ### Perfect for :
133+ ### Typical use cases :
133134
134135* Discord bots
135136* Async HTTP workers
@@ -142,7 +143,7 @@ await service.start()
142143
143144Every container can be validated via:
144145
145- ```
146+ ``` bash
146147poetry run app-health
147148```
148149
@@ -153,22 +154,35 @@ Exit codes:
153154
154155This is the recommended Docker healthcheck command.
155156
157+ Example Docker Compose:
158+
159+ ``` yaml
160+ healthcheck :
161+ test : ["CMD", "poetry", "run", "app-health"]
162+ interval : 30s
163+ timeout : 5s
164+ retries : 3
165+ start_period : 10s
166+ ` ` `
167+
156168---
157169
158170# 7. Metrics
159171
160172If enabled (` metrics_enabled=true`), the service exposes:
161173
162- ```
174+ ` ` ` text
163175http://localhost:<metrics_port>/metrics
164176` ` `
165177
166- Baseline metrics give you:
178+ Baseline metrics provide :
179+
180+ * service liveness (`app_up`)
181+ * total loop iterations (`app_iterations_total`)
182+ * timestamp of last successful iteration (`app_last_iteration_timestamp_seconds`)
183+ * build information (`app_build_info{version,commit,env}`)
167184
168- * uptime
169- * iteration frequency
170- * last-loop freshness
171- * deployment info (version, commit, env)
185+ These can be scraped by Prometheus and visualized in Grafana.
172186
173187---
174188
@@ -178,79 +192,230 @@ All logs follow the unified JSON format:
178192
179193` ` ` json
180194{
181- "ts" : " ... " ,
195+ "ts": "2025-11-22T20:01:23.123456Z ",
182196 "level": "info",
183197 "service": "python-service",
184- "logger" : " app.main " ,
198+ "logger": "app.main_sync ",
185199 "instance": "myhost",
186200 "env": "dev",
187201 "msg": "Service startup succeeded",
188202 "event": "startup_success"
189203}
190204` ` `
191205
192- Errors include stack traces automatically.
206+ * All logs are single-line JSON on stdout.
207+ * Errors automatically include stack traces (`stack`) and `exception_type`.
208+ * Lifecycle events are logged explicitly: `startup_success`, `shutting_down`, `crashed`.
209+
210+ This makes ingestion into Loki, Elastic, or any JSON-aware log pipeline straightforward.
193211
194212---
195213
196214# 9. Configuration
197215
198- A ` .env ` file can override or supplement configuration:
216+ Configuration can come from :
217+
218+ 1. Environment variables
219+ 2. YAML file (`config.yml`)
220+ 3. Defaults
221+
222+ # ## Example `.env` (see `.env.example`)
199223
200224` ` ` env
225+ # Core service configuration
201226APP_SERVICE_NAME=python-service
202227APP_ENV=dev
203228APP_LOG_LEVEL=INFO
229+
230+ # Build / version metadata (typically injected during CI build)
231+ APP_VERSION=0.1.0
232+ APP_COMMIT=local-dev
233+
234+ # Optional: override instance ID
235+ APP_INSTANCE=local-dev
236+
237+ # Optional: YAML config path (relative to working directory)
238+ APP_CONFIG_PATH=config.yml
239+
240+ # Metrics configuration
204241APP_METRICS_ENABLED=1
205242APP_METRICS_PORT=8000
243+
244+ # Main loop configuration
206245APP_LOOP_SLEEP_SECONDS=5.0
207246` ` `
208247
209- Optionally load YAML config:
248+ # ## Example `config.yml` (see ` config.yml.example`)
210249
211250` ` ` yaml
212251service_name: python-service
252+ env: dev
253+ log_level: INFO
254+
213255metrics_enabled: true
214256metrics_port: 8000
257+
215258loop_sleep_seconds: 5.0
259+
260+ version: 0.1.0
261+ commit: local-dev
216262` ` `
217263
264+ Environment variables override YAML values; YAML overrides hardcoded defaults.
265+
218266---
219267
220268# 10. Tests
221269
222- Run the full suite:
270+ Run the full test suite :
223271
224- ` ` `
272+ ` ` ` bash
225273poetry run pytest
226274` ` `
227275
228- Coverage is preconfigured.
276+ Coverage is configured via `coverage.ini` / `[tool.coverage.*]` in `pyproject.toml`.
277+ Pre-commit hooks ensure :
278+
279+ * type checking (mypy)
280+ * linting/formatting (ruff)
281+ * basic file hygiene (YAML/TOML/JSON + whitespace checks)
282+ * an optional GUI-based repo state check before committing.
229283
230284---
231285
232286# 11. Docker
233287
234- Build and run:
288+ Build and run locally :
235289
236- ```
290+ ` ` ` bash
237291docker build -t python-service .
238292docker run --rm -p 8000:8000 python-service
239293` ` `
240294
241- CI automatically :
295+ The GitHub Actions workflow :
242296
297+ * installs dependencies
243298* runs tests
244- * builds/pushes images
245- * performs smoke tests
299+ * builds the Docker image
300+ * pushes to GHCR
301+ * performs a smoke test via `/metrics`
302+
303+ The image name is derived from the repository owner and name in CI, lowercased :
304+
305+ * `ghcr.io/<owner>/<repo>` → e.g. `ghcr.io/hartmannlight/python-boilerplate`
306+
307+ You can adjust the image name logic in `.github/workflows/build.yml` if needed.
246308
247309---
248310
249311# 12. Summary
250312
251313This boilerplate lets you start any kind of Python backend quickly :
252314
253- * ** Sync workers** for polling, simple loops, data processing
315+ * **Sync workers** for polling, simple loops, and data processing
254316* **Async services** for event-driven, high-concurrency environments
255317
256318Everything else (logging, health, metrics, config, CI, Docker) is unified and ready to use.
319+
320+ ---
321+
322+ # 13. Checklist: What you should change when using this boilerplate
323+
324+ When you create a new project from this boilerplate, you should review and adapt at least the following items.
325+
326+ # ## 13.1 Project metadata
327+
328+ * In `pyproject.toml`:
329+
330+ * `[tool.poetry].name` → your project/package name
331+ * `[tool.poetry].description` → short description of your service
332+ * `[tool.poetry].authors` → your name/email
333+ * `[tool.poetry].version` → initial version (e.g. `0.1.0` for your project)
334+
335+ # ## 13.2 README and docs
336+
337+ * Update the title and description in `README.md` to match your service.
338+ * If you keep `docs/observability-baseline.md`, you can:
339+
340+ * either reference it from the README
341+ * or adapt it to your organization’s standards.
342+
343+ # ## 13.3 Service entry point
344+
345+ Decide which runtime model you actually need :
346+
347+ * Sync:
348+
349+ * Use `app-sync` as the main entry point (`poetry run app-sync`).
350+ * In Docker/Docker Compose, use `["poetry", "run", "app-sync"]`.
351+ * Async:
352+
353+ * Use `app-async` (`poetry run app-async`).
354+ * In Docker/Docker Compose, use `["poetry", "run", "app-async"]`.
355+
356+ Optional cleanups :
357+
358+ * If your project will **never** use async, you can remove `service_async.py` and `main_async.py` and the `app-async` script.
359+ * If your project will **only** be async, you can remove `service_sync.py` and `main_sync.py` and the `app-sync` script.
360+
361+ # ## 13.4 Configuration defaults
362+
363+ Check and adapt :
364+
365+ * `.env.example`
366+
367+ * `APP_SERVICE_NAME` → canonical name of your service
368+ * `APP_ENV` → default environment (`dev`, `stg`, `prod`, etc.)
369+ * `APP_METRICS_PORT` → port that fits your stack
370+ * `APP_LOOP_SLEEP_SECONDS` → reasonable default for your loop
371+ * `config.yml.example`
372+
373+ * `service_name`, `env`, `log_level` as appropriate
374+ * `metrics_enabled`, `metrics_port`
375+ * `loop_sleep_seconds`
376+ * `version` / `commit` (cosmetic; usually overwritten by CI)
377+
378+ # ## 13.5 Docker & CI
379+
380+ * `.github/workflows/build.yml`:
381+
382+ * The image name is derived automatically from GitHub repository owner and name, lowercased.
383+ * If you want to push to a different registry or org (e.g. Docker Hub), adjust the `Prepare image name` step.
384+
385+ * `docker-compose.yml` / `docker-compose.override.yml`:
386+
387+ * Ensure `image:` and `command:` match your desired entry point and registry.
388+ * Update `container_name:` if you care about the container name.
389+
390+ # ## 13.6 License
391+
392+ * The template assumes `Apache-2.0` (see workflow labels and usually a `LICENSE` file).
393+ * If you use a different license:
394+
395+ * Update or replace `LICENSE`.
396+ * Adjust the `org.opencontainers.image.licenses` label in `.github/workflows/build.yml` if needed.
397+
398+ # ## 13.7 Repo-state check script (optional)
399+
400+ * `scripts/repo_state_check_gui.py`:
401+
402+ * This is optional tooling to show a small GUI before committing.
403+ * You can:
404+
405+ * Adapt the checks (e.g. enforce email domain, branch naming),
406+ * Or remove this hook from `.pre-commit-config.yaml` if you don’t want interactive confirmation dialogs.
407+
408+ # ## 13.8 Observability integration
409+
410+ * Ensure your logging/metrics/health expectations match:
411+
412+ * If you add HTTP endpoints, consider:
413+
414+ * `/healthz` and `/readyz`
415+ * HTTP metrics (`app_requests_total`, etc.)
416+ * If you use Prometheus/Grafana:
417+
418+ * Wire the metrics endpoint into your scrape config.
419+ * Optionally add dashboards around `app_up`, `app_iterations_total`, and `app_build_info`.
420+
421+ Once you have gone through this checklist and adapted these points, the boilerplate should behave as a first-class, project-specific service template in your environment.
0 commit comments