| .hop | ||
| bootstrap | ||
| ddd_domain | ||
| Patches | ||
| tests | ||
| .gitignore | ||
| pyproject.toml | ||
| README.md | ||
ddd_domain
Database-driven application using half-orm with half-orm-dev workflow.
Database: ddd_domain
Generated with: half-orm-dev==1.0.0-a32
🚀 Quick Start
Prerequisites
# Install half-orm-dev (includes half-orm)
# IMPORTANT: Install the version specified in .hop/config.
# Replace <version> by the value of hop_version.
pip install half-orm-dev==<version>
Version requirement: This project requires half-orm-dev >= 1.0.0-a32
Clone This Project
# Clone project with automatic database setup (use git_origin in .hop/config)
half_orm dev clone <git_origin>
This will:
- Clone the repository and checkout
ho-prodbranch - Configure database connection in
/etc/half_orm/ddd_domainor${HALFORM_CONF_DIR}/ddd_domain - Restore production schema from
.hop/model/schema.sql - Generate Python ORM classes in
ddd_domain/directory
📖 Development Workflow
This project uses half-orm-dev for database versioning and patch management.
1. Create a Release
# Create new minor release (e.g., 0.17.0)
half_orm dev release create minor
# Creates ho-release/0.17.0 branch
# Creates .hop/releases/0.17.0-patches.toml
2. Create a Patch
Create patch (auto-added as candidate)
half_orm dev patch create -
Example: Create user authentication patch
half_orm dev patch create 456-user-auth
Creates ho-patch/456-user-auth branch
Creates Patches/456-user-auth/ directory
### 3. Develop Your Changes
```bash
# Add SQL migration
echo "CREATE TABLE users (id SERIAL PRIMARY KEY, username TEXT);" > Patches/456-user-auth/1-create-users.sql
# Add Python migration (optional)
cat > Patches/456-user-auth/2-seed-data.py << 'EOF'
from ddd_domain.public.users import Users
def apply(model):
Users(username='admin').ho_insert()
EOF
# Apply patch to test database
half_orm dev patch apply
# Generated ORM classes available in ddd_domain/
from ddd_domain.public.users import Users
user = Users(username='john').ho_insert()
4. Test Your Changes
# Run tests (pytest must be configured)
pytest tests/
# half-orm-dev validates:
# - Patch SQL syntax
# - Patch idempotency (can be applied twice)
# - Test suite passes with full release context
5. Merge Patch into Release
# Merge patch (automatic validation + tests)
half_orm dev patch merge
# What happens:
# 1. Creates temp validation branch
# 2. Applies ALL staged patches + your patch
# 3. Runs pytest tests/
# 4. If tests pass → merges into ho-release/X.Y.Z
# 5. Changes patch status to "staged" in TOML
# 6. Deletes patch branch
6. Promote Release
# Stage → RC (first release candidate)
half_orm dev release promote rc
# Creates .hop/releases/0.17.0-rc1.txt snapshot
# Tags ho-release-0.17.0-rc1
# RC → Production (after validation)
half_orm dev release promote prod
# Merges into ho-prod branch
# Creates .hop/releases/0.17.0.txt
# Tags production-0.17.0
# Generates .hop/model/schema-0.17.0.sql
🔧 Common Commands
Status & Information
half_orm dev check # Show repository state
Patch Management
half_orm dev patch create # create a new patch for release
half_orm dev patch apply # Apply patch to dev database
half_orm dev patch merge # Integrate patch into release
Release Management
half_orm dev release create <level> # Create release (patch/minor/major)
half_orm dev release promote rc # Stage → RC
half_orm dev release promote prod # RC → Production
Production Deployment
# On production server
half_orm dev update # Check available releases
half_orm dev upgrade <version> # Deploy specific version
📁 Project Structure
ddd_domain/
├── .hop/ # half-orm-dev metadata
│ ├── config # Repository configuration
│ ├── model/ # Database schemas and data
│ │ ├── schema.sql # Current production schema (symlink)
│ │ ├── schema-X.Y.Z.sql # Versioned schemas
│ │ ├── metadata-X.Y.Z.sql # half_orm_meta data dumps
│ │ └── data-X.Y.Z.sql # Reference data from @HOP:data patches
│ └── releases/ # Release tracking files
│ ├── X.Y.Z-patches.toml # Development releases (mutable)
│ ├── X.Y.Z-rcN.txt # Release candidates (immutable)
│ └── X.Y.Z.txt # Production releases (immutable)
├── Patches/ # Patch directories
│ └── <id>-<description>/ # Individual patch files
│ ├── *.sql # SQL migrations (numbered)
│ └── *.py # Python migrations (optional)
├── ddd_domain/ # Generated ORM package
│ └── <schema>/ # schema classes
└── tests/ # Test suite (pytest)
🌿 Git Branch Structure
ho-prod: Production branch (stable, source of truth)ho-release/X.Y.Z: Release integration branches (temporary)ho-patch/ID: Patch development branches (temporary)
All development happens on patch branches, merged into release branches, then promoted to production.
💾 Data Persistence (@HOP:data)
For reference data that must be loaded with every database installation (lookup tables, default roles, etc.), use the @HOP:data annotation:
-- @HOP:data
-- This file will be included in model/data-X.Y.Z.sql
INSERT INTO roles (name, description)
VALUES ('admin', 'Administrator')
ON CONFLICT (name) DO NOTHING;
INSERT INTO permissions (name)
VALUES ('read'), ('write'), ('delete')
ON CONFLICT DO NOTHING;
How it works
- In patches: SQL files starting with
-- @HOP:datacontain reference data - Production promote: All
@HOP:datafiles are consolidated intomodel/data-X.Y.Z.sql - Clone/Restore: Data files are loaded automatically after schema restoration
- Production upgrade: Data is inserted via normal patch application (no special handling)
Best practices
- Use
ON CONFLICT DO NOTHINGorON CONFLICT DO UPDATEfor idempotency - Keep data files small and focused (one concern per file)
- Number your SQL files to control execution order:
01_roles.sql,02_permissions.sql - Only use for reference data, not user-generated data
⚡ Async Usage
The generated package supports both synchronous and asynchronous access to the database.
Sync
from ddd_domain.public.users import Users
user = Users(username='john').ho_get()
users = list(Users(is_active=True).ho_select())
Async
import asyncio
from ddd_domain import aconnect, adisconnect
from ddd_domain.public.users import Users
async def main():
await aconnect()
try:
user = await Users(username='john').ho_aget()
users = await Users(is_active=True).ho_aselect()
finally:
await adisconnect()
asyncio.run(main())
REST API (FastAPI)
from contextlib import asynccontextmanager
from fastapi import FastAPI
import ddd_domain
@asynccontextmanager
async def lifespan(app: FastAPI):
await ddd_domain.aconnect()
yield
await ddd_domain.adisconnect()
app = FastAPI(lifespan=lifespan)
The async connection pool is established automatically during tests via the _async_pool
fixture in tests/conftest.py.
📚 Documentation
- half-orm-dev: https://github.com/half-orm/half-orm-dev
- half-orm: https://github.com/half-orm/half-orm
- Workflow guide: https://github.com/half-orm/half-orm-dev#readme
🆘 Getting Help
half_orm dev --help # General help
half_orm dev patch --help # Patch commands
half_orm dev release --help # Release commands
⚠️ Important Notes
- Always work on patch branches - Never commit directly to ho-prod or ho-release
- Test before merging -
patch mergeruns tests automatically - Sequential releases - Only the smallest version in preparation can be promoted
- Idempotent patches - SQL must be replayable (use
IF NOT EXISTS, etc.) - Production schema -
.hop/model/schema.sqlis the source of truth
Generated by half-orm-dev 1.0.0-a32