Install
$ agentstack add skill-mitodl-agent-kit-drf-api-performance ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Performant DRF APIs
A fast API is two problems: the shape of the response, and the cost of the queries that fill it. These rules apply to every DRF viewset, serializer, and queryset.
The rules
- Keep response nesting to two levels or less; split deeper data into a second endpoint.
- Paginate every list endpoint. Set the default once in a shared module, cap
client-supplied page size with max_limit, and order deterministically.
- Narrow the pagination count query to the primary key. It is a second query
over your whole result set, and it scales with production data, not fixtures.
- Pick the narrowest prefetch tool that works:
select_related()for foreign
keys, prefetch_related() for to-many relationships, prefetch() for data the model has no direct relationship to.
- Use
select_related(), notprefetch_related(), when the queryset **already
filters or orders on that table** - the join is happening either way.
- Three or four to-one joins are fine; treat eight as the review threshold
where you check EXPLAIN and split the query instead of widening it further.
- **Never query inside a serializer or call a function directly or indirectly
that makes one** - the body runs once per object, so a query there is multiplied by the page size. The view's queryset assembles the data.
- Declare
required_prefetcheson every serializer. It fails loudly under
DEBUG and pytest; in production it only logs, so treat it as a development guardrail and not a reason to skip the prefetch.
- Back a prefetch with a same-named
cached_propertyso non-API callers get
the same answer without a second implementation.
- Test list APIs with 5-10 records at each level, or the N+1 checks won't fire.
- Pin a constant query count across varying data with
django_assert_num_queries, and never add skip_nplusone_check to a new test.
References
| Read this | For | | --------- | --- | | [response-shape.md](references/response-shape.md) | The two-level nesting rule, worked normalization example, the extra-round-trip trade-off | | [pagination.md](references/pagination.md) | DefaultPagination in a shared module, DEFAULT_PAGINATION_CLASS, the three legitimate per-view overrides, class comparison, why the count query gets expensive, .only() vs .values() (and when .only() raises), widening count_fields | | [prefetching.md](references/prefetching.md) | Tool comparison, the already-joined exception, writing a prefetch() prefetcher and its footguns, composite keys, the cached_property shadowing pattern and the hasattr antipattern | | [joins-and-query-plans.md](references/joins-and-query-plans.md) | Width vs multiplication, when table size enters the plan, Postgres planner thresholds, reading EXPLAIN (ANALYZE, BUFFERS), getting the SQL out of Django | | [serializers.md](references/serializers.md) | The SerializerMethodField N+1, the full "move it to the queryset" table, BaseSerializer and required_prefetches, and why it only raises outside production | | [testing-and-lint.md](references/testing-and-lint.md) | django-zeal setup and scoped exemptions, django_assert_num_queries vs django_assert_max_num_queries, drf-lint's ORM001/ORM002 and its baseline |
Resources
- Write Performant APIs - the handbook page this skill is drawn from
- 2026-03-24 MIT Learn outage post-mortem - what an expensive count query costs in production
- django-prefetch, django-zeal, mitol-drf-lint
- DRF: Pagination, Django:
prefetch_related(), Django:Prefetchobjects, Django:cached_property
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: mitodl
- Source: mitodl/agent-kit
- License: BSD-3-Clause
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.