{"id":51388330,"url":"https://github.com/vsdudakov/yara-orm","last_synced_at":"2026-07-04T22:00:30.449Z","repository":{"id":368036204,"uuid":"1283247581","full_name":"vsdudakov/yara-orm","owner":"vsdudakov","description":"Fast async Python ORM with a Rust engine — Tortoise-style models, querysets, relations and migrations for PostgreSQL, MySQL and SQLite. A faster, fully-typed alternative to Tortoise ORM and async SQLAlchemy.","archived":false,"fork":false,"pushed_at":"2026-07-03T20:42:01.000Z","size":1849,"stargazers_count":5,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-03T21:37:56.665Z","etag":null,"topics":["async","async-orm","asyncio","data-mapper","database","database-orm","high-performance","mariadb","migrations","mysql","orm","postgresql","pyo3","python","python-orm","query-builder","rust","sqlalchemy","sqlite","tortoise-orm"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/yara-orm/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vsdudakov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"docs/contributing.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-28T17:56:04.000Z","updated_at":"2026-07-03T11:21:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vsdudakov/yara-orm","commit_stats":null,"previous_names":["vsdudakov/yara-orm"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/vsdudakov/yara-orm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsdudakov%2Fyara-orm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsdudakov%2Fyara-orm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsdudakov%2Fyara-orm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsdudakov%2Fyara-orm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vsdudakov","download_url":"https://codeload.github.com/vsdudakov/yara-orm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsdudakov%2Fyara-orm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35136712,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","async-orm","asyncio","data-mapper","database","database-orm","high-performance","mariadb","migrations","mysql","orm","postgresql","pyo3","python","python-orm","query-builder","rust","sqlalchemy","sqlite","tortoise-orm"],"created_at":"2026-07-03T21:35:18.074Z","updated_at":"2026-07-04T22:00:30.404Z","avatar_url":"https://github.com/vsdudakov.png","language":"Python","funding_links":["https://github.com/sponsors/vsdudakov"],"categories":[],"sub_categories":[],"readme":"# Yara ORM\n\n**A fast, async Python ORM with a Rust engine — [Tortoise](https://tortoise.github.io/)-style\nmodels, querysets, relations and migrations for PostgreSQL, MySQL and SQLite.**\n\n[![CI](https://github.com/vsdudakov/yara-orm/actions/workflows/ci.yml/badge.svg)](https://github.com/vsdudakov/yara-orm/actions/workflows/ci.yml)\n[![PyPI](https://img.shields.io/pypi/v/yara-orm.svg)](https://pypi.org/project/yara-orm/)\n[![Python](https://img.shields.io/badge/python-3.9%E2%80%933.14-blue.svg)](https://github.com/vsdudakov/yara-orm)\n[![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](#development)\n[![Docs](https://img.shields.io/badge/docs-vsdudakov.github.io%2Fyara--orm-blue.svg)](https://vsdudakov.github.io/yara-orm/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE.md)\n[![Sponsor](https://img.shields.io/badge/sponsor-%E2%9D%A4-ec6cb9.svg?logo=github-sponsors)](https://github.com/sponsors/vsdudakov)\n\n📖 **Documentation: [vsdudakov.github.io/yara-orm](https://vsdudakov.github.io/yara-orm/)**\n\n✍️ **Deep dive: [How the GIL, PyO3 \u0026 asyncio cooperate](https://dev.to/vsdudakov/i-built-a-python-orm-with-a-rust-engine-heres-how-the-gil-pyo3-and-asyncio-actually-cooperate-4fkj)** — how the Rust engine bridges Python's event loop without the GIL collapsing it.\n\nYara ORM is a high-performance **async ORM for Python** that pairs the ergonomics\nof a Django/Tortoise-style API — models, querysets, relations, aggregation and\nmigrations — with a hot path (connection pooling, parameter binding, row decoding)\nwritten in compiled **Rust** (PyO3 + tokio). It is a drop-in-feel **alternative to\nTortoise ORM and async SQLAlchemy**: **2–9× faster** than popular pure-Python ORMs\non common operations, with first-class **PostgreSQL**, **MySQL** and **SQLite** backends, full\ntype hints, and **100% test coverage**.\n\n```python\nfrom yara_orm import Model, YaraOrm, fields\n\nclass User(Model):\n    id = fields.IntField(pk=True)\n    name = fields.CharField(max_length=120)\n\nawait YaraOrm.init(\"postgres://localhost/app\")\nawait YaraOrm.generate_schemas()\nawait User.create(name=\"Ada\")\nprint(await User.filter(name__icontains=\"ad\").count())\n```\n\n---\n\n## Highlights\n\n- ⚡ **Rust engine** — pooling, binding and decoding in compiled code; the async\n  bridge (PyO3 + tokio) keeps your event loop free.\n- 🧩 **Familiar API** — Tortoise/Django-style models, lazy chainable querysets,\n  `Q` objects, aggregation, `prefetch_related`, transactions, signals. Coming from\n  Tortoise? Most code moves across unchanged — see\n  [Migrating from Tortoise ORM](https://vsdudakov.github.io/yara-orm/guides/migrating-from-tortoise/).\n- 🗄️ **Pluggable backends** — PostgreSQL, MySQL/MariaDB and SQLite today, selected by\n  URL; a new database is one Rust trait + one Python dialect.\n- 🚚 **Migrations** — operation-based, auto-generated, backend-portable\n  (`makemigrations` / `upgrade` / `downgrade`).\n- 🧪 **Quality** — fully typed, linted (ruff + ty) and **100% test coverage**.\n\n## Installation\n\n```bash\npip install yara-orm\n```\n\nPrebuilt wheels are published for Linux, macOS and Windows on CPython 3.9–3.14,\nso installation needs **no Rust toolchain**. (Installing the source\ndistribution on an unsupported platform compiles the engine and requires a Rust\ntoolchain — see [Development](#development).)\n\n## Quick start\n\n```python\nimport asyncio\nfrom yara_orm import Model, YaraOrm, fields\n\n\nclass Tournament(Model):\n    id = fields.IntField(pk=True)\n    name = fields.CharField(max_length=100)\n    created_at = fields.DatetimeField(auto_now_add=True)\n\n\nclass Event(Model):\n    id = fields.IntField(pk=True)\n    name = fields.CharField(max_length=100, index=True)\n    tournament = fields.ForeignKeyField(\"Tournament\", related_name=\"events\")\n\n\nasync def main() -\u003e None:\n    await YaraOrm.init(\"postgres://localhost/app\")   # or \"mysql://…\", \"sqlite:///app.db\"\n    await YaraOrm.generate_schemas()\n\n    cup = await Tournament.create(name=\"World Cup\")\n    await Event.create(name=\"Final\", tournament=cup)\n\n    # Lazy, chainable queries\n    finals = await Event.filter(name__icontains=\"fin\").order_by(\"-id\")\n    count = await Event.filter(tournament=cup).count()\n\n    # Relations\n    async for event in cup.events:\n        print(event.name, \"→\", (await event.tournament).name)\n\n    await YaraOrm.close()\n\n\nasyncio.run(main())\n```\n\n## Querying\n\n```python\n# Lookups: exact, not, gt/gte/lt/lte, in, isnull, contains/icontains,\n# startswith/endswith (+ case-insensitive `i` variants)\nawait User.filter(age__gte=18, name__icontains=\"a\").order_by(\"-age\").limit(10)\n\n# Complex boolean filters with Q\nfrom yara_orm import Q\nawait User.filter(Q(name=\"Ada\") | Q(age__lt=30)).exclude(active=False)\n\n# Aggregation + group by\nfrom yara_orm import Count, Sum\nawait Author.annotate(books=Count(\"books\")).filter(books__gte=1)\nawait Book.annotate(total=Sum(\"rating\")).group_by(\"author_id\").values(\"author_id\", \"total\")\n\n# Construction-free projections\nawait User.all().values(\"id\", \"name\")\nawait User.all().values_list(\"name\", flat=True)\n```\n\nModel methods: `create`, `bulk_create`, `get`, `get_or_none`, `filter`,\n`exclude`, `all`, `annotate`, `prefetch_related`, `raw`; instances `save`,\n`delete`, `fetch_related`.\n\n## Relations\n\n```python\nclass Author(Model):\n    name = fields.CharField(max_length=100)\n\nclass Book(Model):\n    title = fields.CharField(max_length=200)\n    author = fields.ForeignKeyField(\"Author\", related_name=\"books\")\n    tags = fields.ManyToManyField(\"Tag\", related_name=\"books\")\n\nbook = await Book.create(title=\"Compilers\", author=author)\nawait book.tags.add(tag1, tag2)        # m2m add / remove / clear\n\nawait book.author                       # forward FK (awaitable)\nasync for b in author.books: ...        # reverse manager\nawait Author.all().prefetch_related(\"books\")   # no N+1\n```\n\n`ForeignKeyField`, `OneToOneField`, `ManyToManyField`, recursive self-FK,\n`related_name`, `Prefetch(rel, queryset=...)`.\n\n## Transactions, signals \u0026 more\n\n```python\nfrom yara_orm import in_transaction, atomic, pre_save, connections\n\nasync with in_transaction():            # commit on success, rollback on error\n    await Account.create(name=\"A\")\n    async with in_transaction():        # nesting opens a savepoint\n        await Account.create(name=\"B\")  # rolls back independently on error\n\n@atomic(isolation=\"SERIALIZABLE\")       # isolation levels (PostgreSQL/MySQL)\nasync def transfer(): ...\n\n@pre_save(User)                          # lifecycle signals\nasync def on_save(sender, instance, using_db, update_fields): ...\n\nawait connections.get(\"default\").execute(\"INSERT ...\", [..])   # manual SQL\n```\n\nAlso: **enum fields** (`IntEnumField`/`CharEnumField`), column/table\n**comments** (`description=`, `Meta.table_description`), and **multi-database\nrouting** via a `Router` over multiple named connections.\n\n## Backends\n\nBackends are selected by the connection URL; the abstraction is a single Rust\ntrait (`Backend`) plus a Python `BaseDialect` subclass:\n\n```python\nawait YaraOrm.init(\"postgres://user@localhost/db\")     # PostgreSQL (tokio-postgres)\nawait YaraOrm.init(\"mysql://user:pass@localhost/db\")   # MySQL/MariaDB (mysql_async)\nawait YaraOrm.init(\"sqlite:///path/to/app.db\")          # SQLite (rusqlite)\n```\n\nThe SQLite backend maps rich types (uuid/json/datetime/decimal) onto SQLite's\nstorage classes and reconstructs them on read from the declared column type, so\nthe model layer is identical across backends.\n\n## Migrations\n\nA Django/Tortoise-style, operation-based migration system. Migrations are\nauto-generated from model changes and **backend-portable** — the same operations\nrender to PostgreSQL, MySQL or SQLite DDL at apply time. Applied migrations are tracked\nin an `orm_migrations` table.\n\n```bash\n# autodetect model changes -\u003e migrations/0001_initial.py\npython -m yara_orm --models myapp.models makemigrations --name initial\n\n# preview SQL without running it (per the target dialect)\npython -m yara_orm --db sqlite:///app.db --models myapp.models sqlmigrate 0001_initial\n\n# apply / revert / inspect\npython -m yara_orm --db postgres://localhost/app --models myapp.models upgrade\npython -m yara_orm --db postgres://localhost/app --models myapp.models downgrade\npython -m yara_orm --db postgres://localhost/app --models myapp.models history\n```\n\nEach migration is a `class Migration(m.Migration)` whose `operations` are built\nfrom live field objects: `CreateModel`, `DeleteModel`, `AddField`, `RemoveField`,\n`AlterField`, `AddIndex`, `RemoveIndex`, plus hand-written renames\n(`RenameModel` / `RenameField` / `RenameIndex`), constraints (`AddConstraint` /\n`RemoveConstraint` / `RenameConstraint` with `UniqueConstraint` / `CheckConstraint`)\nand `RunSQL` / `RunPython` for data migrations. `makemigrations` emits the\nidempotent analogs (`CreateModelIfNotExists`, `AddFieldIfNotExists`, …) and detects\n`AlterField` automatically. The same commands are available programmatically via\n`yara_orm.MigrationManager`.\n\n## Performance\n\nMedian of 5 runs, Python 3.12, 5000 rows — Yara ORM is fastest (or tied) on\nevery operation measured on PostgreSQL **and MySQL**, and wins everything\nthroughput-shaped on SQLite. Cells show Yara ORM's time and each competitor's\nslowdown factor (\u003e1 means Yara ORM is faster). Full methodology and tables in\n[`benchmarks/`](benchmarks/).\n\n### PostgreSQL 18\n\n![Yara ORM vs Tortoise, SQLAlchemy and Pony on PostgreSQL — latency per operation, log scale, lower is better](docs/assets/benchmark-postgres.png)\n\n| operation     | yara-orm | vs Tortoise | vs SQLAlchemy | vs Pony |\n|---------------|---------:|------------:|--------------:|--------:|\n| bulk_insert   | 14.7 ms  | 1.6×        | 4.6×          | 14.9×   |\n| single_insert | 34.2 ms  | 2.3×        | 4.4×          |  1.8×   |\n| fetch_all     |  3.5 ms  | 4.8×        | 6.1×          |  9.8×   |\n| count         |  0.3 ms  | 1.9×        | 3.2×          |  1.5×   |\n| group_by      |  0.7 ms  | 1.6×        | 1.9×          |  3.1×   |\n| filter        |  2.2 ms  | 3.9×        | 3.5×          |  8.1×   |\n| get_by_pk     | 65.0 ms  | 3.0×        | 4.4×          |  1.3×   |\n| update        |  3.2 ms  | 1.1×        | 1.2×          | 37.3×   |\n| delete        |  0.7 ms  | 1.2×        | 1.6×          | 135.6×  |\n\n### MySQL 8.4\n\nSame workload against MySQL (Tortoise over asyncmy, SQLAlchemy over aiomysql,\nPony over pymysql):\n\n![Yara ORM vs Tortoise, SQLAlchemy and Pony on MySQL — latency per operation, log scale, lower is better](docs/assets/benchmark-mysql.png)\n\n| operation     | yara-orm  | vs Tortoise | vs SQLAlchemy | vs Pony |\n|---------------|----------:|------------:|--------------:|--------:|\n| bulk_insert   |  46.0 ms  | 1.0×        | 17.4×         |  9.4×   |\n| single_insert | 693.7 ms  | 1.1×        | 1.3×          |  1.1×   |\n| fetch_all     |   5.6 ms  | 6.0×        | 6.9×          |  8.4×   |\n| count         |   0.7 ms  | 1.4×        | 1.7×          |  1.1×   |\n| group_by      |   1.2 ms  | 1.2×        | 1.7×          |  2.0×   |\n| filter        |   3.4 ms  | 5.3×        | 4.8×          |  7.3×   |\n| get_by_pk     | 110.9 ms  | 2.1×        | 4.9×          |  2.8×   |\n| update        |   7.2 ms  | 1.1×        | 1.5×          | 32.5×   |\n| delete        |   4.9 ms  | 1.0×        | 1.1×          | 42.9×   |\n\n(`single_insert` is dominated by InnoDB's per-commit fsync — every ORM pays\nit; `get_by_pk` and `single_insert` include the Docker-network round trip.)\n\n### SQLite\n\n![Yara ORM vs Tortoise, SQLAlchemy and Pony on SQLite — latency per operation, log scale, lower is better](docs/assets/benchmark-sqlite.png)\n\nYara ORM wins everything throughput-shaped (fetch_all 6–16×, filter 4–14×,\nbulk_insert 1.8–80×) and trails only the two latency-bound point ops, where\nthe per-statement asyncio bridge costs tens of µs against in-process sync\ndrivers — the opt-in `sqlite://...?sync_fast_path=1` URL flag removes that\nbridge entirely (point queries ~7× faster).\n\nSpeed comes from the Rust hot path, **positional row decoding** (no per-row dict\nor column-name allocation), **compiled-SQL + prepared-statement caching**, and\nconnection pooling (see\n[Performance](https://vsdudakov.github.io/yara-orm/performance/)). Run it\nyourself with `make bench` / `make bench-mysql` / `make bench-sqlite`.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────┐\n│ Python  (python/yara_orm) ................. │\n│   Model / metaclass ....... schema + ORM API│\n│   QuerySet ................ lazy SQL builder│\n│   fields .................... abstract types│\n│   dialects ................ per-DB SQL rules│\n└───────────────┬─────────────────────────────┘\n                │  sql + params  (PyO3 / asyncio bridge)\n┌───────────────▼─────────────────────────────┐\n│ Rust  (rust/src)  →  yara_orm._engine ..... │\n│   Engine ...................... async facade│\n│   Backend trait .............. pluggable DBs│\n│     PgBackend ............... tokio-postgres│\n│     MySqlBackend ................ mysql_async│\n│     SqliteBackend ................. rusqlite│\n│   Value .................. Py⇆Rust⇆SQL types│\n└─────────────────────────────────────────────┘\n```\n\n- **Rust owns** pooling (deadpool), binding, type conversion and decoding.\n- **Python owns** the model layer and SQL generation.\n- **Adding a database** = a new `Backend` impl + scheme match in\n  `rust/src/backend/mod.rs`, plus a `BaseDialect` subclass in\n  `python/yara_orm/dialects.py`. The model layer never changes.\n\n## Development\n\n```bash\ngit clone https://github.com/vsdudakov/yara-orm\ncd yara-orm\nmake dev        # create .venv313 and install dev tools (maturin, ruff, ty, pytest)\nmake build      # compile the Rust engine into the venv (maturin develop)\nmake lint       # ruff check + ruff format --check + ty\nmake test       # pytest against $DB (default postgres://localhost/orm_demo)\nmake cov        # tests with the 100% coverage gate\nmake bench      # 4-way benchmark (needs `make bench-setup` once; Python ≤ 3.12 for Pony)\nmake bench-mysql   # same 4-way comparison on MySQL\nmake bench-sqlite  # same 4-way comparison on SQLite\n```\n\nRequires a Rust toolchain (`rustup`), a local PostgreSQL for the Postgres tests\nand a local MySQL for the MySQL tests; the SQLite tests are self-contained.\n\n## Contributing\n\nIssues and pull requests are welcome. Please run `make lint` and `make cov`\n(both must be green — lint clean and 100% coverage) before opening a PR.\n\n## Sponsor\n\nYara ORM is MIT-licensed and developed in the open. If it saves your project\ntime — or you'd like to support continued work on the Rust engine, backends and\ndocs — please consider [**sponsoring on GitHub**](https://github.com/sponsors/vsdudakov).\nEvery bit helps and is hugely appreciated. ❤️\n\n## License\n\n[MIT](LICENSE.md) © Yara ORM contributors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvsdudakov%2Fyara-orm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvsdudakov%2Fyara-orm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvsdudakov%2Fyara-orm/lists"}