readme, claude mds update info

This commit is contained in:
2026-09-15 23:20:30 +03:00
parent 7505f26c98
commit 962f250a8d
2 changed files with 89 additions and 8 deletions
+26 -5
View File
@@ -89,11 +89,17 @@ workers. Python >=3.13,<4.0, Poetry for dependency management.
`src/service/email/templates/`), inline CSS (email clients don't support `src/service/email/templates/`), inline CSS (email clients don't support
`<style>` reliably), `EmailMessage` with `set_content()` (plain-text `<style>` reliably), `EmailMessage` with `set_content()` (plain-text
fallback) + `add_alternative(html, subtype="html")`. fallback) + `add_alternative(html, subtype="html")`.
- **Known gap**: the reset-password flow is a stub. `ResetEmailConsumer.process_message` - Reset-password flow mirrors welcome: `ResetEmailSender`
only prints and acks — it never calls a sender — and (`src/service/email/email_reset.py`) renders `templates/reset.html`
`src/service/email/email_reset.py` is empty. `templates/reset.html` still (`{{ temp_password }}`, no longer hardcoded) the same way
has a hardcoded placeholder password. Don't assume reset emails actually `DaemonEmailSender` does `welcome.html`. `ResetEmailConsumer.process_message`
send until this is wired up like `WelcomeEmailConsumer`/`DaemonEmailSender`. reads both `email` and `temp_password` from the message body and uses the
same transient/permanent classification as `WelcomeEmailConsumer`.
`EmailProducer.send_reset_email(email, temp_password)` takes the password
as a second argument now. **Still missing**: nothing in the app actually
calls `send_reset_email` yet — there's no password-reset route that
generates a `temp_password` and publishes it. Don't assume the
reset-password feature is reachable end-to-end until that route exists.
## Logging (`src/logging/`) ## Logging (`src/logging/`)
@@ -204,3 +210,18 @@ workers. Python >=3.13,<4.0, Poetry for dependency management.
which broke refresh-token-cookie-dependent tests like logout). which broke refresh-token-cookie-dependent tests like logout).
- `test_user_fixture` is `indirect=True` parametrized with - `test_user_fixture` is `indirect=True` parametrized with
`(direct_permissions, group)` tuples. `(direct_permissions, group)` tuples.
- `tests/unit/test_consumers.py` covers `RabbitMQClient`/`WelcomeEmailConsumer`/
`ResetEmailConsumer` entirely with mocks — no real broker involved.
Pattern: `monkeypatch.setattr(rabbitmq_client_module.aio_pika, "connect_robust", ...)`
patches the module attribute that `connect()` looks up at call time (not
the `rabbitmq_client` singleton's method — that's a bound method, it has
no attribute of its own to patch). `aio_pika.connect_robust`/`asyncio.sleep`
must both be mocked when testing the retry loop, or the test really
sleeps `2**attempt` seconds between attempts. `message.process(...)` is an
async context manager, not a plain awaitable — mocking it needs a
`MagicMock` with `__aenter__`/`__aexit__` set to `AsyncMock`s (see
`make_fake_message()` in that file), not just `AsyncMock()`. When
asserting on what a mocked async method returned, compare against
`mock.return_value` (or a variable captured before assigning it), never
against the mock itself — `some_mock is some_mock.return_value` is never
true, they're two different objects.
+63 -3
View File
@@ -10,24 +10,50 @@ A FastAPI project combining Excel and digital data ("The Great Excel project tha
- PostgreSQL (asyncpg, psycopg2) - PostgreSQL (asyncpg, psycopg2)
- Pydantic 2 / Pydantic Settings - Pydantic 2 / Pydantic Settings
- Poetry — dependency management - Poetry — dependency management
- Redis — caching, rate limiting, token revocation
- RabbitMQ (aio-pika) — background email workers
- Jinja2 — HTML email templates
- Docker, Ansible — deployment - Docker, Ansible — deployment
## Architecture ## Architecture
``` ```
src/ src/
├── cache/ # Redis client, rate limiting
├── daemons/ # background worker entrypoints (BaseDaemon, registry)
├── database/ # DB CRUD operations ├── database/ # DB CRUD operations
├── errors/ # HTTP errors ├── errors/ # HTTP errors
├── logging/ # logging middleware ├── logging/ # queue-based logging infra + HTTP middleware
├── messaging/ # RabbitMQ client, producers, consumers, topology
├── migrations/ # Alembic migrations ├── migrations/ # Alembic migrations
├── models/ # Pydantic and SQLAlchemy models, configs ├── models/ # Pydantic and SQLAlchemy models, configs, RabbitMQ topology
├── reports/ # reports ├── reports/ # reports
├── service/ # business logic (auth, users_crud) ├── service/ # business logic (auth, users_crud, email sending)
└── web/ # routes (protected_routes) └── web/ # routes (protected_routes)
``` ```
Layers are connected top to bottom: `web → service → database → models`. Layers are connected top to bottom: `web → service → database → models`.
## Background workers (RabbitMQ)
Email sending (welcome / password-reset) runs as separate daemon processes,
decoupled from the web API via a RabbitMQ topic exchange:
```
main.py / daemon_run.py → apply_topology() → RabbitMQ ("email" exchange)
├── queue_welcome_email → WelcomeEmailConsumer
└── queue_reset_email → ResetEmailConsumer
```
Each queue has a matching dead-letter queue for messages that fail
permanently (bad data, non-retryable errors) instead of retrying forever.
Run a worker locally with:
```bash
python daemon_run.py welcome_email # single daemon
python daemon_run.py --all # all daemons enabled in configs/daemons.json
```
## Authentication ## Authentication
- JWT access + refresh tokens - JWT access + refresh tokens
@@ -46,8 +72,42 @@ tests/
Uses pytest, pytest-asyncio, pytest-cov, pytest-mock, allure-pytest. Uses pytest, pytest-asyncio, pytest-cov, pytest-mock, allure-pytest.
### Allure report
`make allure` needs the Allure **command-line tool** (Java-based, not a pip
package) — `allure-pytest` only writes raw result files, the CLI turns them
into an HTML report.
1. Download Allure **2.44.0** from
[github.com/allure-framework/allure2/releases](https://github.com/allure-framework/allure2/releases).
2. Extract it into `.venv/allure-2.44.0/` so that `.venv/allure-2.44.0/bin/allure`
exists — this matches the `ALLURE` variable already set in `makefile`.
3. If you install it somewhere else (or on Windows), update the `ALLURE`
variable at the top of `makefile` to point to your actual `allure`
binary path — the Windows path is already there, commented out.
```bash
make test # runs pytest, writes results to tests/allure-results/reports
make allure # builds tests/allure-results/html/index.html from those results
```
## Installation ## Installation
Requires Poetry itself to be installed first (it's a global tool, not a
project dependency). Recommended via [pipx](https://pipx.pypa.io/):
```bash
pipx install poetry
```
or via the official installer:
```bash
curl -sSL https://install.python-poetry.org | python3 -
```
Then install the project dependencies:
```bash ```bash
poetry install poetry install
``` ```