encode

Stars and mentions

Last 30 days, Oct 4 – Nov 2

GitHub stars0
mentions5
01234Oct 4Oct 18Nov 2
UTC days · repositories encode owns0 repositories

Package downloads

No package download history available yet.

Top repositories

encode’s repositories ranked by new forks, as on trending.

Loading

Contributes to

Repositories outside encode’s account where encode opened or reviewed pull requests in the past 365 days.

Who noticed encode

Everyone who starred or forked encode’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

5posts
Nov 30 posts
  1. The New HTTP QUERY Method, and Using It in Django This is my first post on here, so bear with me. It's about the new HTTP QUERY method, and a small package I've put together so anyone can use it with Django REST framework now, rather than waiting for more widespread adoption. But mostly it's about the problem that made me care about it in the first place. The problem: GET URLs that kept growing I look after a Django project for a client where most of the app is big filterable, sortable tables. They're server-side DataTables, backed by django-rest-framework-datatables, with django-filter doing a lot of the filtering on the backend. It's a setup I really like - it's simple to set up and modify over time, and all the searching, sorting and paging happens in the database. The issue is how DataTables asks. In server-side mode, by default, it sends everything as a GET query string. Not just the search box and the page, but six parameters for every single column (columns[3][data], columns[3][searchable], columns[3][search][value] and so on), plus the ordering, plus whatever extra filters you bolt on. Our main table has 21 columns, and on top of that the filter panel adds around 40 more parameters - booleans, ranges, multi-selects, tags, and so on. When I rebuilt a request roughly from today's table, a relatively simple URL came out at about 4.8KB. Pick a handful of options in one of the multi-selects and it's well past 6KB. Gunicorn's default limit on the request line is 4094 bytes. So, unsurprisingly, years ago, it fell over, and left me scrambling to try and work out why, and how to fix it. My fix back in November 2022 was a commit called, in full, "Attempt to fix gunicorn error.", which added --limit-request-line 0 to the gunicorn start script. Zero means no limit at all. That worked, but it never sat right with me. I have always felt there must be a better approach. Why not just use POST? The obvious answer is to switch the tables to POST and put everything in the body. But it's a read pretending to be a write. POST says "this changes something", so it isn't cacheable, it isn't safe to retry, it needs a CSRF token for what is just a search, and any permission class that checks for safe methods treats it as a write. With DRF in particular, IsAuthenticatedOrReadOnly and friends would see a list request as an attempt to create something. You end up working around the framework instead of with it. So it was either a URL nobody should have to look at, or a request that lies about what it does. Selfishly, I just wanted my tables to work properly, but I think a lot of people are in the same spot. What QUERY is RFC 10008 defines a new HTTP method, QUERY. It's the first new method since PATCH. The short version is that it's safe and idempotent like GET, but it carries a request body like POST. It fills the gap between GET and POST exactly, which makes it a perfect fit for dynamic filtering - search screens, filter panels and big tables like mine, where users can combine any number of filters. Filters go in the body, so the size limit is one you control rather than the tightest thing on the path, and the request is still honestly a read, so retries, permissions and (in principle) caching all behave the way they should. Where things are at Django has merged QUERY support for 6.2 (django/django#21855, by Michiel Beijen). "query" is now in View.http_method_names, so a class-based view just defines a query() method. It's treated as safe throughout, so it's exempt from CSRF, cache_page and the cache middleware don't cache it, and the test client gets RequestFactory.query(). That's the foundation, but I don't write plain Django views for my APIs. I use packages like DRF, and DRF (along with the packages around it) doesn't know about QUERY yet. When I talked through how I could help make this new method easily adoptable with my mentor, Tim Schilling (fantastic, by the way, if anyone is looking for coding-based mentoring, whether that's technical support or career support), his suggestion was to give people something they can use now, as well as contributing to the wider community directly. So here we are. drf-query-method Until the rest of the ecosystem catches up, I've put together drf-query-method, a small package that adds QUERY support to DRF today. pip install drf-query-method Call patch() once at startup, for example in an AppConfig.ready(), and give your viewset a query() handler: import drf_query_method drf_query_method.patch() class NoteViewSet(ModelViewSet): queryset = Note.objects.all() serializer_class = NoteSerializer filter_backends = [SearchFilter, OrderingFilter] search_fields = ["title"] ordering_fields = ["created", "title"] def query(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) Now QUERY /notes/ goes to query(), and the filters read their parameters from the body: QUERY /notes/ HTTP/1.1 Content-Type: application/json {"search": "django", "ordering": "-created,title"} If you'd rather not patch DRF, there are opt-in classes with the same names as DRF's (drf_query_method.routers, .viewsets, .filters, .permissions, .test), so it's an import change rather than a code change. It also works on older Django. Django 6.2 adds QUERY itself; on 4.2 to 6.1 the package adds it for you (add_query_method(), which patch() calls). A quick word on CSRF, because it's one of the nice things about QUERY: you don't need a token. It's a safe method, so Django 6.2 skips the CSRF check for it, just as it does for GET. Older versions of Django's CSRF middleware only know about GET, HEAD, OPTIONS and TRACE, so on 4.2 to 6.1 the package teaches it that QUERY is safe too. Either way, you don't send a CSRF token, even with session authentication. It's still protected against cross-site requests by CORS: browsers can only send QUERY from JavaScript, and a cross-origin QUERY needs your server's permission first. The rule is the same as for GET - a query() handler should never change anything. Closing the loop on DataTables This was the bit I actually wanted. If django-filter is installed, patch() makes DjangoFilterBackend read the body too, and as of 0.2.0 it does the same for django-rest-framework-datatables - its filter backends, pagination and renderer. So the table setup becomes: new DataTable("#notes", { serverSide: true, ajax: { url: "/api/notes/?format=datatables", type: "QUERY", }, columns: [{data: "title"}, {data: "created"}], }); jQuery sends data as a form-encoded body for any method other than GET and HEAD, which is the shape django-rest-framework-datatables already expects. format=datatables stays in the URL, and everything else moves into the body. And there's no CSRF token to send, because QUERY is a safe method. htmx supports it too For htmx lovers like myself, who are probably wondering whether this works with htmx - it does, and htmx got there first. htmx 4 (released on 28 August 2026) adds an hx-query attribute. It sends a QUERY request with the form values in the body, the same way hx-post does, rather than squeezing them into the URL: <form hx-query="/notes/search/" hx-target="#results"> <input name="search"> <select name="ordering">...</select> </form> htmx form-encodes the values, and filter_params merges a form-encoded body over the URL parameters, so SearchFilter, OrderingFilter and (with patch()) django-filter all pick them up with nothing extra. And as with DataTables, there's no CSRF header to add. The one thing to know is that htmx 4 sits under the next tag on npm, with 2.x still on latest, so you have to ask for version 4 explicitly. htmx 2 doesn't have hx-query. It's on PyPI (0.3.0 at the time of writing), and the docs are here. The plan: make this package redundant I'd honestly rather this package didn't need to exist for long. The aim is for the main packages to support QUERY themselves, at which point you can drop drf-query-method from your dependencies and rely on them instead. To help with that, it deliberately uses the same names as the upstream work - query() handlers and request.filter_params - so removing it should mean deleting a line or two rather than rewriting your views. DRF. Back in August I opened a discussion on DRF about supporting QUERY. DRF describes itself as essentially feature-complete, and the reply at the time was, reasonably, to land it in Django first. As that's now happened, I've opened a PR to update DRF accordingly encode/django-rest-framework#10091. It adds QUERY to SAFE_METHODS and the permission maps, routes it to a viewset's query() on the list route, adds request.filter_params for the filters to read, and adds .query() to the test client. django-filter. If and when the DRF change is merged, I'd like to contribute the matching change to django-filter, so DjangoFilterBackend reads QUERY bodies through the same filter_params idea. I've already worked out what needs to change. drf-spectacular. Schema generation currently crashes with KeyError: 'query' on any view with a query() handler, so I've opened a one-line fix: tfranzel/drf-spectacular#1553. Once those land, the main ecosystem supports QUERY and drf-query-method has done its job. If it doesn't work out - some big packages are understandably conservative about adopting new things too quickly - the package stays as the fix, and I'll keep it maintained. Things to watch out for It's a new method, so not everything knows about it yet. - CORS. QUERY isn't a CORS-safelisted method, so cross-origin requests get a preflight and the server has to allow it. With django-cors-headers, add it to CORS_ALLOW_METHODS. - Proxies, CDNs and firewalls. Some reject methods they don't recognise. Check it actually reaches Django through everything in front of it. - Caching. Responses are cacheable in principle, but caches generally don't key on request bodies, and Django's cache_page won't cache QUERY. Don't expect it to be cached. - The browsable API can't send QUERY, so you'll be testing with curl, fetch() or the test client. Feedback welcome This is my first real go at contributing to DRF, so I'd really appreciate people kicking the tyres. If you've got a filter-heavy API or a big DataTable, try it and tell me what breaks: - Package: pypi.org (GitHub, docs) - DRF pull request: encode/django-rest-framework#10091 - drf-spectacular fix: tfranzel/drf-spectacular#1553 And if you think QUERY belongs in DRF itself (or don't), a comment on the pull request would help the maintainers see whether people actually want it. Thanks for reading!

  2. I let httpx's departed reviewers come back for a day. They had notes. This is a submission for the Hacktoberfest Open-Source AI Challenge: Week 4 — Ghost in the Machine. What I Built Ancestors lets a repository's past reviewers look at today's pull requests. Every long-lived open-source project has people who reviewed hundreds of PRs and then moved on. encode/httpx is a good example. Of its 2,690 line comments, 675 were written by florimondmanca, whose last one is from February 2023; 255 by sethmlarson, all in 2019; 124 by yeraydiazdiaz, none after mid-2020. Three of the four most prolific reviewers in the project's history have not left a review comment in years. Their judgement did not leave with them. It is still there, in threads nobody reads any more. Ancestors ingests that history, distils what the reviewers kept asking for, and then reviews a new diff in their collective voice. Three rules make it honest rather than spooky: 1. Only the dead may speak. A comment counts as an ancestor only if it is more than twelve months old, and the PR under review is never allowed to testify about itself. (My first version got this wrong and happily "discovered" the reviewers' concerns by reading this week's review thread. Fixed, and the fix is the most important line in the repo.) 2. No citation, no verdict. Every note must cite a comment id that was actually retrieved for that hunk. If the model cites nothing verifiable, the verdict is downgraded to "the ancestors are silent on this". 3. Two witnesses or it does not count. A "house rule" in the charter survives only if two different comments back it. It runs entirely on my desktop: Gemma 3 (4B) writes, EmbeddingGemma retrieves, Mastra holds the agent, memory, vector store and the charter workflow, and Temporal turns a multi-hour pass over a repo's open PRs into something that survives me killing the worker. Día de los Muertos was the obvious frame. The dead are allowed to visit for one day, and they may only speak about what they actually saw. Demo !The ancestors visited PR #3769: three notes, each citing a 2020 review comment on the same file A report as rendered by the little viewer in out/: every note ends in clickable citations; hunks the ancestors never spoke about are listed as silent. !Temporal timeline: reviewPr attempt 1 (red) dies with the worker, attempt 2 picks the same PR up on a new worker, then the second PR runs The Temporal UI timeline of the run described below: the red segment is the review that died with worker #1; the retry is the same activity on worker #2. Repo: vigohan95/ancestors (MIT). Everything below is from real runs on a Ryzen 7 5700G with no GPU. What the ancestors actually said I pointed it at three open httpx PRs this morning. Every voice quoted below is from 2020–2024; the cutoff means nothing from the last twelve months can speak, and the PR's own thread is excluded. PR #3769 — "Ensure mounted transports are closed even if main transport raises." The PR wraps transport shutdown in Client.close() / aclose() in try/finally. For three of the four hunks the ancestors came back with the same two voices from August 2020, when this exact method was last redesigned: > 🕯️ httpx/_client.py −1267,10 +1267,12 — The ancestors are concerned about exception handling and ensuring all transports are closed, even in error scenarios. Comment [c477206186] suggests passing exception information and closing proxies, while [c472953172] recommends handling redundant close calls. The current code uses a try...finally block to guarantee transport closure, but the absence of exception handling is noted. > > - [c477206186] florimondmanca, 2020-08-26, PR #1218 on httpx/_client.py: “…await self._transport.aexit(exc_type, exc_value, traceback) for proxy in self._proxies.values()…” > - [c472953172] lovelydinosaur, 2020-08-19, PR #1197 on httpx/_client.py: “We should put self._is_closed = True as the very first line, rather than the…” That is a real thread. In PR #1197 and #1218 the maintainers argued about exactly this close-ordering problem — pass the exception down, set _is_closed first, close every proxy even when one raises. The 2026 PR re-opens the same ground without knowing, and the ancestors hand the reviewer the two comments to read before deciding. (4 hunks, 106 s, 3 notes, 1 silent.) PR #3767 — "Include header name in encoding error message." For the hunk in httpx/_models.py that re-encodes header values, the ancestors pulled obataku's 2020 question about why the code does not just use normalize_header_value, and alexmojaki's 2024 aside about self.text for non-UTF-8 charsets. Useful context for a reviewer; probably not a blocker. The other three hunks (tests, an unrelated signature) came back silent. PR #3777 — "Add real async iterator for ByteStreams." Both hunks: silent. The nearest voice (agronholm, 2025-07, on __aiter__() returning any AsyncIterator) is from this summer, so it is not old enough to be an ancestor, and nothing older applied. I like that it says so. A hosted model would have produced three confident paragraphs here. The current weakness is visible too: when the ancestors conclude "this is acceptable", Gemma 4B still labels it a note instead of honoured. The verdict schema has an ok state; the small model under-uses it. That is a prompt problem I know how to fix and a model-size problem I can fix with one environment variable. How I Built It 1. The voices gh api repos/encode/httpx/pulls/comments returns every line-anchored review comment since 2019: 2,690 of them across 168 files, median 119 characters, 1,182 from 2019 alone. Each substantive comment (≥25 characters; 2,442 of them) is embedded together with where it was said — file path, reviewer, the last six lines of the hunk under review — using EmbeddingGemma through Ollama, and stored in a LibSQL vector index via @mastra/libsql. A blob-less clone supplies the 1,482-commit log, so the agent can also tell you who last touched a file and why. Ingest, end to end, on CPU: 448 seconds. 2. The charter — a Mastra workflow Before reviewing anything, a three-step createWorkflow (sample-voices → distil-charter → save-charter) samples comments stratified by year (so 2019's flood does not drown 2024), hands them to Gemma, and asks for the expectations the reviewers kept returning to, each with the comment ids that support it. Then the code applies the two-witness rule and throws away everything else. Here is the entire charter for httpx after the two-witness rule: > Distilled by Gemma from 44 comments spread across every year of review history; a rule only survives if at least two different comments back it (7 single-witness candidates dropped). > > 1. Prefer explicit parameters over **kwargs in public signatures. ([c399643358] [c552568019]) One rule. Gemma proposed eight; seven were one reviewer talking about one line, dressed up as tradition. I would rather hand the review agent one true sentence than eight plausible ones, and the sampled comments still reach the agent through retrieval, where they are attributed to a person and a date instead of to "the project". 3. The review — a Mastra agent on a very short leash The agent never gets "review this PR". For each of the most substantive hunks it gets: the hunk, the last five commits to that file before the cutoff, the charter, and the six closest ancestor comments (comments on the same file get a boost — the ancestors literally stood here). It has to answer in a strict JSON shape: note, ok, or silent, plus citations. The line that keeps it honest: const cited = (parsed?.citations ?? []) .map(c => allowed.get(c.replace(/[\[\]]/g, ''))) .filter(Boolean); if (verdict !== 'silent' && cited.length === 0) verdict = 'silent'; allowed is the set of comments that were actually retrieved for this hunk. A verdict that cites nothing from that set is silence, whatever the model wrote. This is what makes a 4B model usable: the retrieval does the remembering, the model does the reading, and it cannot pass off invention as tradition. Per hunk on this CPU: 25–40 seconds. A four-hunk PR: about two minutes. 4. The haunting — Temporal Reviewing one PR takes minutes; reviewing a repository's open PRs takes an hour. That is exactly the kind of job that dies when a laptop sleeps. The hauntRepo workflow lists open PRs and calls a reviewPr activity per PR; the activity heartbeats after every hunk, the worker runs one activity at a time (maxConcurrentActivityTaskExecutions: 1, because there is one local model), and the retry policy does the rest. Here is the run I kept (workflow haunt-encode-httpx-1793376447857, two most recent open PRs): | time (UTC) | what happened | |---|---| | 16:07:27 | workflow starts; listOpenPrs returns #3796 and #3785 | | 16:07:28 | reviewPr(3796) attempt 1 starts on worker #1; hunks 1–2 reviewed, heartbeat after each | | 16:07:51 | I kill worker #1's node process (taskkill /F /T) mid-hunk | | 16:09:40 | heartbeat timeout (2 min) fires; Temporal schedules attempt 2; worker #2, started at 16:08:08, picks it up and reviews all 6 hunks | | 16:11:20 | attempt 2 completes (98 s of model time); reviewPr(3785) runs on worker #2 | | 16:11:39 | workflow completes: 2 PRs reviewed, 3 notes, nothing lost, nothing reviewed twice by the same worker | The activity itself is idempotent (it rewrites the report file), so the retry policy is the whole crash story: no checkpoint code, no queue, no cron. The first version of this demo "worked" because my kill command only killed the npx wrapper and the orphaned node process quietly finished the job; the event history is what caught me — the retry attempt never appeared. Durable execution is also a lie detector. Why open innovation matters for this - Review comments on a private repo are the last thing you want to ship to a hosted model. Everything here talks to localhost:11434. Unplug the network after ingest and the review still works (--diff file.patch skips GitHub entirely). - It has to be cheap enough to run on every PR, forever. The whole httpx history embeds in seven minutes on a five-year-old desktop CPU; a review costs electricity, not tokens. - Swappable. ANCESTORS_MODEL=gemma3:12b and the ancestors get more articulate; no code change. The project's values live in the retrieval, the cutoff and the charter, not in a model vendor's weights. - Where open beat closed for me: I could make the model shut up. With a hosted API I would be prompting against a system tuned to always say something helpful. Here I own the loop: no citation, no verdict; too young, not an ancestor. Numbers | | | |---|---| | Review comments ingested | 2,690 (2019–2026), 168 files | | Embedded (≥25 chars) | 2,442 with EmbeddingGemma, 768 dims | | Commits indexed | 1,482 | | Ingest wall time (CPU) | 448 s | | Charter | 1 Gemma call, ~120 s | | Review PR #3769 | 4 hunks, 106 s, 3 notes / 1 silent | | Review PR #3767 | 4 hunks, 106 s, 1 note / 3 silent | | Review PR #3777 | 2 hunks, 55 s, 2 silent | | Hardware | Ryzen 7 5700G, 31 GB RAM, no discrete GPU | Prize Categories - Best Use of Gemma — Gemma 3 4B generates every verdict and the charter; EmbeddingGemma does all retrieval. Both run locally through Ollama; nothing leaves the machine. - Best Use of Mastra — Agent + Memory on LibSQLStore, LibSQLVector for the ancestor index, and a three-step createWorkflow for the charter. - Best Use of Temporal — the hauntRepo workflow with heartbeating reviewPr activities; crash-and-resume demonstrated above. Team Solo: @vigohan95. Disclosure: I used Claude Code as a pair programmer while building this. The design, the mistakes (see rule 1), every run and every number above are mine and real.

  3. graphloom: give your coding agent a map of the repo instead of letting it grep 40 files Repo: 👉 bayuh85/graphloom (MIT, Python 3.10+, standard library only, 32 offline tests) The problem Ask a coding agent "what touches the invoices table?" and watch what it does. It greps for invoices, opens the first match, reads 300 lines, greps again, opens the schema, opens a doc, opens another service. Ten tool calls later it has burned a lot of context and still might miss the function that builds the SQL string two modules away. The information it needs is not hidden. Imports, calls, CREATE TABLE statements and the backtick names in your docs are all sitting in the repo. They just are not connected anywhere the agent can look them up. So I wanted a map: build it once, cheaply and offline, then let the agent ask short questions against it and get back a few lines with file:line evidence instead of whole files. What graphloom does graphloom build walks a repo and writes three files into .graphloom/: | File | What it is | | --- | --- | | graph.json | Versioned graph. Every edge has a provenance tag and a file:line excerpt. | | REPORT.md | Hub nodes by degree, isolated modules, cross-layer links, suggested questions. | | graph.html | One self-contained interactive viewer. No CDN, works offline. | What it understands, all without a model: - Python through the stdlib ast module: modules, classes, functions, imports, calls - JS/TS with a small scanner: imports, exports, calls, fetch("/route") URLs - SQL: CREATE TABLE nodes and foreign keys, plus edges from code whose SQL strings name a table - Markdown: heading nodes linked to the symbols you wrote in backticks - TOML/YAML/JSON: config keys, linked to the code that reads them Then you query the graph instead of the tree: query, path, explain (offline) and ask (one LLM call over a small subgraph). graphloom serve exposes the same tools over MCP for Cursor and Claude Code. Quickstart on the bundled example The repo ships a small wholesale checkout app (examples/harbor-shop): Python services, a SQL schema, a browser cart and two docs. git clone https://github.com/bayuh85/graphloom && cd graphloom python3 -m venv .venv && . .venv/bin/activate pip install -e . cd examples/harbor-shop graphloom build . nodes: 67 edges: 108 parsed: 75 linked: 33 llm: 0 wrote graph: .graphloom/graph.json wrote report: .graphloom/REPORT.md wrote html: .graphloom/graph.html Now the question from the intro. "From the HTTP route, how do we end up sending a receipt email?" graphloom path post_invoice send_receipt path: post_invoice -> send_receipt resolved: fn:backend/app.py:post_invoice -> fn:backend/notify/emailer.py:send_receipt hops: 4 directed: yes 1. fn:backend/app.py:post_invoice invokes [parsed] -> fn:backend/billing/invoices.py:create_invoice backend/app.py:10 return create_invoice(payload, settings) 2. fn:backend/billing/invoices.py:create_invoice invokes [parsed] -> fn:backend/billing/payments.py:record_payment backend/billing/invoices.py:39 record_payment(str(payload["invoice_id"]), total, "card", str(payload["email"])) 3. fn:backend/billing/payments.py:record_payment invokes [parsed] -> fn:backend/notify/emailer.py:queue_receipt backend/billing/payments.py:12 queue_receipt(email, invoice_id) 4. fn:backend/notify/emailer.py:queue_receipt invokes [parsed] -> fn:backend/notify/emailer.py:send_receipt backend/notify/emailer.py:5 send_receipt(email, invoice_id) (Trimmed slightly for width.) Four hops, each with the exact line that makes the call. An agent can cite that, or open just those four lines. And "what touches the invoices table?": graphloom query invoices --budget 1600 ## tbl:invoices kind: table layer: data name: invoices at: sql/schema.sql:17 columns: id, customer_id, sku, total_cents, status edges: -> ties [parsed] tbl:customers (customers) sql/schema.sql:17 -> ties [parsed] tbl:products (products) sql/schema.sql:17 <- mentions [linked] doc:docs/billing.md:h:creating-an-invoice docs/billing.md:9 <- ties [parsed] tbl:payments (payments) sql/schema.sql:25 <- touches [linked] fn:backend/billing/invoices.py:create_invoice backend/billing/invoices.py:35 "INSERT INTO invoices (customer_id, sku, total_cents, status) " The --budget flag matters for agents: the output stops at roughly that many tokens and says truncated: yes when it does. Provenance: what was read vs. what was guessed Every edge carries one of three tags: | Tag | Meaning | | --- | --- | | parsed | Read straight from syntax: an import, a call to a known function, a foreign key | | linked | Resolved afterwards: a backtick in a doc, a table name inside a SQL string, a fetch("/invoices") matched to a route | | llm | Added by the optional semantic pass, and kept only if it anchors to a node that already exists | This is the part I care about. When an agent says "create_invoice writes to invoices", you can see that came from a linked edge on line 35, not from the model's imagination. A real repo: httpx The toy example is nice, so I also pointed it at encode/httpx (commit b5addb6, about 280 KB of Python plus the docs). $ time graphloom build . nodes: 1767 edges: 3388 parsed: 2244 linked: 1144 llm: 0 real 0m0.494s Half a second, no network. The hubs table in REPORT.md reads like a cheat sheet of the codebase: Response (degree 160), Client (152), MockTransport (101), Request (99), AsyncClient (84). How does Client.get reach send? graphloom path fn:httpx/_client.py:Client.get fn:httpx/_client.py:Client.send hops: 2 directed: yes 1. fn:httpx/_client.py:Client.get invokes [parsed] -> fn:httpx/_client.py:Client.request httpx/_client.py:1053 return self.request( 2. fn:httpx/_client.py:Client.request invokes [parsed] -> fn:httpx/_client.py:Client.send httpx/_client.py:825 return self.send(request, auth=auth, follow_redirects=follow_redirects) That answer is 609 bytes. httpx/_client.py alone is 65,713 bytes, which is what an agent typically reads to find the same thing. To be fair about the limits: graphloom does not do type inference. Asking for a path from Client.get to HTTPTransport.handle_request comes back directed: no, routed through the "Custom transports" doc heading, because the real call is self._transport.handle_request(...) and a static scan can't know what self._transport is. The output tells you that (directed: no, linked edges), so the agent knows to go read the code at that point instead of trusting the path. Where the model comes in (and why it stays cheap) The graph never needs a model. Two optional features do: - graphloom build --semantic --max-calls 4 sends doc chunks only (never source code) to a model, which proposes concepts and anchors. Results are cached by content hash, so a second build of unchanged docs costs nothing. - graphloom ask "question" retrieves a small subgraph and asks the model to answer citing node ids and file:line. For ask "how does Client.get end up sending the request?" on httpx, the retrieved context sent to the model is about 10 KB, roughly 2.5k tokens. That's one small call per question, not a whole file per tool call. Because the calls are small and frequent, the price per token matters more than the model's size. graphloom defaults to AtmoRouter, a cheap AI API gateway with an OpenAI-compatible API (and an Anthropic-compatible one), and an OpenRouter alternative where one key covers 109 models at an average of 83.7% below official prices. The default model is zai/glm-5.3-flash. export ATMOROUTER_API_KEY=ar-your-key-here graphloom build . --semantic --max-calls 4 graphloom ask "what touches the invoices table?" It's not locked in. Any OpenAI-compatible endpoint works: | Variable | Default | | --- | --- | | GRAPHLOOM_BASE_URL | https://atmorouter.dev/v1 | | GRAPHLOOM_API_KEY | falls back to ATMOROUTER_API_KEY | | GRAPHLOOM_MODEL | zai/glm-5.3-flash | | GRAPHLOOM_API_STYLE | openai (or anthropic to POST {base}/messages) | Using it from Cursor or Claude Code Build once, then register the MCP server. The server speaks newline-delimited JSON-RPC over stdio and exposes graph_query, graph_path, graph_explain and graph_ask. { "mcpServers": { "graphloom": { "command": "graphloom", "args": ["serve", "--graph", ".graphloom/graph.json"] } } } Put that in .cursor/mcp.json for Cursor or .mcp.json for Claude Code. Then tell the agent to try graph_path / graph_query before grepping. Three of the four tools are offline, so you only pay when it calls graph_ask. Try it - Code: bayuh85/graphloom - Default gateway: atmorouter.dev It's MIT licensed, and the extractors are small, readable files (extract_python.py, extract_sql.py, ...), so adding Go or Rust support is a contained PR. If it saves your agent some file reads, a star on GitHub helps other people find it.

    1 comment
  4. graphify: Turn Your Codebase into a Knowledge Graph for AI Coding Agents graphify has been all over my timeline this week: it went past 126k GitHub stars, got into Y Combinator and showed up on the Vercel Marketplace. The pitch is simple. Type /graphify in Claude Code, Codex or Cursor, and your agent queries a graph of your code instead of reading files one by one. "Saves tokens" is an easy claim to make and a hard one to check, so I installed it on a plain Linux box and pointed it at a real repo. No cloud trial, no API keys. Here's what happened. What graphify actually is graphify is an open-source (Apache-2.0) Python CLI plus an agent skill. It parses your project with tree-sitter and writes three files to graphify-out/: - graph.html, an interactive force-directed graph you open in a browser - GRAPH_REPORT.md, a summary with "god nodes", communities and surprising connections - graph.json, the full graph your agent queries through explain, path and query Code extraction is deterministic and local. An LLM only gets involved for docs, PDFs, images and video, or for naming communities, and only if you configure a backend. Setup My test box runs Debian 13 with Python 3. The PyPI package name has a double y: python3 -m venv venv venv/bin/pip install graphifyy # installed 0.9.92 git clone --depth 1 https://github.com/encode/httpx testbed/httpx For the testbed I picked httpx: a real, well-known Python HTTP client with about 17,750 lines of Python plus docs and a changelog. It's big enough to be interesting and small enough that I know roughly what the answers should be. Building the graph: 2 seconds, 0 tokens You don't need an assistant for the code-only build. The update command does it straight from the CLI: cd testbed/httpx graphify update . Re-extracting code files in . (no LLM needed)... AST extraction: 101/101 uncached files (100%) [8 workers] [graphify watch] Rebuilt: 2233 nodes, 4453 edges, 166 communities A fresh build took 2.1 seconds. After I edited one file, an incremental rebuild re-parsed only that file and took 1.6 seconds. The report's own footer says Token cost: 0 input · 0 output. The output is not small: graph.json is 1.7 MB, graph.html is 1.9 MB and GRAPH_REPORT.md is 24 KB. Markdown docs got pulled in as structure too, so CHANGELOG.md and docs/api.md show up as nodes. !graph.html for httpx What the report got right The "god nodes" list is honestly a good first page for a new contributor: 1. Response - 170 edges 2. Request - 111 edges 3. Changelog - 79 edges 4. Client - 69 edges 5. AsyncClient - 51 edges 6. BaseClient - 45 edges That's httpx's real core. 88% of edges were EXTRACTED (read directly from the source) and 12% were INFERRED, with an average confidence of 0.91. I like that every edge carries this tag, because it tells you what to trust. "Surprising connections" was less useful. Every entry was a changelog or doc heading linked to Client, which is true but not surprising. Querying: where the token savings come from This is what your agent actually runs. I counted every output with tiktoken's cl100k encoding: graphify explain "httpx/_client.py::Client" graphify path "httpx/_client.py::Client" "DigestAuth" graphify query "how does a request get sent through the transport" !explain output - explain on Client came to 628 tokens. It listed 69 connections with file and line numbers, like --> HTTPTransport [uses] [INFERRED] httpx/_client.py:L731. Reading _client.py itself costs 13,659 tokens. - path returned a 3-hop chain in a single line: Client <-contains- _client.py -imports_from-> _auth.py -contains-> DigestAuth. - query came to 1,728 tokens. It ran a BFS that found 536 nodes but showed only 82, and printed a clear TRUNCATED warning telling me to raise --budget to 14720 to see everything. !path and query output So yes, for "where is X and what touches it" questions, a graph lookup is roughly 20x cheaper than dumping the file. What it doesn't do is replace reading the code once the agent has to change a function. The graph points you to the right lines; it doesn't contain them. Two rough edges Ambiguous names. graphify explain "Client" refused to answer, because Client matched both the class in httpx/_client.py and a heading in docs/api.md. The error tells you to use the path::symbol form, which works, but docs being indexed alongside code means common names collide a lot. Community labels with a small local model. Without an API key, the 166 communities are named after a representative file or symbol (test_content.py, AsyncByteStream). graphify can rename them with an LLM, Ollama included: pip install openai # the Ollama backend needs it; the first run told me so graphify label . --backend=ollama --model=qwen3:1.7b On my box, with a 1.7B model on CPU, this ran for more than 11 minutes without finishing, and I killed it. The code graph doesn't need this step at all, but if you want nice labels, give it a real model or an API key. I saw the same kind of small-model limit when I ran Anatomy's skill on a 1.7B local model. Is it worth installing? For code, yes. It's free, local and fast, and explain/path give an agent a cheap map before it starts opening files. I'd add graphify update . to a git hook and let the agent use the graph first. What I didn't test: the /graphify skill inside a paid assistant, the semantic pass over PDFs and images, and the hosted Graphify Cloud product with its "80% token savings" claim. My 628-vs-13,659 number is one lookup on one repo, not a benchmark of a whole agent session. Repo: Graphify-Labs/graphify

  5. oh is httpx 1.0 finally happening (again)? httpx#3796

    4 likes · 240 views
Trending awards, every completed day and week
Gold
01st place finishes
Silver
02nd place finishes
Bronze
03rd place finishes
Awarded repositories
0Owned by encode now

No trending awards yet

None of the repositories encode owns has finished in the top three of a completed trending day or week, by stars, forks or PR authors. See who is trending now.

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.