Skip to content

how to contribute patterns and conventions

github-actions[bot] edited this page Jun 19, 2026 · 1 revision

Patterns and conventions

Coding patterns the 360 Ghar backend enforces and how to follow them. Most are encoded in CLAUDE.md and AGENTS.md at the repo root and enforced by ruff in CI.

Async-first

All database operations and services use async/await. Services inject an AsyncSession via FastAPI dependencies and never block on I/O. The service-class pattern is class XService: def __init__(self, db: AsyncSession), with async methods returning ORM models or 3-tuples. Sync helpers (UTC, timezone) live in app/core/utils.py.

Service layer

Endpoints in app/api/api_v1/endpoints/ are thin controllers: validate input, enforce auth, delegate to app/services/. Business rules belong in services. REST, MCP, and the AI agent all call the same service functions instead of re-implementing them. Complex queries factor out into app/repositories/ (BaseRepository, PropertyRepository, PropertyQueryBuilder).

Shared MCP tool logic

MCP tool business logic lives in app/mcp/tool_ops/. Both MCP servers (app/mcp/user_server.py, app/mcp/admin/server.py) and the AI agent (app/services/ai_agent/tool_bridge.py) call into tool_ops. Never duplicate tool behavior in the server or the bridge. When adding a new MCP tool, implement the logic in tool_ops/ first, then wire it through both surfaces. Multi-client wrappers in app/mcp/chatgpt/ format responses and bind widgets but still call tool_ops for the actual work.

Shared httpx clients

Four httpx.AsyncClient singletons live in app/core/http.py:

Client Timeout Used by
get_scraper_client() 30s Data hub scrapers, jamabandi, gazette
get_blog_client() 120s Perplexity blog, SerpAPI image search
get_general_client() 30s Image downloads, geocoding, image gen
get_supabase_auth_http_client() 10s Supabase Auth JWKS / metadata

Never create an ephemeral async with httpx.AsyncClient() per request. Use the shared clients and pass a per-request timeout= override when needed.

Single scheduler and serverless mode

One AsyncIOScheduler from app/infrastructure/scheduler.py is registered in app/infrastructure/lifespan.py. Four job families attach to it: blog auto-publish, notifications, vector sync, and data hub scraping. Do not create per-module scheduler instances; add jobs via get_scheduler(). When SERVERLESS_ENABLED=True, both DB engines switch to NullPool (no persistent connections), schedulers are skipped, and the cache falls back to in-memory so the app can scale to zero behind PgBouncer. The trade-off is roughly 10-50ms added latency per request; move cron work to Railway cron jobs in that mode.

SSE event bus

SSEEventBus in app/core/sse.py provides per-user pub/sub. Services call await sse_bus.emit(user_id, event_dict) after a DB commit, never before. The endpoint GET /api/v1/flatmates/sse consumes from the queue with a 30-second keepalive; the bus drops the oldest event on a full queue. Event types are listed in the glossary; adding a new type requires updating CLAUDE.md and AGENTS.md. Streaming endpoints release the main-pool session before streaming and use the background pool (get_bg_db) for tool calls.

3-tuple cursor pagination

As of June 2026, paginated list endpoints return (items, next_cursor, has_more) tuples instead of bare lists, with keyset cursors on created_at replacing offset pagination. This applies across properties, users, agents, bookings, visits, and blog. New list endpoints should return the 3-tuple and accept a cursor query parameter.

Overlapping bookings

The same property can be booked by multiple people for the same or overlapping dates. Do not add date-overlap conflict checks, double-booking guards, or DB exclusion constraints. check_availability in app/services/booking.py only validates that the property exists and the guest count fits max_occupancy.

Auth

Clients authenticate directly with Supabase Auth and send a bearer access token. get_current_user (app/api/api_v1/dependencies/auth.py) verifies the JWT and syncs the local user row. Phone is the primary identifier. There are no /api/v1/auth/* session endpoints; clients own login, refresh, and logout via the Supabase SDK. When Supabase is unreachable, the dep returns PROVIDER_UNREACHABLE and the endpoint responds HTTP 503 with Retry-After: 5.

Ruff lint rules

Ruff runs in CI (uv run ruff check app/) and fails on any violation. Target is Python 3.10, line length 100. Key rules:

  • I001, UP035, F401, E402from __future__ import annotations as the first import in every .py file. Use list/dict/set/tuple/type not typing.*. Import Callable, Awaitable, AsyncIterator, Sequence from collections.abc. Remove unused imports. All imports at file top; # noqa: E402 only for unavoidable circular imports with a comment.
  • UP045, UP006, UP007, UP037X | None not Optional[X], X | Y not Union[X, Y], list[X] not List[X]. Remove unnecessary quotes in annotations. For forward references, add from __future__ import annotations and import the type under TYPE_CHECKING.
  • B904 — Always chain exceptions in except. from e when the original aids debugging; from None when logging the original and raising a user-facing exception.
  • E712 — Never == True/== False. Use the column directly (Model.is_active) or bitwise negation (~Model.is_active, not_(...)).
  • B905, C401, F541, F811, E741, F841, W291/W292/W293zip(a, b, strict=True); set comprehensions not set(gen); no placeholder-free f-strings; no redefining imported names; no single-letter l; no unused assignments (use _); no trailing whitespace, clean blank lines, files end with a newline.

Custom exceptions live in app/core/exceptions.py; always chain per B904.

Data safety

Never delete real user data without explicit confirmation. seed_data/02_clear_data.py filters by WHERE is_seed_data = true on users, agents, and properties, and deletes child records via subquery joins to seed parents. Never run it against production. New seeded models need either an is_seed_data column (server_default=text("false")) or a FK cascade to an existing seeded parent.

Docs drift

Update docs/ and docs/repo-contract.json when adding any public surface: endpoint, service module, MCP tool, widget, scheduler, shared httpx client domain, flatmates/social feature, notification type, AI provider, SSE event type, infrastructure module, seed data, storage bucket, or is_seed_data column.

See also

Clone this wiki locally