# halfORM
[](https://pypi.org/project/half-orm/)
[](https://www.python.org)
[](https://www.postgresql.org)
[](https://pypi.org/project/half-orm/)
[](https://github.com/half-orm/half-orm/actions/workflows/python-package.yml)
[](https://coveralls.io/github/half-orm/half-orm?branch=main)
[](https://pepy.tech/project/half_orm)
**The PostgreSQL-native ORM that stays out of your way**
> halfORM lets you keep your database schema in SQL where it belongs, while giving you the comfort of Python for data manipulation. No migrations, no schema conflicts, no ORM fighting — just PostgreSQL and Python working together.
```python
from half_orm.model import Model
# Connect to your existing database
blog = Model('blog_db')
# Work with your existing tables instantly
Post = blog.get_relation_class('blog.post')
Author = blog.get_relation_class('blog.author')
# Clean, intuitive operations
post = Post(title='Hello halfORM!', content='Simple and powerful.')
result = post.ho_insert()
print(f"Created post #{result['id']}")
```
## 🎯 Why halfORM?
**Database-First Approach**: Your PostgreSQL schema is the source of truth. halfORM adapts to your database, not the other way around.
**SQL Transparency**: See exactly what queries are generated with `ho_mogrify()`. No mysterious SQL, no query surprises.
**PostgreSQL Native**: Use views, triggers, stored procedures, and advanced PostgreSQL features without compromise.
## ⚡ Quick Start
### Installation
```bash
pip install half_orm
```
### Configuration (one-time setup)
```bash
# Create config directory
mkdir ~/.half_orm
export HALFORM_CONF_DIR=~/.half_orm
# Create connection file: ~/.half_orm/my_database
echo "[database]
name = my_database
user = username
password = password
host = localhost
port = 5432" > ~/.half_orm/my_database
```
### First Steps
```python
from half_orm.model import Model
# Connect to your database
db = Model('my_database')
# See all your tables and views
print(db)
# Create a class for any table
Person = db.get_relation_class('public.person')
# See the table structure
print(Person())
```
## 🚀 Core Operations
### CRUD Made Simple
```python
# Create
person = Person(first_name='Alice', last_name='Smith', email='alice@example.com')
result = person.ho_insert()
# Read
for person in Person(last_name='Smith').ho_select():
print(f"{person['first_name']} {person['last_name']}")
# Update
Person(email='alice@example.com').ho_update(last_name='Johnson')
# Delete
Person(email='alice@example.com').ho_delete()
```
### Smart Querying
```python
# No .filter() method needed - the object IS the filter
young_people = Person(birth_date=('>', '1990-01-01'))
gmail_users = Person(email=('ilike', '%@gmail.com'))
# Navigate and constrain in one step
alice_posts = Post().author_fk(name=('ilike', 'alice%'))
# Chainable operations
recent_posts = (Post(is_published=True)
.ho_order_by('created_at desc')
.ho_limit(10)
.ho_offset(20))
# Set operations
active_or_recent = active_users | recent_users
power_users = premium_users & active_users
```
## 🎨 Custom Relation Classes with Foreign Key Navigation
Override generic relation classes with custom implementations containing business logic and personalized foreign key mappings:
```python
from half_orm.model import Model, register
from half_orm.relation import singleton
blog = Model('blog_db')
@register
class Author(blog.get_relation_class('blog.author')):
Fkeys = {
'posts_rfk': '_reverse_fkey_blog_post_author_id',
'comments_rfk': '_reverse_fkey_blog_comment_author_id',
}
@singleton
def create_post(self, title, content):
"""Create a new blog post for this author."""
return self.posts_rfk(title=title, content=content).ho_insert()
@singleton
def get_author_s_recent_posts(self, limit=10):
"""Get author's most recent posts."""
return self.posts_rfk().ho_order_by('published_at desc').ho_limit(limit).ho_select()
def get_recent_posts(self, limit=10):
"""Get most recent posts."""
return self.posts_rfk().ho_order_by('published_at desc').ho_limit(limit).ho_select()
@register
class Post(blog.get_relation_class('blog.post')):
Fkeys = {
'author_fk': 'author_id',
'comments_rfk': '_reverse_fkey_blog_comment_post_id',
}
def publish(self):
"""Publish this post."""
from datetime import datetime
self.published_at.value = datetime.now()
return self.ho_update()
# This returns your custom Author class with all methods!
post = Post(title='Welcome').ho_get()
author = post.author_fk().ho_get() # Instance of your custom Author class
# Use your custom methods
author.create_post("New Post", "Content here")
recent_posts = author.get_recent_posts(5)
# Chain relationships seamlessly
author.posts_rfk().comments_rfk().author_fk() # The authors that commented any post of the author
```
## 🔧 Advanced Features
### Transactions
```python
from half_orm.relation import transaction
class Author(db.get_relation_class('blog.author')):
@transaction
def create_with_posts(self, posts_data):
# Everything in one transaction
author_result = self.ho_insert()
for post_data in posts_data:
Post(author_id=author_result['id'], **post_data).ho_insert()
return author_result
```
### PostgreSQL Functions & Procedures
```python
# Execute functions
results = db.execute_function('my_schema.calculate_stats', user_id=123)
# Call procedures
db.call_procedure('my_schema.cleanup_old_data', days=30)
```
### Query Debugging
```python
# See the exact SQL being generated
person = Person(last_name=('ilike', 'sm%'))
person.ho_mogrify()
list(person.ho_select()) # or simply list(person)
# Prints: SELECT * FROM person WHERE last_name ILIKE 'sm%'
# Works with all operations
person = Person(email='old@example.com')
person.ho_mogrify()
person.ho_update(email='new@example.com')
# Prints the UPDATE query
# Performance analysis
count = Person().ho_count()
is_empty = Person(email='nonexistent@example.com').ho_is_empty()
```
## 🏗️ Real-World Example
```python
from half_orm.model import Model, register
from half_orm.relation import singleton
# Blog application
blog = Model('blog')
@register
class Author(blog.get_relation_class('blog.author')):
Fkeys = {
'posts_rfk': '_reverse_fkey_blog_post_author_id'
}
@singleton
def create_post(self, title, content):
return self.posts_rfk(title=title, content=content).ho_insert()
@register
class Post(blog.get_relation_class('blog.post')):
Fkeys = {
'author_fk': 'author_id',
'comments_rfk': '_reverse_fkey_blog_comment_post_id'
}
# Usage
author = Author(name='Jane Doe', email='jane@collorg.org')
if author.ho_is_empty():
author.ho_insert()
# Create post through relationship
post_data = author.create_post(
title='halfORM is Awesome!',
content='Here is why you should try it...'
)
post = Post(**post_data)
print(f"Published: {post.title.value}")
print(f"Comments: {post.comments_rfk().ho_count()}")
```
## 📊 halfORM vs. Others
| Feature | SQLAlchemy | Django ORM | Peewee | **halfORM** |
|---------|------------|------------|---------|-------------|
| **Learning Curve** | Steep | Moderate | Gentle | **Minimal** |
| **SQL Control** | Limited | Limited | Good | **Complete** |
| **Custom Business Logic** | Classes/Mixins | Model Methods | Model Methods | **@register decorator** |
| **Database Support** | Multi | Multi | Multi | **PostgreSQL only** |
| **PostgreSQL-Native** | Partial | Partial | No | **✅ Full** |
| **Database-First** | No | No | Partial | **✅ Native** |
| **Setup Complexity** | High | Framework | Low | **Ultra-Low** |
| **Best For** | Complex Apps | Django Web | Multi-DB Apps | **PostgreSQL + Python** |
## 🎓 When to Choose halfORM
### ✅ Perfect For
- **PostgreSQL-centric applications** - You want to leverage PostgreSQL's full power
- **Existing database projects** - You have a schema and want Python access
- **SQL-comfortable teams** - You prefer SQL for complex queries and logic
- **Rapid prototyping** - Get started in seconds, not hours
- **Microservices** - Lightweight, focused ORM without framework baggage
### ⚠️ Consider Alternatives If
- **Multi-database support needed** - halfORM is PostgreSQL-only
- **Django ecosystem** - Django ORM integrates better with Django
- **Team prefers code-first** - You want to define models in Python
- **Heavy ORM features needed** - You need advanced ORM patterns like lazy loading, identity maps, etc.
## 📚 Documentation (WIP)
**[📖 Complete Documentation](https://half-orm.github.io/half-orm/)** - Full documentation site 🚧
### Quick Links
- **[🚀 Quick Start](https://half-orm.github.io/half-orm/quick-start/)** - Get running in 5 minutes
- **[🎓 Tutorial](https://half-orm.github.io/half-orm/tutorial/)** - Step-by-step learning path *(WIP)*
- **[📋 API Reference](https://half-orm.github.io/half-orm/api/)** - Complete method documentation *(WIP)*
## 🤝 Contributing
We welcome contributions! halfORM is designed to stay simple and focused.
- **[Issues](https://github.com/half-orm/half-orm/issues)** - Bug reports and feature requests
- **[Discussions](https://github.com/half-orm/half-orm/discussions)** - Questions and community
- **[Contributing Guide](CONTRIBUTING.md)** - How to contribute code
## 📈 Status & Roadmap
halfORM is actively maintained and used in production. Current focus:
- ✅ **Stable API** - Core features are stable since v0.8
- 🔄 **Performance optimizations** - Query generation improvements
- 📝 **Documentation expansion** - More examples and guides
- 🧪 **Advanced PostgreSQL features** - Better support for newer PostgreSQL versions
## 📜 License
halfORM is licensed under the [LGPL-3.0](LICENSE) license.
---
> **"Database-first development shouldn't be this hard. halfORM makes it simple."**
**Made with ❤️ for PostgreSQL and Python developers**
Raw data
{
"_id": null,
"home_page": "https://github.com/half-orm/half-orm",
"name": "half-orm",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": null,
"author": "Jo\u00ebl Ma\u00efzi",
"author_email": "joel.maizi@collorg.org",
"download_url": "https://files.pythonhosted.org/packages/65/3a/15d91e999b9734bd826edbd58a2beb20fbea739654abe698620dba725006/half_orm-0.16.2.tar.gz",
"platform": null,
"description": "# halfORM\n\n[](https://pypi.org/project/half-orm/)\n[](https://www.python.org)\n[](https://www.postgresql.org)\n[](https://pypi.org/project/half-orm/)\n[](https://github.com/half-orm/half-orm/actions/workflows/python-package.yml)\n[](https://coveralls.io/github/half-orm/half-orm?branch=main)\n[](https://pepy.tech/project/half_orm)\n\n**The PostgreSQL-native ORM that stays out of your way**\n\n> halfORM lets you keep your database schema in SQL where it belongs, while giving you the comfort of Python for data manipulation. No migrations, no schema conflicts, no ORM fighting \u2014 just PostgreSQL and Python working together.\n\n```python\nfrom half_orm.model import Model\n\n# Connect to your existing database\nblog = Model('blog_db')\n\n# Work with your existing tables instantly\nPost = blog.get_relation_class('blog.post')\nAuthor = blog.get_relation_class('blog.author')\n\n# Clean, intuitive operations\npost = Post(title='Hello halfORM!', content='Simple and powerful.')\nresult = post.ho_insert()\nprint(f\"Created post #{result['id']}\")\n```\n\n## \ud83c\udfaf Why halfORM?\n\n**Database-First Approach**: Your PostgreSQL schema is the source of truth. halfORM adapts to your database, not the other way around.\n\n**SQL Transparency**: See exactly what queries are generated with `ho_mogrify()`. No mysterious SQL, no query surprises.\n\n**PostgreSQL Native**: Use views, triggers, stored procedures, and advanced PostgreSQL features without compromise.\n\n## \u26a1 Quick Start\n\n### Installation\n```bash\npip install half_orm\n```\n\n### Configuration (one-time setup)\n```bash\n# Create config directory\nmkdir ~/.half_orm\nexport HALFORM_CONF_DIR=~/.half_orm\n\n# Create connection file: ~/.half_orm/my_database\necho \"[database]\nname = my_database\nuser = username\npassword = password\nhost = localhost\nport = 5432\" > ~/.half_orm/my_database\n```\n\n### First Steps\n```python\nfrom half_orm.model import Model\n\n# Connect to your database\ndb = Model('my_database')\n\n# See all your tables and views\nprint(db)\n\n# Create a class for any table\nPerson = db.get_relation_class('public.person')\n\n# See the table structure\nprint(Person())\n```\n\n## \ud83d\ude80 Core Operations\n\n### CRUD Made Simple\n\n```python\n# Create\nperson = Person(first_name='Alice', last_name='Smith', email='alice@example.com')\nresult = person.ho_insert()\n\n# Read\nfor person in Person(last_name='Smith').ho_select():\n print(f\"{person['first_name']} {person['last_name']}\")\n\n# Update\nPerson(email='alice@example.com').ho_update(last_name='Johnson')\n\n# Delete \nPerson(email='alice@example.com').ho_delete()\n```\n\n### Smart Querying\n\n```python\n# No .filter() method needed - the object IS the filter\nyoung_people = Person(birth_date=('>', '1990-01-01'))\ngmail_users = Person(email=('ilike', '%@gmail.com'))\n\n# Navigate and constrain in one step\nalice_posts = Post().author_fk(name=('ilike', 'alice%'))\n\n# Chainable operations\nrecent_posts = (Post(is_published=True)\n .ho_order_by('created_at desc')\n .ho_limit(10)\n .ho_offset(20))\n\n# Set operations\nactive_or_recent = active_users | recent_users\npower_users = premium_users & active_users\n```\n\n## \ud83c\udfa8 Custom Relation Classes with Foreign Key Navigation\n\nOverride generic relation classes with custom implementations containing business logic and personalized foreign key mappings:\n\n```python\nfrom half_orm.model import Model, register\nfrom half_orm.relation import singleton\n\nblog = Model('blog_db')\n\n@register\nclass Author(blog.get_relation_class('blog.author')):\n Fkeys = {\n 'posts_rfk': '_reverse_fkey_blog_post_author_id',\n 'comments_rfk': '_reverse_fkey_blog_comment_author_id',\n }\n \n @singleton\n def create_post(self, title, content):\n \"\"\"Create a new blog post for this author.\"\"\"\n return self.posts_rfk(title=title, content=content).ho_insert()\n \n @singleton\n def get_author_s_recent_posts(self, limit=10):\n \"\"\"Get author's most recent posts.\"\"\"\n return self.posts_rfk().ho_order_by('published_at desc').ho_limit(limit).ho_select()\n\n def get_recent_posts(self, limit=10):\n \"\"\"Get most recent posts.\"\"\"\n return self.posts_rfk().ho_order_by('published_at desc').ho_limit(limit).ho_select()\n\n@register \nclass Post(blog.get_relation_class('blog.post')):\n Fkeys = {\n 'author_fk': 'author_id',\n 'comments_rfk': '_reverse_fkey_blog_comment_post_id',\n }\n\n def publish(self):\n \"\"\"Publish this post.\"\"\"\n from datetime import datetime\n self.published_at.value = datetime.now()\n return self.ho_update()\n\n# This returns your custom Author class with all methods!\npost = Post(title='Welcome').ho_get()\nauthor = post.author_fk().ho_get() # Instance of your custom Author class\n\n# Use your custom methods\nauthor.create_post(\"New Post\", \"Content here\")\nrecent_posts = author.get_recent_posts(5)\n\n# Chain relationships seamlessly \nauthor.posts_rfk().comments_rfk().author_fk() # The authors that commented any post of the author\n```\n\n## \ud83d\udd27 Advanced Features\n\n### Transactions\n```python\nfrom half_orm.relation import transaction\n\nclass Author(db.get_relation_class('blog.author')):\n @transaction\n def create_with_posts(self, posts_data):\n # Everything in one transaction\n author_result = self.ho_insert()\n for post_data in posts_data:\n Post(author_id=author_result['id'], **post_data).ho_insert()\n return author_result\n```\n\n### PostgreSQL Functions & Procedures\n```python\n# Execute functions\nresults = db.execute_function('my_schema.calculate_stats', user_id=123)\n\n# Call procedures \ndb.call_procedure('my_schema.cleanup_old_data', days=30)\n```\n\n### Query Debugging\n```python\n# See the exact SQL being generated\nperson = Person(last_name=('ilike', 'sm%'))\nperson.ho_mogrify()\nlist(person.ho_select()) # or simply list(person)\n# Prints: SELECT * FROM person WHERE last_name ILIKE 'sm%'\n\n# Works with all operations\nperson = Person(email='old@example.com')\nperson.ho_mogrify()\nperson.ho_update(email='new@example.com')\n# Prints the UPDATE query\n\n# Performance analysis\ncount = Person().ho_count()\nis_empty = Person(email='nonexistent@example.com').ho_is_empty()\n```\n\n## \ud83c\udfd7\ufe0f Real-World Example\n\n```python\nfrom half_orm.model import Model, register\nfrom half_orm.relation import singleton\n\n# Blog application\nblog = Model('blog')\n\n@register\nclass Author(blog.get_relation_class('blog.author')):\n Fkeys = {\n 'posts_rfk': '_reverse_fkey_blog_post_author_id'\n }\n \n @singleton\n def create_post(self, title, content):\n return self.posts_rfk(title=title, content=content).ho_insert()\n\n@register\nclass Post(blog.get_relation_class('blog.post')):\n Fkeys = {\n 'author_fk': 'author_id',\n 'comments_rfk': '_reverse_fkey_blog_comment_post_id' \n }\n\n# Usage\nauthor = Author(name='Jane Doe', email='jane@collorg.org')\nif author.ho_is_empty():\n author.ho_insert()\n\n# Create post through relationship\npost_data = author.create_post(\n title='halfORM is Awesome!',\n content='Here is why you should try it...'\n)\n\npost = Post(**post_data)\nprint(f\"Published: {post.title.value}\")\nprint(f\"Comments: {post.comments_rfk().ho_count()}\")\n```\n\n## \ud83d\udcca halfORM vs. Others\n\n| Feature | SQLAlchemy | Django ORM | Peewee | **halfORM** |\n|---------|------------|------------|---------|-------------|\n| **Learning Curve** | Steep | Moderate | Gentle | **Minimal** |\n| **SQL Control** | Limited | Limited | Good | **Complete** |\n| **Custom Business Logic** | Classes/Mixins | Model Methods | Model Methods | **@register decorator** |\n| **Database Support** | Multi | Multi | Multi | **PostgreSQL only** |\n| **PostgreSQL-Native** | Partial | Partial | No | **\u2705 Full** |\n| **Database-First** | No | No | Partial | **\u2705 Native** |\n| **Setup Complexity** | High | Framework | Low | **Ultra-Low** |\n| **Best For** | Complex Apps | Django Web | Multi-DB Apps | **PostgreSQL + Python** |\n\n## \ud83c\udf93 When to Choose halfORM\n\n### \u2705 Perfect For\n- **PostgreSQL-centric applications** - You want to leverage PostgreSQL's full power\n- **Existing database projects** - You have a schema and want Python access\n- **SQL-comfortable teams** - You prefer SQL for complex queries and logic\n- **Rapid prototyping** - Get started in seconds, not hours\n- **Microservices** - Lightweight, focused ORM without framework baggage\n\n### \u26a0\ufe0f Consider Alternatives If\n- **Multi-database support needed** - halfORM is PostgreSQL-only\n- **Django ecosystem** - Django ORM integrates better with Django\n- **Team prefers code-first** - You want to define models in Python\n- **Heavy ORM features needed** - You need advanced ORM patterns like lazy loading, identity maps, etc.\n\n## \ud83d\udcda Documentation (WIP)\n\n**[\ud83d\udcd6 Complete Documentation](https://half-orm.github.io/half-orm/)** - Full documentation site \ud83d\udea7\n\n### Quick Links\n\n- **[\ud83d\ude80 Quick Start](https://half-orm.github.io/half-orm/quick-start/)** - Get running in 5 minutes\n- **[\ud83c\udf93 Tutorial](https://half-orm.github.io/half-orm/tutorial/)** - Step-by-step learning path *(WIP)*\n- **[\ud83d\udccb API Reference](https://half-orm.github.io/half-orm/api/)** - Complete method documentation *(WIP)*\n\n## \ud83e\udd1d Contributing\n\nWe welcome contributions! halfORM is designed to stay simple and focused.\n\n- **[Issues](https://github.com/half-orm/half-orm/issues)** - Bug reports and feature requests\n- **[Discussions](https://github.com/half-orm/half-orm/discussions)** - Questions and community\n- **[Contributing Guide](CONTRIBUTING.md)** - How to contribute code\n\n## \ud83d\udcc8 Status & Roadmap\n\nhalfORM is actively maintained and used in production. Current focus:\n\n- \u2705 **Stable API** - Core features are stable since v0.8\n- \ud83d\udd04 **Performance optimizations** - Query generation improvements \n- \ud83d\udcdd **Documentation expansion** - More examples and guides\n- \ud83e\uddea **Advanced PostgreSQL features** - Better support for newer PostgreSQL versions\n\n## \ud83d\udcdc License\n\nhalfORM is licensed under the [LGPL-3.0](LICENSE) license.\n\n---\n\n> **\"Database-first development shouldn't be this hard. halfORM makes it simple.\"**\n\n**Made with \u2764\ufe0f for PostgreSQL and Python developers**\n",
"bugtrack_url": null,
"license": "GPLv3",
"summary": "A simple PostgreSQL to Python mapper.",
"version": "0.16.2",
"project_urls": {
"Bug Reports": "https://github.com/half-orm/half-orm/issues",
"Documentation": "https://half-orm.github.io/half-orm/",
"Homepage": "https://github.com/half-orm/half-orm",
"Source": "https://github.com/half-orm/half-orm"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "a5200278ab2fc7feb423252afc90c15daf727c2a5040cf520652f5d6ef97356f",
"md5": "8342bfca56ad0d8deaf44f71eaad8df7",
"sha256": "d2e451cc4f1335247ad71c73728cf0b3e02da9948f35030e97c08e0a5e35d825"
},
"downloads": -1,
"filename": "half_orm-0.16.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "8342bfca56ad0d8deaf44f71eaad8df7",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 43716,
"upload_time": "2025-09-10T08:19:55",
"upload_time_iso_8601": "2025-09-10T08:19:55.197525Z",
"url": "https://files.pythonhosted.org/packages/a5/20/0278ab2fc7feb423252afc90c15daf727c2a5040cf520652f5d6ef97356f/half_orm-0.16.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "653a15d91e999b9734bd826edbd58a2beb20fbea739654abe698620dba725006",
"md5": "ccda78cc3959e87cc6591d60bb7fedda",
"sha256": "ef4beb7245369f9ba94d55479025dcc4c530249920f5e68e029ce98d7c4a75f3"
},
"downloads": -1,
"filename": "half_orm-0.16.2.tar.gz",
"has_sig": false,
"md5_digest": "ccda78cc3959e87cc6591d60bb7fedda",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 48382,
"upload_time": "2025-09-10T08:19:56",
"upload_time_iso_8601": "2025-09-10T08:19:56.894946Z",
"url": "https://files.pythonhosted.org/packages/65/3a/15d91e999b9734bd826edbd58a2beb20fbea739654abe698620dba725006/half_orm-0.16.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-10 08:19:56",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "half-orm",
"github_project": "half-orm",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"requirements": [
{
"name": "click",
"specs": []
},
{
"name": "gitdb",
"specs": []
},
{
"name": "GitPython",
"specs": []
},
{
"name": "psycopg2-binary",
"specs": []
}
],
"tox": true,
"lcname": "half-orm"
}