Skip to main content

A SQL-y and well-typed ORM for Python

Project description

Embar

Embar logo

A Python ORM with types


Embar is a new ORM for Python with the following goals:

  • Type safety: your type checker should know what arguments are valid, and what is being returned from any call.
  • Type hints: your LSP should be able to guide you towards the query you want to write.
  • SQL-esque: you should be able to write queries simply by knowing SQL and your data model.
  • You should be able to actually just write SQL when you need to.

These are mostly inspired by Drizzle. The Python ecosystem deserves something with similar DX.

Embar uses Template strings and so only supports Python 3.14.

Quickstart

Install

uv add embar

Set up database models

# schema.py
from embar.column.common import Integer, Text
from embar.config import TableConfig
from embar.table import Table

class User(Table):
    embar_config = TableConfig(table_name="users")

    id: Integer = Integer(primary=True)
    email: Text = Text("user_email", default="text", not_null=True)

class Message(Table):
    id: Integer = Integer()
    user_id: Integer = Integer().fk(lambda: User.id)
    content: Text = Text()

Create client and apply migrations

# main.py
import sqlite3
from embar.db.sqlite import Db as SqliteDb

conn = sqlite3.connect(":memory:")
db = SqliteDb(conn)
db.migrate([User, Message])

Insert some data

user = User(id=1, email="foo@bar.com")
message = Message(id=1, user_id=user.id, content="Hello!")

db.insert(User).values(user).run()
db.insert(Message).values(message).run()

Query some data

With join, where and group by.

from typing import Annotated
from embar.query.selection import Selection
from embar.query.where import Eq, Like, Or

class UserSel(Selection):
    id: Annotated[int, User.id]
    messages: Annotated[list[str], Message.content.many()]


users = (
    db.select(UserSel)
    .fromm(User)
    .left_join(Message, Eq(User.id, Message.user_id))
    .where(Or(
        Eq(User.id, 1),
        Like(User.email, "foo%")
    ))
    .group_by(User.id)
    .run()
)
# [ UserSel(id=1, messages=['Hello!']) ]

Query some more data

This time with fully nested child tables, and some raw SQL.

from datetime import datetime
from embar.sql import Sql

class UserHydrated(Selection):
    email: Annotated[str, User.email]
    messages: Annotated[list[Message], Message.many()]
    date: Annotated[datetime, Sql(t"CURRENT_TIMESTAMP")]


users = (
    db.select(UserHydrated)
    .fromm(User)
    .left_join(Message, Eq(User.id, Message.user_id))
    .group_by(User.id)
    .limit(2)
    .run()
)
# [UserHydrated(
#      email='foo@bar.com',
#      messages=[Message(content='Hello!', id=1, user_id=1)],
#      date: datetime(2025, 10, 26, ...)
# )]

Update a row

Unfortunately this requires another model to be defined, as Python doesn't have a Partial[] type.

from typing import TypedDict

class MessageUpdate(TypedDict, total=False):
    id: int
    user_id: int
    content: str

(
    db.update(Message)
    .set(MessageUpdate(content="Goodbye"))
    .where(Eq(Message.id, 1))
    .run()
)

Contributing

Install uv.

Then:

uv sync

This project uses poethepoet for tasks/scripts.

You'll need Docker installed to run tests.

Format, lint, type-check, test:

uv run poe fmt
           lint
           check
           test

# or
uv run poe all

Or do this:

# Run this or put it in .zshrc/.bashrc/etc
alias poe="uv run poe"

# Then you can just:
poe test

Other ORMs to co consider

There seems to be a gap in the Python ORM market.

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

embar-0.1.0.tar.gz (19.1 kB view details)

Uploaded Source

Built Distribution

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

embar-0.1.0-py3-none-any.whl (28.2 kB view details)

Uploaded Python 3

File details

Details for the file embar-0.1.0.tar.gz.

File metadata

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

File hashes

Hashes for embar-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a97897add5e0b17a1203d26e277b6ffec3eb0079779f630f2f6da02aaacb08b0
MD5 c1fdaa1d5935eed1917fef8b718af698
BLAKE2b-256 72d6719e022452297f4714f360f11c496f5ef0a96d8b4bb5eeb8af61c5866853

See more details on using hashes here.

Provenance

The following attestation bundles were made for embar-0.1.0.tar.gz:

Publisher: release.yml on carderne/embar

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

File details

Details for the file embar-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: embar-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 28.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for embar-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d6439815210e57445f72816cb4450a94b697b64af06a0fcdd11a2ccc4de9fa12
MD5 64f41465edb828e1ad1d55c20de2731e
BLAKE2b-256 a1264a535e417fc6fea476a9b135f63e2186247ec8548d6d9bdf0fdc68ca873f

See more details on using hashes here.

Provenance

The following attestation bundles were made for embar-0.1.0-py3-none-any.whl:

Publisher: release.yml on carderne/embar

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