Skip to main content

🐠 Danio

UnitTest Status PyPI version Codecov coverage License Python Support

Danio is an elegant, lightweight, and asynchronous ORM for Python. It bridges Python's standard dataclasses with encode's robust databases library, bringing a highly expressive, type-safe, and asynchronous interface to your database operations.

Designed with modern developers in mind, Danio aims to make database interaction simple, clean, and robust against common pitfalls like Out Of Memory (OOM) errors and runtime type mismatches.


✨ Features

  • ⚡ Async First: Built from the ground up for asyncio using standard async/await paradigms.
  • 📦 Dataclasses Core: Leverages Python's native dataclasses for clean, lightweight model schemas.
  • 🛡️ Type-Safety: 100% type hints everywhere. Auto-generated stubs (Danio Hints) ensure flawless IDE autocompletion.
  • 🧠 Prevent Out-Of-Memory: Designed with memory limits in mind, allowing easy customization of Fields and Model serialization.
  • 🚀 Comprehensive CRUD: Simple query-chaining, transaction support, connection-pooling, and table locks.
  • 🔔 Lifecycle Signals: Built-in hooks like before_create, after_create, before_update, etc.
  • 🔋 Advanced DB Operations: Bulk creation, bulk deletion, atomic updates, and seamless UPSERT / get_or_create.
  • 🚧 Schema Migrations: Practical helper scripts to assist with standard SQL table schema migrations.
  • 🔌 Multi-Engine Support: Out-of-the-box support for MySQL, Postgres, and SQLite.

⚙️ Installation

Install via pip:

pip install danio

Note: Depending on your database of choice, you should install corresponding async drivers (e.g., aiomysql, asyncpg, or aiosqlite).


📖 Documentation

Full documentation, guides, and tutorials can be found at: 👉 Danio Documentation Site


⚡ Glance

import dataclasses
import enum
import typing
import datetime
import danio

# 1. Establish Database Connection
db = danio.Database(
    "mysql://root:***@server:3306/test",
    maxsize=3,
    charset="utf8mb4",
    use_unicode=True,
    connect_timeout=60,
)

# 2. Define Model using Python Type-Hints and PEP 593 Annotated Fields
@danio.model
class User(danio.Model):
    # Auto-generated and maintained by danio:
    # --------------------Danio Hints--------------------
    # TABLE NAME: user
    # TABLE IS MIGRATED!
    ID: typing.ClassVar[danio.Field]          # "id" serial PRIMARY KEY NOT NULL
    NAME: typing.ClassVar[danio.Field]        # "name" varchar(255)  NOT NULL
    AGE: typing.ClassVar[danio.Field]         # "age" int  NOT NULL
    CREATED_AT: typing.ClassVar[danio.Field]  # "created_at" timestamp  NOT NULL
    UPDATED_AT: typing.ClassVar[danio.Field]  # "updated_at" timestamp  NOT NULL
    GENDER: typing.ClassVar[danio.Field]      # "gender" int  NOT NULL
    # --------------------Danio Hints--------------------

    class Gender(enum.Enum):
        MALE = 0
        FEMALE = 1
        OTHER = 2

    id: typing.Annotated[int, danio.IntField(primary=True, type="serial")] = 0
    name: typing.Annotated[str, danio.CharField(comment="User name")] = ""
    age: typing.Annotated[int, danio.IntField] = 0
    created_at: typing.Annotated[
        datetime.datetime,
        danio.DateTimeField(type="timestamp without time zone", comment="Created time"),
    ] = dataclasses.field(default_factory=datetime.datetime.now)
    updated_at: typing.Annotated[
        datetime.datetime,
        danio.DateTimeField(type="timestamp without time zone", comment="Updated time"),
    ] = dataclasses.field(default_factory=datetime.datetime.now)
    gender: typing.Annotated[Gender, danio.IntField(enum=Gender)] = Gender.MALE

    # Lifecycle Hooks
    async def before_update(self, validate=True):
        self.updated_at = datetime.datetime.now()
        await super().before_update(validate=True)

    async def validate(self):
        await super().validate()
        if not self.name:
            raise danio.ValidateException("Name cannot be empty!")

    @classmethod
    def get_database(cls, operation: danio.Operation, table: str, *args, **kwargs) -> danio.Database:
        return db

# 3. Perform Asynchronous CRUD Operations
async def main():
    # ---- Create ----
    user = await User(name="batman", age=30).save()
    print(f"Created user ID: {user.id}")

    # ---- Read (Type-safe Queries) ----
    user = await User.where(User.NAME == "batman").fetch_one()
    
    # ---- Update ----
    user.gender = User.Gender.MALE
    await user.save()

    # ---- Advanced Query Chaining ----
    active_users = await User.where(User.AGE > 18).limit(10).order_by(User.NAME).fetch_all()

    # ---- Complex Expression Updates ----
    # Increment all matching users' ages atomically
    await User.where(User.ID == 1).update(age=(User.AGE + 1))

    # ---- Bulk Operations ----
    users_to_create = [User(name=f"user_{i}", age=20) for i in range(10)]
    await User.bulk_create(users_to_create)

    # ---- Upsert (Create or Update) ----
    user, created, updated = await User(id=1, name="updated_name").create_or_update(
        key_fields=(User.ID,)
    )

🛠️ Development and Formatting

We use Ruff for extremely fast linting and formatting.

To format and check your code quality before submitting a PR:

# Check code style and run static checks
make lint

# Automatically format and fix lint errors
make format

# Run full tests across your local SQLite/Postgres/MySQL setup
make test

📄 License

Danio is open-sourced software licensed under the BSD 3-Clause License.

Download files

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

Source Distribution

danio-0.6.0.tar.gz (33.4 kB view details)

Uploaded Source

Built Distribution

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

danio-0.6.0-py3-none-any.whl (24.3 kB view details)

Uploaded Python 3

File details

Details for the file danio-0.6.0.tar.gz.

File metadata

  • Download URL: danio-0.6.0.tar.gz
  • Upload date:
  • Size: 33.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for danio-0.6.0.tar.gz
Algorithm Hash digest
SHA256 865fd4505a598ad20c5472a1e2480cf632558b1ec9efb9f0744f59ed79958002
MD5 36296fad11503dd28620722acf58c2c4
BLAKE2b-256 f0b2b9e3b361983d560f1c9a6b5a991324f33f978113cc519c94b88741ff9b7f

See more details on using hashes here.

Provenance

The following attestation bundles were made for danio-0.6.0.tar.gz:

Publisher: test-and-release.yml on strongbugman/danio

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

File details

Details for the file danio-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: danio-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 24.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for danio-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b9e43e0df4f83333ee19389f260156afc8452e98edcc8c85a70102c356a13bc2
MD5 b9ba5e80f7bea4b52cbc6db4f6767f3b
BLAKE2b-256 be677c9603070d65d396247163069fe084743f5a88e85cad3c8a652efd41f501

See more details on using hashes here.

Provenance

The following attestation bundles were made for danio-0.6.0-py3-none-any.whl:

Publisher: test-and-release.yml on strongbugman/danio

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 Sentry Error logging StatusPage Status page