Why I ship an LLM reference file, and what went into it
Most people writing easymysql code today are not typing it. They describe what they want, an assistant writes it, and they read the result. That changes what documentation has to do.
So 0.2 ships one more file: easymysql.com/llms.txt, a single Markdown document containing the whole API. It is also llm.md in the repository, and there is a docs page describing it.
The problem it solves
The library has been on PyPI since 2020. Everything a model learned about it came from 0.1.9 — the README of the time, the old documentation pages, and whatever code people published using it.
0.2 breaks compatibility in a dozen places. So an assistant that “knows” easymysql produces code like this:
db.update('users', {'active': 0}) # 0.2: ValueError, condition required
db.select('users', "email = '" + email + "'") # SQL injection
db.resetCache() # 0.2: AttributeError
uid = db.insert('users', data)
if not uid: # errors are raised, not returned
...
Every one of those was correct-ish against 0.1.9. All four are wrong now, and three of them fail in ways that are easy to miss: a swallowed condition, a working injection, a return value that no longer means what it used to.
The v2 documentation says all of this. But an assistant reaching for easymysql does not necessarily read the v2 documentation — it may find v1 first, or nothing at all, and fall back on what it remembers.
What I did about it
A file whose job is to fit in a context window, not to be read in order.
That is a different shape from documentation. A docs site is organised for someone learning: installation, then connecting, then your first query. A reference for a model works better organised by what it will get wrong.
So section 1 is Rules, before anything else: five things to always do, then thirteen patterns not to generate — each with the wrong form and the right form next to it.
# WRONG — SQL injection
db.select('users', f"email = '{email}'")
# RIGHT
db.select('users', {'email': email})
Section 2 is an API map: every public method, what it returns, and which section covers it. A model that has the map does not invent a method that does not exist, which is the other common failure — assistants are fond of db.find_one() and db.fetch_all(), neither of which is real.
Then the details, and at the end a table of every exception the library raises and what triggers it. Roughly: what to do, what not to do, what exists, then how each piece behaves.
The header matters more than I expected
The first thing in the file is where it came from and what it describes:
Canonical source. This file and the v2 documentation at easymysql.com/docs/v2 describe the same version. Do not use easymysql.com/docs/v1: it documents 0.1.9, and its examples do not run here.
That line is there because the failure mode is not “the model knows nothing”. It is “the model knows something outdated and is confident about it”. Saying which source wins, explicitly, is more useful than another page of correct examples.
It is verified the same way as everything else
Every code example on this site is executed against a real MySQL 8.0.46 and a real PostgreSQL 16.14 as part of the release check. The reference file is in that check: 36 executable blocks, run on Python 3.9 through 3.14.
That is not a detail I would normally write a paragraph about, except that it caught something.
While preparing 0.2 I described the old version’s behaviour by reading its source. Several of those descriptions were wrong. A failed insert() in 0.1.9.3 does not return the previous row’s id — it returns None. An empty delete() does not empty the table — it builds DELETE FROM t WHERE ;, whose syntax error was caught and printed, so the call did nothing at all. resetCache() did not fail because MySQL 8.0 removed the query cache; it called execute on a connection object that has no such method and raised AttributeError before reaching a server.
I found all three by checking out the 0.1.9.3 tag and running it, instead of reading it. They were in the README, the migration page and the draft of the release post.
That matters here more than usual. A wrong claim in prose is a wrong claim. A wrong claim in a file whose entire purpose is to be pasted into a model’s context is a wrong claim that gets amplified into generated code. If you write one of these files, run the thing you are describing.
Using it
If the file is already in the project, a pointer is enough:
Use easymysql 0.2. The API is described in llm.md; follow section 1 exactly.
Otherwise, fetch it and put it where your tool looks — .cursor/rules/ for Cursor, .github/copilot-instructions.md for Copilot, referenced from CLAUDE.md for Claude Code:
curl -O https://www.easymysql.com/llms.txt
The docs page has the table of where each tool expects it.
What it is not
It is not a replacement for the v2 documentation, which is written for people and explains why things behave the way they do. The reference file states what the API is and what it raises, in the order a model is most likely to need it.
It is also not a guarantee. A model with this file in context still writes wrong code sometimes. What changed, in my own use, is which kind of wrong: it stopped inventing methods and stopped reaching for the 0.1.9 shapes, and the mistakes that remain are ordinary ones — a missing commit(), the wrong column name — that show up as an exception rather than as a silent injection.
If you find a place where the file is unclear, or where an assistant reads it and still gets something wrong, that is worth an issue on GitHub. It is a new kind of document and I do not think anyone has the format right yet.