ThriftPy2: a pure Python Apache Thrift stack with no code generation
Pure python approach of Apache Thrift.
At a glance
- What is it?
- ThriftPy2 is a pure Python implementation of the Apache Thrift protocol that parses IDL at run time and generates the client and server stubs on the fly, so there is no compiler step and no generated code to check in. It suits a Python team that wants to talk to an existing Thrift service, and it is the wrong choice if you need the performance of the official C++ library.
- Who is it for?
- ThriftPy2 fits a Python service that has to interoperate with an existing Thrift deployment and wants to skip the compiler and the generated stubs, because the IDL is parsed and the modules built at run time, and because the pure Python path means no build toolchain in your image. It is the wrong choice if you need the throughput of the official C++ implementation, and it is the wrong choice for a schema-first workflow where generated code is committed and reviewed.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 27 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
IDL at run time instead of stubs in the repository
ThriftPy2 is a pure Python implementation of the Apache Thrift protocol, and the readme states the point of it plainly: it allows you to parse Thrift IDL files and create RPC clients and servers without code generation or compilation.
That is a different architecture from the one Thrift is normally used with. The usual workflow runs the Thrift compiler over an IDL file and emits Python, Java or C++ source that you commit, review and regenerate when the schema changes. ThriftPy2 replaces that with a load call at run time:
import thriftpy2 from thriftpy2.rpc import make_server
pingpong_thrift = thriftpy2.load("pingpong.thrift", module_name="pingpong_thrift")
The file is parsed, the service definition is turned into a module, and the module name is the one you pass in. The feature list describes it as dynamically load thrift files as Python modules, with code generated on the fly.
The trade is worth stating plainly. You lose the compile-time check that a generated stub gives you, because a broken schema now surfaces when the module loads rather than when you run the compiler. You gain a build with no Thrift compiler, no generated files in the repository, and a service definition that can change with the IDL rather than with a regeneration step.
The package is on PyPI as thriftpy2, is MIT licensed, and the project description calls it a pure python approach of Apache Thrift. The runtime dependencies are small, ply for the parser and ijson for JSON handling, and the project supports Python 3.10 and newer as well as PyPy3.
One important compatibility note: the readme says ThriftPy2 is compatible with Apache Thrift, so you can use it together with the official implementation servers and clients.
The same service, four ways of serving it
The readme walks through four variants of the same pingpong service, and the differences between them are the API surface in practice.
The synchronous server loads the IDL, defines a Dispatcher class with a method per service function, and hands both to make_server:
import thriftpy2 from thriftpy2.rpc import make_server
pingpong_thrift = thriftpy2.load("pingpong.thrift", module_name="pingpong_thrift")
class Dispatcher(object): def ping(self): return "pong"
server = make_server(pingpong_thrift.PingPong, Dispatcher(), '127.0.0.1', 6000) server.serve()
The client is the mirror image, with make_client and a method call that returns the result:
import thriftpy2 from thriftpy2.rpc import make_client
pingpong_thrift = thriftpy2.load("pingpong.thrift", module_name="pingpong_thrift")
client = make_client(pingpong_thrift.PingPong, '127.0.0.1', 6000) print(client.ping()) # prints "pong"
The async server uses make_aio_server and an async method on the dispatcher, and the async client uses make_aio_client inside an async main that awaits the call and then closes the client.
So the four combinations are: sync server, sync client, asyncio server, asyncio client. The service definition and the dispatcher stay the same shape, which is the point of the design. A handler written for the sync server is a plain method; the same handler on the async server is a coroutine, and the library calls the right one depending on which server you built.
The connection details are the same in all four: the host and port are passed to the factory function, and the service class comes from the loaded module. The examples directory in the repository reflects the range, with folders for pingpong, asyncio_echo, calculator, oneway, multiplexer, tutorial and gunicorn_thrift.
Which protocols and transports are actually implemented
The feature list enumerates the supported protocols and transports, and the list is where the practical constraints of the library live.
The binary protocol and the compact protocol are both available in a Python and a Cython variant. The JSON protocol and the Apache JSON protocol are available in Python only. On the transport side, the buffered transport and the framed transport both have Python and Cython variants, and there is an HTTP server and client, plus asyncio support.
The Python and Cython distinction is the interesting part, because it shows where the performance work went. The setup.py in the repository builds extension modules for the transport layer and the binary protocol, with cythonize calls on thriftpy2/transport/cybase.pyx, the transport glob and thriftpy2/protocol/cybin/cybin.pyx, and the resulting Extensions cover cybase, the buffered, memory and framed transports and the SASL transport, plus cybin.
The setup script is careful about when it does that work. It detects PyPy by checking for __pypy__ in sys.modules and only builds the extensions when it is not PyPy, since a C extension on PyPy is a different proposition. It also detects the platform, and on Windows it appends Ws2_32 to the libraries list for the socket-dependent modules. The build-system section in pyproject.toml requires setuptools and Cython, with Cython constrained to at least 0.28.4 and below 4.
So the accurate description of the project is a pure Python implementation with optional compiled accelerators for the hot transport and binary protocol paths, which is a different claim from a fully compiled implementation and a different one from pure Python with no C at all.
Gunicorn workers, and why a single process is not enough
The readme's deployment advice is specific: running under gunicorn is the recommended way to serve thriftpy2 in production.
The reasoning is stated plainly. A single Python process is limited to one core, while gunicorn forks multiple worker processes to use all of them, and brings mature process management on top, such as graceful shutdown and reload, automatic restart of crashed or leaking workers and adjusting the worker count at runtime. thriftpy2.contrib.gunicorn provides the worker classes.
The application side uses a processor rather than a server, exposed in a module so gunicorn can import it:
# app.py import thriftpy2 from thriftpy2.thrift import TProcessor
pingpong_thrift = thriftpy2.load("pingpong.thrift", module_name="pingpong_thrift")
class Dispatcher(object): def ping(self): return "pong"
app = TProcessor(pingpong_thrift.PingPong, Dispatcher())
and it is started with a worker class that names the Thrift server rather than a plain WSGI app:
gunicorn -k thriftpy2.contrib.gunicorn.ThriftSyncWorker -w 4 -b 127.0.0.1:6000 app:app
Two worker classes exist and they behave differently. ThriftSyncWorker handles one connection at a time per worker process. ThriftAsyncWorker serves a TAsyncProcessor with async handlers on an asyncio event loop and handles many connections per process. Idle connections are closed after the keep-alive seconds, and max-requests counts thrift calls.
The protocol and transport default to binary over buffered, and to use others you subclass a worker and override proto_factory and trans_factory, which can be done in the gunicorn config file itself:
# gunicorn.conf.py from thriftpy2.contrib.gunicorn import ThriftSyncWorker from thriftpy2.protocol import TCompactProtocolFactory from thriftpy2.transport import TFramedTransportFactory
class Worker(ThriftSyncWorker): proto_factory = TCompactProtocolFactory() trans_factory = TFramedTransportFactory()
worker_class = Worker
That is a nice piece of design. Rather than passing options through a wrapper, the configuration is a subclass, and the whole file is normal Python.
Migrating from the original ThriftPy, and the state of the project
The predecessor project, ThriftPy, has been deprecated. The readme's migration note is that ThriftPy2 is fully compatible and you just change your import:
import thriftpy2 as thriftpy
Aliasing the module name to the old one is a pragmatic choice, though it does mean the old name persists in your codebase, and a reader looking at the imports will not immediately see which library is in use.
The development workflow is documented in four steps: fork the repo and make changes, write a test that shows a bug was fixed or the feature works as expected, make sure the tox tests succeed, and send a pull request. The repository has a tox.ini, a Makefile and a basedpyright configuration with typeCheckingMode set to standard, pythonVersion 3.10 and a baseline file for existing errors.
The version history is short and recent, with v0.7.0 from 2026-08-09, the alpha v0.7.0a1 from 2026-07-28 and v0.6.0 from 2026-02-08. The package version in the project metadata is 0.7.0, and the development status classifier is still 4 - Beta.
The last push to the repository was on 2026-09-26, and the project has no tagged releases beyond those version numbers. A library that parses your schema at run time and generates your stubs in memory is one where a version bump can change behaviour in a way a compiled stub would have frozen at build time, so pinning matters more here than it does for a typical dependency.
Editorial conclusion
ThriftPy2 fits a Python service that has to interoperate with an existing Thrift deployment and wants to skip the compiler and the generated stubs, because the IDL is parsed and the modules built at run time, and because the pure Python path means no build toolchain in your image. It is the wrong choice if you need the throughput of the official C++ implementation, and it is the wrong choice for a schema-first workflow where generated code is committed and reviewed. Before adopting it, check that your IDL parses under its own parser rather than the official compiler, and note that the last push was on 2026-09-26 with version 0.7.0 released on 2026-08-09 and the project still marked as beta.
Frequently asked questions
How do I install ThriftPy2?
Run pip install thriftpy2. The library requires Python 3.10 or newer and also supports PyPy3, and it needs no separate Thrift compiler or thrift package installation.
How do I create a Thrift client and server with ThriftPy2?
Load the IDL with thriftpy2.load, then use make_client and make_server from thriftpy2.rpc with the service class from the loaded module and a Dispatcher object holding one method per service function. The asyncio equivalents are make_aio_client and make_aio_server, and the readme shows both variants side by side.
Which protocols and transports does ThriftPy2 support?
The binary protocol and the compact protocol in Python and Cython variants, the JSON protocol and the Apache JSON protocol, the buffered and framed transports in Python and Cython variants, an HTTP server and client, and asyncio support. The protocol and transport default to binary over buffered.
How do I serve ThriftPy2 in production?
The readme recommends gunicorn, because a single Python process is limited to one core. The package ships worker classes in thriftpy2.contrib.gunicorn, ThriftSyncWorker for one connection per worker and ThriftAsyncWorker for many connections per process on an asyncio event loop, and you can switch protocol and transport by subclassing a worker and overriding proto_factory and trans_factory.
What licence is ThriftPy2 released under?
MIT. The last push to the repository was on 2026-09-26, version 0.7.0 was released on 2026-08-09 after the v0.7.0a1 alpha from 2026-07-28, and the package is still classified as beta in its metadata.
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/thriftpy-thriftpy2)Was this useful?