readme, claude mds update info
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user