halfORM
⚠️ BREAKING CHANGES in v1.0.0
Legacy unprefixed API removed — the pre-
ho_method names, deprecated since 0.x, no longer exist:select,insert,update,delete,get,count,is_empty,unaccent,order_by,limit,offset,_mogrify. Use theirho_-prefixed equivalents (ho_select,ho_insert,ho_update,ho_delete,ho_get,ho_count,ho_is_empty,ho_unaccent,ho_mogrify, passingorder_by/limit/offsetasho_select()keyword arguments).
DC_Relationremoved fromhalf_orm.relation— this IDE type-checking stub is now generated per-project byhalf-orm-devinstead of shipped in the library.
ho_get()is now public and returns adictdirectly. It raisesNotFoundError(0 rows) orMultipleRowsError(> 1 row) instead of counting first.Deprecated query-builder setters removed —
ho_limit,ho_offset,ho_order_by,ho_distinctno longer exist as setters. Pass these as keyword arguments toho_select():# Before (0.x) rel.ho_limit = 10 rel.ho_order_by = 'name' list(rel) # After (1.0) list(rel.ho_select(limit=10, order_by='name'))
FKEYS_PROPERTIES/FKEYSclass attributes removed — useFkeysonly.
ho_cast()now raisesCastErrorif the target is not in the PostgreSQL inheritance hierarchy of the relation.See CHANGELOG.md for the full list of changes.
halfORM is a database-first ORM for PostgreSQL. Your schema lives in the database; halfORM introspects it at runtime and gives you Python objects to work with your data. No migrations, no code generation.
The central idea: a Relation object is a predicate. It describes the
logical condition that rows must satisfy to belong to the relation. Its
extension — the set of rows currently satisfying the predicate in the
database — is what you read, update, or delete.
Key features
- Database-first — the schema lives in PostgreSQL, not in Python classes.
- Predicate model — relations are predicates; set operators (
|,&,-) compose them before any SQL is sent. - FK navigation — join across tables (and views) by setting FK attributes;
explicit
Fkeysdict for views where PostgreSQL stores no FK metadata. - Sync + async — every executor has an
a-prefixed async counterpart (ho_aselect,ho_ainsert, …). - Bulk load —
ho_copy/ho_acopyfor high-throughput inserts via PostgreSQLCOPY. - No magic — queries are built from the predicate you describe and executed
only when you call an executor.
ho_mogrify()shows the exact SQL.
Install
pip install half_orm
Requires psycopg 3 (psycopg[binary]).
Configure
# ~/.half_orm/blog
[database]
name = blog
user = alice
password = secret
host = localhost
Usage
from half_orm.model import Model
from half_orm.relation_errors import NotFoundError, MultipleRowsError
blog = Model('blog')
Post = blog.get_relation_class('blog.post')
Author = blog.get_relation_class('blog.author')
# Insert — returns the inserted row as a dict
alice = Author(
first_name='Alice', last_name='Martin', email='alice@example.com'
).ho_insert()
# Query — Author(last_name='Martin') is a predicate, not a query
for row in Author(last_name='Martin').ho_select('id', 'email'):
print(row)
# Get exactly one row
try:
author = Author(last_name='Martin').ho_get()
except NotFoundError:
print("no such author")
except MultipleRowsError:
print("ambiguous — more than one author named Martin")
# FK navigation — no JOIN written by hand
post = Post(id=1)
post.author_fk.set() # join all authors
for row in post.ho_select(json_agg={'author_fk': ['first_name', 'last_name']}):
print(row['author_fk']) # {'first_name': 'Alice', 'last_name': 'Martin'}
# Set operators — compose predicates before hitting the database
recent = Post(created_at=('>', '2024-01-01'))
featured = Post(featured=True)
combined = recent | featured # UNION
print(combined.ho_count())
# Update / delete
Author(id=alice['id']).ho_update(email='alice@newdomain.com')
Author(id=alice['id']).ho_delete()
Documentation
Extensions
| Extension | Description |
|---|---|
| half-orm-dev | Development tools — half_orm dev project scaffolding, patch management, and schema synchronisation. |
License
halfORM is licensed under the GPL-3.0 license.
Metadata
Release files for half-orm 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| half_orm-1.1.0.tar.gz | 108.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| half_orm-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 204.4 kB
Release files / half_orm-1.1.0.tar.gz
| Download URL | half_orm-1.1.0.tar.gz |
|---|---|
| Size | 108.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8bdb194389a666b87da862cb9fe80b18db42f95cc33e533ead905948558cecd8
|
|
BLAKE2b-256 checksum How to use checksums |
8b46f4f11d34539b8350796d6c47e5ec179d0cdc7776b1d77365c375afaf5d1b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / half_orm-1.1.0-py3-none-any.whl
| Download URL | half_orm-1.1.0-py3-none-any.whl |
|---|---|
| Size | 96.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b3d743839d8392b65c6d91a08777904c28285ad8b53e52b1fa88ad163de6ae55
|
|
BLAKE2b-256 checksum How to use checksums |
20015b8143f6f27fad9b76f656d3008e6b4958e01ecc79348115e9f7ec8e82c3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Security for half-orm 1.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.