Muffin is a small ASGI framework that runs on three async libraries
Muffin is a fast, simple and asyncronous web-framework for Python 3
At a glance
- What is it?
- Muffin is a Python 3.11+ ASGI web framework that supports asyncio, Trio and Curio out of the box, with a plugin ecosystem covering templating, sessions, OAuth, databases and admin UI generation. It was completely rewritten on ASGI after version 0.40, and it is MIT licensed with a single pip dependency set of three packages.
- Who is it for?
- Muffin suits a Python developer who wants a small ASGI application with a plugin for the parts that are annoying to write, and who does not want to commit to a single async library, since the same application runs under asyncio, Trio or Curio.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the framework is, and the rewrite that shaped it
Muffin is described as a fast, lightweight, and asynchronous ASGI web framework for Python 3.11+, and the repository description puts it as a fast, simple and asynchronous web framework for Python 3.
The Credits section is where the real story is. Muffin greater than 0.40 is completely rewritten on ASGI, with Kirill Klenov as the author, and Muffin less than 0.40 was based on AIOHTTP, with Klenov, Andrew Grigorev and Diego Garcia credited. So the current framework is a second implementation, and the 0.40 line is the boundary.
That history explains the design. An aiohttp based framework is built on a specific library's request and response objects, while an ASGI framework talks to the server through a standard interface, and the rewrite is what makes the multi-library support possible. A framework that supports Asyncio, Trio and Curio out of the box is making a statement about the ASGI layer doing the abstraction rather than the framework's own internals.
The stated position is that Muffin combines the simplicity of microframeworks with native ASGI performance, supporting multiple async libraries out of the box, and that its rich plugin ecosystem makes building modern web applications pleasant and efficient.
The package is published on PyPI as muffin at version 3.0.1, the licence in the pyproject.toml is MIT, and the README says Muffin is licensed under the MIT license. The default branch is develop rather than main, and the last push was on 2026-10-19.
The dependency set is three packages
The dependency list in the pyproject.toml is short enough to state exactly: asgi-tools>=3.2.0, modconfig and orjson.
The README names the first two and their roles. ASGI-Tools is the ASGI toolkit, and Modconfig is a hierarchical configuration manager. orjson is not mentioned in the README's dependencies section but is in the manifest, and it is the JSON implementation behind the JSON response type.
Three dependencies is a real number to weigh. It means the framework is small enough to read, and it means the transitive tree is small enough that an audit or a supply chain review is not a research project. For a framework that a team might run in front of something sensitive, that is a reasonable property.
The optional extras are equally small. The standard extra is gunicorn and uvicorn with the standard extras, which is what pulls in the httptools and uvloop accelerators. There is no ORM, no templating engine and no HTTP client in the core, because those are the plugin's job.
The entry points are worth noting. There is a console script muffin mapped to muffin.manage:cli, and a pytest plugin registered as muffin_pytest pointing at muffin.pytest, which means a test suite can pick up Muffin specific fixtures without a separate conftest import.
The classifiers list Python 3.11 through 3.15, PyPy as an implementation, and the framework is marked as Production/Stable, with Framework :: AsyncIO and Framework :: Trio classifiers both present.
The quickstart, in fifteen lines
The quickstart is the shortest useful piece of documentation in the repository, and it shows the shape of the API.
import muffin
app = muffin.Application()
@app.route('/', '/hello/{name}')
async def hello(request):
name = request.path_params.get('name', 'world')
return f'Hello, {name.title()}!'There are four things to notice. The application object is constructed with no arguments. The route decorator takes multiple patterns at once, so a single handler serves both the root and a path with a parameter. The handler is an async function that receives a request and returns a string, so the framework does the response wrapping. And the path parameter is read from request.path_params, with a default supplied in the code rather than in the route.
That last point is a small design decision with a large effect on ergonomics. A default of 'world' in the handler means the same function serves both a named and an unnamed request, so there is no need for a second route or a redirect.
You save the file as example.py and run it with uvicorn:
$ uvicorn example:appThen you visit http://localhost:8000 or http://localhost:8000/hello/username in your browser. The uvicorn invocation is worth noting: the application object is an ordinary ASGI application, so any ASGI server can serve it, and the framework does not own the process.
The plugin list is where the real feature set lives
The Plugins section lists twelve packages, and the list is the best available description of what Muffin actually does, because the core does very little.
For templating there is muffin-jinja2, for Jinja2 templates, with asyncio, trio and curio variants. For sessions there is muffin-session, which gives signed cookie-based HTTP sessions. For authentication there is muffin-oauth for OAuth integration, and for monitoring there is muffin-sentry for Sentry error tracking.
For data there are four: muffin-peewee for the Peewee ORM, muffin-databases for SQL database support, muffin-mongo for MongoDB, and muffin-redis for Redis. For internationalisation there is muffin-babel.
Then the two that are more than a binding. muffin-rest provides REST API utilities, and muffin-admin is an auto-generated admin UI.
The distribution is the point. Each of these is a separate repository with its own version and its own release cadence, and the README says to see each repo for usage and installation instructions. So a Muffin application is assembled from independently versioned pieces, which is a different upgrade story from a framework that ships its ORM and its admin panel inside the same package.
The core's own feature list is correspondingly short: ASGI compatible, competitive performance with a link to the benchmark suite, support for Asyncio, Trio and Curio, multiple response types covering text, HTML, JSON, streams, files, SSE and WebSockets, and a first-class plugin system for templating, databases, auth and more.
The response type list is the one to note. Server-sent events and WebSockets are named explicitly, and streams and files are there too, so a Muffin application can do realtime without reaching for a plugin.
The three async libraries, and what that means in practice
Supporting multiple async libraries is unusual enough to be worth understanding as a constraint rather than a feature list.
An asyncio application runs on an event loop with a particular API for cancellation, task groups and structured concurrency. Trio has its own, and a Trio-native library cannot simply be called from asyncio code without a compatibility shim. Curio has a third. A framework that supports all three therefore cannot build on any of their specific concurrency primitives, and has to write against the intersection.
The payoff for a user is that the choice of async library is not forced by the web framework. A team with an existing Trio codebase, or one migrating off asyncio, or one that wants to run the same application under different schedulers for testing, can do that without the framework being the obstacle.
The cost is that some conveniences are unavailable. Any framework feature that would use an asyncio-specific pattern is either reimplemented per library or omitted, and the plugin list shows the same split, with muffin-jinja2 explicitly shipped in asyncio, trio and curio variants. That is a hint about how the maintainer handles the difference: by maintaining separate builds.
The pyproject dev dependencies tell you the testing story. pytest-aio is installed with the curio, trio and uvloop extras, so the test suite runs the same tests under all three backends. That is a good sign, because it means the compatibility claim is checked rather than asserted.
There is also uvloop in the standard extra list, which is an asyncio-specific accelerator, so the fastest path is asyncio with uvloop, and the other two libraries are there for the cases where you need them.
Building, testing and releasing
The Makefile is small and it is built around uv, which tells you the toolchain.
The default target creates a virtual environment, and the rule depends on uv.lock and .pre-commit-config.yaml, so touching either re-syncs the environment. It runs uv sync with all groups and then installs the pre-commit hooks, with GIT_CONFIG set to /dev/null so that a missing git configuration does not break the install.
Testing is a single target. make test runs uv run pytest with -xsvl against the tests directory, and the flags are worth reading: -x stops on the first failure, -s disables output capture so you see prints, -v is verbose and -l shows local variables on failure. The l flags are there because these are async tests, and a traceback without locals is hard to read when the failure is inside a task group.
Linting is a two-tool target. make lint runs ruff check and then pyrefly check, so the project uses ruff for linting and pyrefly for type checking, and the pyrefly dependency is pinned at 0.52.0 or newer in the dev group. The docs target runs sphinx-build over the docs directory with the html builder, and the configuration uses pydata-sphinx-theme and sphinx-copybutton.
The release target is the interesting one, because it shows the branching model. It checks out main, pulls, checks out develop, pulls, bumps the version with uvx bump-my-version, locks, generates a commit message from the git log between main and develop while filtering out GitHub noise and merge commits, merges develop into main with --ff-only, then pushes main, tags the version and pushes the tag.
So develop is the working branch and main is the released branch, which explains why the repository's default branch is develop, and it explains the fast-forward-only merge constraint: the release script assumes the two branches never diverge.
Where Muffin fits, and where it stops
The comparisons worth making are with a larger framework, with a microframework, and with writing ASGI directly.
Against a larger framework, the difference is that Muffin gives you a small core and a set of plugins you choose yourself. The benefit is that your dependency tree reflects what your application actually uses, and that the pieces are independently versioned. The cost is that you are assembling rather than adopting, and that each plugin has its own documentation, its own release cycle and its own compatibility questions with the framework version.
Against a microframework, the difference is the plugin surface. A microframework gives you routing and little else; Muffin gives you routing plus a plugin API and twelve plugins covering the parts of a web application that every project ends up writing. If you were going to pick a template engine and a session layer anyway, that is the difference in effort.
Against writing ASGI by hand, the difference is that Muffin handles the request object, the routing with path parameters, and the response types including SSE and WebSockets. If your application is three endpoints, that is overkill.
The limits are structural. The framework is young in its current form, with the ASGI rewrite having happened after 0.40 and the current version at 3.0.1, so the interface is still moving. The core is deliberately small, which means a project that needs something the twelve plugins do not cover is writing it. The Python floor of 3.11 excludes a meaningful number of production environments, and the supported versions run up to 3.15 in the classifiers, which suggests the project tracks new Python releases closely rather than supporting old ones.
The performance claim is the one to verify rather than accept. The README links to a benchmark suite at klen.github.io/py-frameworks-bench and calls the performance competitive, and benchmarks of web frameworks are sensitive to the server, the worker count and the payload. What the framework controls is the layer between the ASGI server and your handler, and that layer is where the three dependencies live.
The licence is MIT, with no obligations beyond attribution, and the repository carries a CHANGELOG.md, an AGENTS.md, a .pre-commit-config.yaml and a uv.lock for reproducible development environments.
Editorial conclusion
Muffin suits a Python developer who wants a small ASGI application with a plugin for the parts that are annoying to write, and who does not want to commit to a single async library, since the same application runs under asyncio, Trio or Curio. It is the wrong tool if you want a large framework with a big in-tree feature set, because almost everything beyond the routing and response layer is a separate package you install and version yourself, and it is the wrong tool if you are on Python 3.10 or older, since the floor is 3.11. Before adopting it, install muffin[standard] so you get uvicorn and gunicorn, and check the plugin you need actually exists, because the twelve listed plugins cover the common cases and nothing more.
Frequently asked questions
How do I install Muffin?
Run pip install muffin for the base package, or pip install muffin[standard] for the standard installation which includes gunicorn, uvicorn with the standard extras, uvloop and httptools. Muffin requires Python 3.11 or newer.
What is the minimal Muffin application?
Create a muffin.Application(), decorate an async handler with @app.route('/', '/hello/{name}'), read the parameter from request.path_params and return a string. Then serve it with uvicorn example:app and visit http://localhost:8000 or http://localhost:8000/hello/username.
Which async libraries does Muffin support?
Asyncio, Trio and Curio, all out of the box. The dev dependencies install pytest-aio with the curio, trio and uvloop extras so the test suite runs the same tests under all three backends, and the muffin-jinja2 plugin is published in asyncio, trio and curio variants.
What plugins are available for Muffin?
Twelve are listed: muffin-jinja2 for templates, muffin-session for signed cookie sessions, muffin-oauth, muffin-sentry, muffin-peewee, muffin-babel for i18n, muffin-databases, muffin-mongo, muffin-rest, muffin-redis, muffin-admin for an auto-generated admin UI, and muffin-prometheus as a metrics exporter.
How do I run the Muffin test suite?
Run make test, which runs uv run pytest with -xsvl against the tests directory, stopping on the first failure, showing output and local variables. The environment is created and synced with uv from the uv.lock file, and pre-commit hooks are installed as part of it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/klen-muffin)Was this useful?