Skip to main content

A modern, fast ORM for Python.

Project description

iceaxe

Iceaxe Logo

Python Version Test status

A modern, fast ORM for Python. We have the following goals:

  • 🏎️ Performance: We want to exceed or match the fastest ORMs in Python. We want our ORM to be as close as possible to raw-asyncpg speeds. See the "Benchmarks" section for more.
  • 📝 Typehinting: Everything should be typehinted with expected types. Declare your data as you expect in Python and it should bidirectionally sync to the database.
  • 🐘 Postgres only: Leverage native Postgres features and simplify the implementation.
  • Common things are easy, rare things are possible: 99% of the SQL queries we write are vanilla SELECT/INSERT/UPDATEs. These should be natively supported by your ORM. If you're writing really complex queries, these are better done by hand so you can see exactly what SQL will be run.

Iceaxe is used in production at several companies. It's also an independent project. It's compatible with the Mountaineer ecosystem, but you can use it in whatever project and web framework you're using.

For comprehensive documentation, visit https://iceaxe.sh.

To auto-optimize your self hosted Postgres install, check out our new autopg project.

Installation

If you're using poetry to manage your dependencies:

uv add iceaxe

Otherwise install with pip:

pip install iceaxe

Usage

Define your models as a TableBase subclass:

from iceaxe import TableBase

class Person(TableBase):
    id: int
    name: str
    age: int

TableBase is a subclass of Pydantic's BaseModel, so you get all of the validation and Field customization out of the box. We provide our own Field constructor that adds database-specific configuration. For instance, to make the id field a primary key / auto-incrementing you can do:

from iceaxe import Field

class Person(TableBase):
    id: int = Field(primary_key=True)
    name: str
    age: int

Okay now you have a model. How do you interact with it?

Databases are based on a few core primitives to insert data, update it, and fetch it out again. To do so you'll need a database connection, which is a connection over the network from your code to your Postgres database. The DBConnection is the core class for all ORM actions against the database.

from iceaxe import DBConnection
import asyncpg

conn = DBConnection(
    await asyncpg.connect(
        host="localhost",
        port=5432,
        user="db_user",
        password="yoursecretpassword",
        database="your_db",
    )
)

The Person class currently just lives in memory. To back it with a full database table, we can run raw SQL or run a migration to add it:

await conn.conn.execute(
    """
    CREATE TABLE IF NOT EXISTS person (
        id SERIAL PRIMARY KEY,
        name TEXT NOT NULL,
        age INT NOT NULL
    )
    """
)

Inserting Data

Instantiate object classes as you normally do:

people = [
    Person(name="Alice", age=30),
    Person(name="Bob", age=40),
    Person(name="Charlie", age=50),
]
await conn.insert(people)

print(people[0].id) # 1
print(people[1].id) # 2

Because we're using an auto-incrementing primary key, the id field will be populated after the insert. Iceaxe will automatically update the object in place with the newly assigned value.

Updating data

Now that we have these lovely people, let's modify them.

person = people[0]
person.name = "Blice"

Right now, we have a Python object that's out of state with the database. But that's often okay. We can inspect it and further write logic - it's fully decoupled from the database.

def ensure_b_letter(person: Person):
    if person.name[0].lower() != "b":
        raise ValueError("Name must start with 'B'")

ensure_b_letter(person)

To sync the values back to the database, we can call update:

await conn.update([person])

If we were to query the database directly, we see that the name has been updated:

id | name  | age
----+-------+-----
  1 | Blice |  31
  2 | Bob   |  40
  3 | Charlie | 50

But no other fields have been touched. This lets a potentially concurrent process modify Alice's record - say, updating the age to 31. By the time we update the data, we'll change the name but nothing else. Under the hood we do this by tracking the fields that have been modified in-memory and creating a targeted UPDATE to modify only those values.

Selecting data

To select data, we can use a QueryBuilder. For a shortcut to select query functions, you can also just import select directly. This method takes the desired value parameters and returns a list of the desired objects.

from iceaxe import select

query = select(Person).where(Person.name == "Blice", Person.age > 25)
results = await conn.exec(query)

If we inspect the typing of results, we see that it's a list[Person] objects. This matches the typehint of the select function. You can also target columns directly:

query = select((Person.id, Person.name)).where(Person.age > 25)
results = await conn.exec(query)

This will return a list of tuples, where each tuple is the id and name of the person: list[tuple[int, str]].

We support most of the common SQL operations. Just like the results, these are typehinted to their proper types as well. Static typecheckers and your IDE will throw an error if you try to compare a string column to an integer, for instance. A more complex example of a query:

query = select((
    Person.id,
    FavoriteColor,
)).join(
    FavoriteColor,
    Person.id == FavoriteColor.person_id,
).where(
    Person.age > 25,
    Person.name == "Blice",
).order_by(
    Person.age.desc(),
).limit(10)
results = await conn.exec(query)

As expected this will deliver results - and typehint - as a list[tuple[int, FavoriteColor]]

Production

Note that underlying Postgres connection wrapped by conn will be alive for as long as your object is in memory. This uses up one of the allowable connections to your database. Your overall limit depends on your Postgres configuration or hosting provider, but most managed solutions top out around 150-300. If you need more concurrent clients connected (and even if you don't - connection creation at the Postgres level is expensive), you can adopt a load balancer like pgbouncer to better scale to traffic. More deployment notes to come.

It's also worth noting the absence of request pooling in this initialization. This is a feature of many ORMs that lets you limit the overall connections you make to Postgres, and re-use these over time. We specifically don't offer request pooling as part of Iceaxe, despite being supported by our underlying engine asyncpg. This is a bit more aligned to how things should be structured in production. Python apps are always bound to one process thanks to the GIL. So no matter what your connection pool will always be tied to the current Python process / runtime. When you're deploying onto a server with multiple cores, the pool will be duplicated across CPUs and largely defeats the purpose of capping network connections in the first place.

Benchmarking

We have basic benchmarking tests in the __tests__/benchmarks directory. To run them, you'll need to execute the pytest suite:

uv run pytest -m integration_tests

Current benchmarking as of October 11 2024 is:

raw asyncpg iceaxe external overhead
TableBase columns 0.098s 0.093s
TableBase full 0.164s 1.345s 10%: dict construction 90%: pydantic overhead

Development

If you update your Cython implementation during development, you'll need to re-compile the Cython code. This can be done with a simple uv sync.

uv sync

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

iceaxe-0.8.5.tar.gz (213.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

iceaxe-0.8.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (285.4 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

iceaxe-0.8.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl (282.4 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ ARM64

iceaxe-0.8.5-cp313-cp313-macosx_11_0_arm64.whl (278.7 kB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

iceaxe-0.8.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (285.4 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

iceaxe-0.8.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl (282.4 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ ARM64

iceaxe-0.8.5-cp312-cp312-macosx_11_0_arm64.whl (278.8 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

iceaxe-0.8.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (280.7 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

iceaxe-0.8.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl (278.0 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ ARM64

iceaxe-0.8.5-cp311-cp311-macosx_11_0_arm64.whl (274.5 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

File details

Details for the file iceaxe-0.8.5.tar.gz.

File metadata

  • Download URL: iceaxe-0.8.5.tar.gz
  • Upload date:
  • Size: 213.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for iceaxe-0.8.5.tar.gz
Algorithm Hash digest
SHA256 981d5fdc0ba1280c19d1b0498149bdacbdc3c5ccd2a4fe5bd75923aefc8368be
MD5 9b4dde6d4663aefddf8912e2cf629e17
BLAKE2b-256 d0301d4e313323e08d200053dc6ddd6866d79c5e43fa89b5fd97f72231b8cf72

See more details on using hashes here.

Provenance

The following attestation bundles were made for iceaxe-0.8.5.tar.gz:

Publisher: test.yml on piercefreeman/iceaxe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iceaxe-0.8.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for iceaxe-0.8.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 97c93a62abc0fefa8784807d41f18189db4834c69138b5ccd3a6b6de1a8352ca
MD5 9d63686c76e4676b1c6968fa0fc279d6
BLAKE2b-256 23cd9a599dc2a7135cbc981921e9be012f205235b5f0343cbf125621e89fb412

See more details on using hashes here.

Provenance

The following attestation bundles were made for iceaxe-0.8.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl:

Publisher: test.yml on piercefreeman/iceaxe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iceaxe-0.8.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl.

File metadata

File hashes

Hashes for iceaxe-0.8.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 f3d0c90f1ffe9bd93b4282147f08497d8c1d3ff91a1cb14c8de233b65c7f4fce
MD5 4b9c97369060a61f84acd2082472d0ae
BLAKE2b-256 3d06819e76cccade1c1c924d09752acb64c89ea0247c429fb25ef4383faffad8

See more details on using hashes here.

Provenance

The following attestation bundles were made for iceaxe-0.8.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl:

Publisher: test.yml on piercefreeman/iceaxe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iceaxe-0.8.5-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for iceaxe-0.8.5-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 0da4932a1c61acff5c14a840f64e7976970c7eef3c18e8f5fb053aa78e6d8e73
MD5 57c666f019646dfdec9106984bda78b2
BLAKE2b-256 c1b2c31139d7fd884ec4da8db588460a8cfced0896d7fc1a552ea1ef7670d93e

See more details on using hashes here.

Provenance

The following attestation bundles were made for iceaxe-0.8.5-cp313-cp313-macosx_11_0_arm64.whl:

Publisher: test.yml on piercefreeman/iceaxe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iceaxe-0.8.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for iceaxe-0.8.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 f4c29fc3547c310283ebcc59bdd2aa6919e2be08e67b1adac51875961e15738c
MD5 10311cdebc33dd2904006fb07bcca6d6
BLAKE2b-256 2ce80dfbf62cfb824c7be36324533f06a5bb5888580857d83cd1f428fac2c5d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for iceaxe-0.8.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl:

Publisher: test.yml on piercefreeman/iceaxe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iceaxe-0.8.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl.

File metadata

File hashes

Hashes for iceaxe-0.8.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 b15946fabe7d021cc33ccb276f1f6f56ef9758356c3308b16ebbc6c69e456bce
MD5 4dc36f2fce9887bb2f6344d6a9b5c654
BLAKE2b-256 8a2aa90aeed0ccfe707f091cd670f8a7ade6edd6569d5a518c73ab163d79c717

See more details on using hashes here.

Provenance

The following attestation bundles were made for iceaxe-0.8.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl:

Publisher: test.yml on piercefreeman/iceaxe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iceaxe-0.8.5-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for iceaxe-0.8.5-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8c7887895135530408b0372258adbbb1bb7afafc665eb4ecf135dfec5b2b63d2
MD5 924c195c581e57b5d921c691640c45a0
BLAKE2b-256 51f1d0fed91a61fc560733eaa09373eb2340dd35b01a84deee91c5f490069cb2

See more details on using hashes here.

Provenance

The following attestation bundles were made for iceaxe-0.8.5-cp312-cp312-macosx_11_0_arm64.whl:

Publisher: test.yml on piercefreeman/iceaxe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iceaxe-0.8.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for iceaxe-0.8.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 6bafa5f1170cdab2300e4934679b8ec06d191fae1499731cc916ef6e6c82baf2
MD5 69137ffd427551d63fba62c80d6d01e4
BLAKE2b-256 22819bf8fdf7a49d0c400efea2fc6f9043b7c5f98e0854dfd2de71726ccf4cc0

See more details on using hashes here.

Provenance

The following attestation bundles were made for iceaxe-0.8.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl:

Publisher: test.yml on piercefreeman/iceaxe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iceaxe-0.8.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl.

File metadata

File hashes

Hashes for iceaxe-0.8.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 fdc9c166239d7007331b886f493c4dbb12bacc6c0680880ed1a1981f0383b1e7
MD5 726c6238f9c683c7654cae28f25f46e8
BLAKE2b-256 b2109b6f63baa11ec15e64e1238a5a8b91c198305184d5596456b7afa9081b08

See more details on using hashes here.

Provenance

The following attestation bundles were made for iceaxe-0.8.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl:

Publisher: test.yml on piercefreeman/iceaxe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iceaxe-0.8.5-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for iceaxe-0.8.5-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 22bc775bcba80952b8d5b46de4380e825c80eb6ba52742186045a5bb2d83a086
MD5 ffca58854fe5e6602b46616ad4e7533f
BLAKE2b-256 61f775eec9b247012e73a959758ff7e7b9dc65fcb8a367b7b2f0161ac90d615b

See more details on using hashes here.

Provenance

The following attestation bundles were made for iceaxe-0.8.5-cp311-cp311-macosx_11_0_arm64.whl:

Publisher: test.yml on piercefreeman/iceaxe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page