python
Technical notes on web development, DevOps, and AI integration.
51 articles
- 23:55security
A Rate Limit Alone Was Not Enough for This Login Endpoint
Sliding window dual keying plus dummy-hash timing equalization: two separate defenses for one login endpoint.
TL;DR: A live smoke test confirmed the login rate limiter kicks in with HTTP 429 and a Retry-After header on the eleventh attempt per minute. The real fix pairs that throttling with timing equalization, running bcrypt against a dummy hash so response speed never reveals whether an account exists. Docs endpoints are now dev-only, and duplicate registrations return 409.
#security#fastapi#python - 23:15backend
One ruff Commit Broke Three Layers at Once
A harmless-looking ruff sweep touched runtime aliases, TYPE_CHECKING boundaries, isort classification, and the Docker build layout all at once.
TL;DR: A harmless-looking lint commit shipped runtime import moves, type-checking guards, and a new pyproject.toml in one package. Only the last piece broke CI, because setuptools' flat-layout discovery choked on the leftover build directory the Docker install created. The fix: exclude build artifacts in ruff config and clean them within the same RUN instruction.
#python#ruff#isort - 22:43backend
When EmailStr Rejects .test Fixtures Under Pydantic
email-validator rejects reserved test domains at the syntax layer; here is how to pick fixture domains that pass Pydantic validation.
TL;DR: CI failures on register and login endpoints came from Pydantic's EmailStr rejecting reserved domains like .test and .local at the syntax layer, before any DNS lookup. The simplest fix is swapping fixtures to example.com, example.net, or example.org, which pass validation. Alternatively, pass test_environment=True to email-validator, though Pydantic needs a custom wrapper for that.
#python#pydantic#email-validator - 17:43backend
A FastAPI Skeleton of Layer Contracts, Not Empty Folders
Four folders with empty __init__.py files carry real contracts: layer boundaries, a unit of work per request, JWT tenant claims, and the 72-byte bcrypt gate.
TL;DR: Four seemingly empty folders establish layered contracts in a FastAPI skeleton, keeping business logic, technical details, and HTTP translation cleanly separated. A yield-based dependency handles transactions per request, committing only on success so multi-table use cases stay atomic. Multi-tenancy rides in JWT claims, bcrypt limits get enforced at the validation gate, and swappable signal sources await future needs.
#fastapi#python#jwt - 18:07tooling
The Morning My Research Tool Swallowed Its Own Medicine
One pruned line in the morning build settled it: an unpruned vector index answers with documents that no longer exist, and free-form data is a contract.
TL;DR: Renaming a research file left a stale embedding in a JSON vector index that kept returning a ghost document. The fix added tolerant reading for summary field aliases, automatic pruning of missing files during builds, and a self-testing suite. It is a sixty-line reminder that small local tools need honest indexes and defensive boundaries.
#python#embedding#backward-compatibility - 02:35devops
Checking the gate with one command
What used to take four MCP round trips and an 86KB payload at session start is now one read-only script with semantic exit codes.
TL;DR: Checking status used to need four heavy MCP calls, and caching only created stale-data headaches. The fix was a lightweight read-only script that runs four checks in parallel with clear exit codes for ready, drift, or infra trouble. It handles quirks like Cloudflare blocks and rate limits while staying local for quick iteration and always-fresh data.
#python#plane#devops - 02:23testing
Parity tests without a shared validator
Two validation gates stay separate, but a parametrized test table binds both to the same accept/reject contract and the same error precedence.
TL;DR: Two standalone modules rejected bad configs differently, and sharing validation code at runtime wasn’t allowed due to isolation rules. Instead of merging code, a parametrized pytest table now feeds hostile inputs to both, ensuring they agree on accept versus reject. It also enforces resolve-first ordering where path traversal errors beat file shape errors, keeping docs updated and modules isolated.
#python#testing#plane - 23:44tooling
Page identity without a hidden marker
A v3.1 design moves page identity from a hidden HTML marker to a configured page_id, adds retrieve-after-write hash comparison, and keeps unknown forms fail-loud.
TL;DR: v3 kept page identity in an HTML comment in the body, so a single rewrite without it made verification block the page. Commit 67f50e0 specs v3-1 moving identity to config with strict naming and verifying writes via read-back hash comparison that fails loudly. It is design only, does not identify the stripping cause, and keeps tolerance tight to prevent drift.
#plane#python#markdown - 21:24tooling
Version-aware preflight without breaking v2
Preflight v3 without breaking the frozen v2 validator.
TL;DR: V2 preflight blocked v3 configs, so a new pagesync preflight now validates v3 decisions and Pages while leaving the frozen v2 engine alone. Duplicate validators stay aligned via a shared adversarial test table that requires matching accept/reject outcomes and surfaces traversal errors first. Page sync verifies what Plane stored via retrieve-after-write name and hash checks instead of trusting hidden markers.
#plane#python#testing