Library / SDK
reagento/adaptix avatar
reagento/adaptix

adaptix: a Python library for loading, dumping and converting between models

An extremely flexible and configurable data model conversion library.

617 stars38 forksPythonApache-2.0

At a glance

What is it?
adaptix is a configurable data model conversion library for Python, covering deserialization into dataclasses, TypedDicts, NamedTuples, attrs, SQLAlchemy, pydantic and msgspec models, plus model to model conversion through a separate API. The 3.0 line is still on a beta tag, so the project tells you to pin a version.
Who is it for?
adaptix suits a Python team that wants one conversion layer across several model kinds, because the loader and dumper work with dataclasses, TypedDicts, NamedTuples, attrs, SQLAlchemy, pydantic and msgspec, and because the Retort keeps the conversion rules separate from the model definition. It does not suit a team that needs a stable API today, because the 3.0 line is a beta tag and the README asks you to pin a specific version.
Can I use it commercially?
Yes. Apache-2.0 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 received new commits within the last day.
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

One Retort, many model kinds, and rules kept out of the model

adaptix is described as an extremely flexible and configurable data model conversion library, and the design decision that makes it different from a hand-written from_dict is the separation of model definition from conversion rules.

The README makes that its second listed advantage: separated model definition and rules of conversion that allow preserving SRP and have different representations for one model. So a dataclass stays a plain dataclass with plain annotations, and everything about how it maps to and from a dict lives in configuration you control elsewhere.

The central object is the Retort, which the README describes as meant to be a global constant or a one-time created object. It holds the configuration and performs the work.

python
from dataclasses import dataclass

from adaptix import Retort


@dataclass
class Book:
    title: str
    price: int


data = {
    "title": "Fahrenheit 451",
    "price": 100,
}

# Retort is meant to be global constant or just one-time created
retort = Retort()

book = retort.load(data, Book)
assert book == Book(title="Fahrenheit 451", price=100)
assert retort.dump(book) == data

The default configuration needs nothing at all for that case, which the README lists as its first advantage: sane defaults for JSON processing, no configuration is needed for simple cases.

The other structural point is that there is no forced model representation. The library works with @dataclass, TypedDict, NamedTuple, attrs, sqlalchemy, pydantic and msgspec models, and the README describes that as dozens of types in the specific types behaviour documentation. If your codebase has a mix of dataclasses and pydantic models in different corners, that mix is the case this library is built for.

The primary language is Python, the licence is Apache-2.0, and the last push was on 2026-10-20.

Errors carry a path and can be serialised

Two features here are the ones that decide whether a validation library is usable behind an API, and adaptix lists them separately.

The first is saving path where an exception is raised, including unexpected errors. So when a payload fails to load, the exception knows which field failed, not just that something did.

The second is machine-readable errors that could be dumped. This is the part that matters for an API. A validation error you can turn into a JSON body is a validation error your client can act on. A validation error that only produces a traceback is a validation error your client sees as a 500.

Combined, they mean the failure case is a designed output rather than an accident. The README links both features from the same tutorial page on error handling, which suggests they are meant to be read together.

The rest of the loading behaviour is shaped by one negative default, and it is the one that will surprise people coming from pydantic. There is no auto casting by default, and the loader does not try to guess value from plenty of input formats.

That is a deliberate position. A loader that guesses is convenient until it guesses wrong, and a wrong guess is a data integrity bug rather than an error. Disabling additional checks to speed up data loading from trusted sources is offered as the escape hatch for the case where the input really is trusted, so the strictness is configurable rather than fixed.

The other behaviours are convenience features that are off or silent by default: automatic name style conversion such as snake_case to camelCase, and the predicate system that allows concisely and precisely overriding some behaviour. The predicate system is the mechanism behind most of the configuration in the documentation, and it is what makes it possible to write a rule that applies to a subset of fields without hand-writing a loader for each.

Model to model conversion is a separate API with its own entry point

The second half of the library is not loading and dumping. It is converting one model into another, and it lives under a different import path.

python
from dataclasses import dataclass

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

from adaptix.conversion import get_converter


class Base(DeclarativeBase):
    pass


class Book(Base):
    __tablename__ = 'books'

    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str]
    price: Mapped[int]


@dataclass
class BookDTO:
    id: int
    title: str
    price: int


convert_book_to_dto = get_converter(Book, BookDTO)

assert (
    convert_book_to_dto(Book(id=183, title="Fahrenheit 451", price=100))
    ==
    BookDTO(id=183, title="Fahrenheit 451", price=100)
)

get_converter takes the source type and the target type and returns a callable. The SQLAlchemy model is the source and a plain dataclass is the target, which is the classic ORM entity to DTO case.

The notable part is that the converter is produced once, at import time, and then reused. That is the same reasoning as the Retort being a global constant: conversion configuration is built once and then is cheap to use.

The design does mean the two halves share an idea but not an interface. Loading and dumping goes through the Retort, while conversion goes through get_converter and a distinct module path, adaptix.conversion. If you are mixing them in one codebase you will be importing from two places, which is a small amount of friction worth knowing about.

The use cases the README lists explain why both halves exist. Validation and transformation of received data for your API. Conversion between data models and DTOs. Config loading and dumping via a codec that produces or takes a dict. Storing JSON in a database and representing it as a model inside the application code. Creating API clients that convert a model to JSON to send to the server. Persisting entities at cache storage. And implementing fast and primitive ORM.

Model library support is an optional extra, with a strict variant

The optional dependency list in the package metadata is where the integration surface is defined, and it has a shape worth understanding.

There are eight extras, four of them in pairs. The plain ones are attrs at 21.3.0 or newer, sqlalchemy at 2.0.0 or newer, pydantic at 2.1.0 or newer and msgspec at 0.18.5 or newer. The strict ones are the same libraries with an upper bound: attrs-strict caps at 24.2.0, sqlalchemy-strict at 2.0.46, pydantic-strict at 2.12.5 and msgspec-strict at 0.20.0.

So you choose between a wide version range and a narrow one. The strict extras exist for the case where the library's behaviour against a new release of your model library is not something you want to discover in production, and the upper bounds are exactly the versions the author had tested.

The core dependency list is almost empty. The only runtime dependency is exceptiongroup at 1.1.3 or newer, and only on Python below 3.11, where it provides the backport of the exception group feature. Otherwise adaptix has no runtime dependencies at all. The model libraries are not needed unless you use them.

The build system is uv_build at 0.8.22, the Python requirement is 3.10 or newer, and the classifiers cover 3.10 through 3.14, CPython and PyPy, and the Development Status is Alpha. That last classifier is worth holding in mind: it matches the beta tag, and it is the packaging metadata telling you the same thing the README does.

The README's own note on the beta status is blunt. Adaptix is ready for production, but the beta version only means there may be some backward incompatible changes, so you need to pin a specific version.

That is a slightly contradictory pair of statements, and the resolution is that the library is considered usable while the API contract is not frozen. Which is fine for an application, and less fine for a library that other people depend on.

The developer workflow runs on just, tox, uv and invoke

The justfile at the repository root shows how the project is developed, and the tooling is a good indicator of how much machinery is involved.

The bootstrap target prepares the environment.

bash
@bootstrap:
    pip install -r requirements/pre.txt
    uv pip install -e .
    uv pip install -r requirements/dev.txt
    prek
    prek install

There are separate requirement files, pre.txt, dev.txt and runner.txt, and the package is installed in editable mode with uv. The prek command is the pre-commit hook runner, and it is invoked both to run the hooks and to install them.

The test targets are driven by tox, and the just file computes the environments rather than listing them. The basic test target runs tox over the environments whose names match py and end in new, so the fast subset runs on every change, while test-all-seq runs every Python environment in reverse order and test-all runs them in parallel with -p auto.

Coverage is generated with invoke rather than tox directly.

bash
@cov output='coverage.xml':
    {{ inv }} cov \
      --env-list $(tox list --no-desc | grep -e '^py' | grep -v '^pypy' | sort -r | tr '\n' ',') \
      --output {{ output }} \
      --parallel

Note that pypy is excluded from the coverage list, which means PyPy is in the test matrix but not in the measured one.

Dependencies are compiled with pip-tools through an invoke task, with a separate upgrade variant. Documentation is built with sphinx-build into a docs-build directory, and the changelog is managed with towncrier, with a default version of Preview and the --keep flag so unreleased fragments are not consumed.

There is a benchmarks/ directory at the root, which is where the speed claim in the README comes from. The README calls adaptix one of the fastest data parsing and serialization libraries. Treat that as a claim to check in the benchmark directory rather than a number to quote, since the project publishes its own measurements and the comparison set is not specified in the README.

Where adaptix is the wrong tool

The first wrong-tool case is a codebase that wants one object model and a single library for it. If your models are all dataclasses and you want simple loading, the standard library plus a few hand-written from_dict methods is a smaller dependency than a framework with a predicate system, a Retort and eight optional extras. The value of adaptix shows up when the models are heterogeneous or the rules are complicated; on a small uniform project it is more machinery than the problem needs.

The second case is a team that wants pydantic's model, because pydantic does validation and coercion and provides a model object with validators, while adaptix deliberately does not auto cast and treats loading as a separate concern from the model definition. If your team has standardised on pydantic v2, adaptix is an alternative to it rather than a companion to it, and the pydantic extra here is for loading data into existing pydantic models rather than for replacing their validation layer.

The third case is a team unwilling to pin versions. The README says it plainly: the beta version means there may be backward incompatible changes, so you need to pin a specific version. A service that installs its dependencies fresh on every deploy is one release away from a breaking change in a beta, and the development status classifier of Alpha is consistent with that.

The fourth case is a project that wants no dependencies at all in a strict environment. adaptix itself has one conditional dependency, so this is only a problem if you also want the model library extras, which is most of the point.

On licence, Apache-2.0 is permissive and applies to the library without the copyleft obligations that would complicate distribution. The extras pull in other libraries with their own licences, attrs, SQLAlchemy, pydantic and msgspec, so a compliance review has to follow those as well, and the strict extras exist partly so you can hold the model library at a version you have already assessed.

The examples directory is where you should look before committing. There are api_division, real_world_app and simple_api_processing directories, which cover the range from a single endpoint to a full application layout, and each of those is a better guide to the library's idioms than any feature list.

Editorial conclusion

adaptix suits a Python team that wants one conversion layer across several model kinds, because the loader and dumper work with dataclasses, TypedDicts, NamedTuples, attrs, SQLAlchemy, pydantic and msgspec, and because the Retort keeps the conversion rules separate from the model definition. It does not suit a team that needs a stable API today, because the 3.0 line is a beta tag and the README asks you to pin a specific version. Before adopting, install the extra for whichever model library you use, since sqlalchemy, pydantic, msgspec and attrs support are all optional extras, and read the error handling documentation, because the path in the exception and the machine-readable error payload are the two features that most affect what your API returns to a client.

Frequently asked questions

What is adaptix?

adaptix is a configurable data model conversion library for Python. It provides loading and dumping between dicts and models, and a separate conversion API for turning one model into another, with the conversion rules kept separate from the model definition itself.

How do I install adaptix and load data into a model?

Install the pinned release with pip install adaptix==3.0.0b12, then create a Retort and call retort.load(data, Book) to build the model, or retort.dump(book) to turn it back into a dict. The Retort is meant to be a global constant or created once.

How do I convert between two models with adaptix?

Import get_converter from adaptix.conversion and call it with the source and target types, for example convert_book_to_dto = get_converter(Book, BookDTO). The returned callable converts instances, and it is built once at import time.

Which model libraries does adaptix support?

It works with @dataclass, TypedDict, NamedTuple, attrs, sqlalchemy, pydantic and msgspec models. Each of attrs, sqlalchemy, pydantic and msgspec is an optional extra with a version range, and there is a matching strict extra with an upper bound for each of those four.

Does adaptix cast values automatically?

No. The README states there is no auto casting by default and that the loader does not try to guess a value from plenty of input formats. Additional checks can be disabled to speed up loading from trusted sources.

What license is adaptix released under?

adaptix is licensed under Apache-2.0, with the LICENSE file at the root of the repository. The current release is 3.0.0b12, and the README asks users to pin a specific version because backward incompatible changes may still occur in the beta line.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. reagento/adaptix on GitHub
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/reagento-adaptix.svg)](https://hysenlabs.com/projects/reagento-adaptix)

Was this useful?