First-class Django integration for Palm Engine — ORM storage, auto-discovery, and management commands
Project description
palm-django
First-class Django integration for Palm Engine — the Python-first Behavior Tree orchestrator.
Install with one command, add palm_django to INSTALLED_APPS, and run flows, wizards, and resources against your existing Django database.
Features
- Zero-config bootstrap —
ApplicationHoststarts onAppConfig.ready()with Django-tuned defaults - ORM storage — Palm definitions, instances, and KV entries persist in Django models
- Auto-discovery —
palm_definitions/palmhooks in each installed app - Model resources —
@as_palm_resourceexposes CRUD as{app}.{model}.{action} - Auto schemas — optional
schema=Truegenerates PalmDictStateSchemafrom Django fields - Transactional bridging —
palm_atomic()rolls back ORM + Palm writes together - Operator CLI —
python manage.py palm doctor|server|run|flow|instance|resource|quickstart - Palm Explorer —
palm serverserves the SSR hub at/explorer - Django Admin — inspect definitions, instances, and storage; start flows from admin
Requirements
- Python 3.11+
- Django 4.2+
- palmengine 0.12.9+
Installation
pip install palm-django
This installs palmengine (0.12.9+) and Django (4.2+) automatically.
From source (development):
git clone https://github.com/JGabrielGruber/palm-django.git
cd palm-django
pip install -e ".[dev]"
Quick start
1. Add to INSTALLED_APPS
# settings.py
INSTALLED_APPS = [
# ...
"palm_django",
]
On startup, palm_django bootstraps a process-wide ApplicationHost and scans your Django apps for Palm hooks.
2. Run migrations
Django ORM is the default storage backend. Apply palm-django tables before first use:
python manage.py migrate palm_django
3. Configure Palm (optional)
Use a PALM dict and/or individual PALM_* settings. Keys accept either STORAGE_BACKEND (Django style) or storage_backend (Palm style).
# settings.py
PALM = {
# Default is django ORM — override only when needed:
# "STORAGE_BACKEND": "memory",
"LOAD_EXAMPLE_DEFINITIONS": False,
"HOST_PROFILE": "all_in_one",
}
# palm-django integration options (not forwarded to PalmSettings)
PALM_AUTO_START = True
PALM_DISCOVERY_MODULES = ("palm_definitions", "palm")
Defaults are tuned for Django projects (no bundled Palm examples, lightweight startup).
4. Scaffold with quickstart (optional)
python manage.py palm quickstart
python manage.py palm quickstart --app myapp
python manage.py palm quickstart --app myapp --write # writes myapp/palm_definitions.py
5. Register definitions in your apps
Create myapp/palm_definitions.py:
from palm.common.persistence.definition_repository import DefinitionRepository
from palm.definitions.flow import FlowDefinition
def register_definitions(repository: DefinitionRepository) -> None:
repository.register_flow(
FlowDefinition(
id="hello_flow",
name="hello_flow",
pattern="dag",
options={"name": "hello_flow"},
)
)
palm_django imports register_definitions from each installed app automatically.
Supported hooks (all optional, per app):
| Module suffix | Hook |
|---|---|
palm_definitions |
register_definitions() |
palm_definitions |
register_resources() |
palm_definitions |
register_commit_handlers() |
palm |
same hooks (alternate name) |
6. Expose Django models as Palm resources
Decorate any model (or set a class-level palm_resource dict) and palm-django auto-registers CRUD resources at bootstrap:
from django.db import models
from palm_django import as_palm_resource
@as_palm_resource(actions=["get", "create", "update", "delete", "list"])
class Order(models.Model):
customer_id = models.IntegerField()
total = models.DecimalField(max_digits=12, decimal_places=2)
Resource names follow {app_label}.{model_name}.{action} — e.g.
myapp.order.create. Params bind from Palm state via {{ state.pk }},
{{ state.data }}, etc.; results promote to output_key (defaults to the
model name) in wizard/BT leaves.
from palm_django import get_app
app = get_app()
result = app.invoke_resource(
"myapp.order.create",
state={"data": {"customer_id": 1, "total": "49.99"}},
)
7. Wizard + Django model resources
Use Palm 0.12 step_kind: resource with resource_ref pointing at your auto-registered model resources:
FlowDefinition(
id="flow-onboard-order",
name="onboard_order",
pattern="wizard",
options={
"include_summary": True,
"steps": [
{
"slug": "customer_id",
"title": "Customer",
"prompt": "Enter customer id",
"validation": [{"rule": "not_empty"}],
},
{
"slug": "create-order",
"title": "Create order",
"step_kind": "resource",
"resource_ref": "myapp.order.create",
"action": "create",
"params": {
"data": {
"customer_id": "{{ state.customer_id }}",
"total": "{{ state.total }}",
}
},
"output_key": "order",
},
],
},
)
See tests/palm_sample/palm_definitions.py for a full working example (item_wizard).
8. Use Palm in your code
from palm_django import get_host, palm_atomic
def start_onboarding(user_id: str):
return get_host().submit_flow("onboard", metadata={"user_id": user_id})
# Roll back Palm storage + ORM writes together
with palm_atomic():
order = Order.objects.create(customer_id=1, total="10.00")
get_host().submit_flow("fulfill_order", metadata={"order_id": order.pk})
Or access the infrastructure layer directly:
from palm_django import get_app
flows = get_app().list_flows()
9. Operator commands
# Health check (human-readable or JSON)
python manage.py palm doctor
python manage.py palm doctor --json
# Palm Explorer (ServerRuntime + SSR hub) — foreground until Ctrl+C
python manage.py palm server
python manage.py palm host server # alias
python manage.py palm server --port 9000
python manage.py palm server --host 0.0.0.0 --port 8080
# Scaffold snippets
python manage.py palm quickstart --app myapp
# Start a flow or process (auto-detects kind)
python manage.py palm run sample_flow
python manage.py palm flow start onboard --metadata '{"user_id": 42}'
# Inspect catalog
python manage.py palm flow list
python manage.py palm instance list
python manage.py palm instance list --all
python manage.py palm resource list
# Invoke a resource with state binding
python manage.py palm resource invoke myapp.order.create \
--state '{"data": {"customer_id": 1, "total": "10.00"}}'
# Resume a persisted instance
python manage.py palm instance resume <instance_id>
Commands bootstrap Palm automatically and run against the active Django database.
Palm Explorer server
palm server starts a full ServerRuntime with the Palm Explorer SSR surface. It uses your Django database (ORM storage), discovered flows/resources, and PALM_* settings:
python manage.py palm server
# Palm Explorer available at http://127.0.0.1:8080/explorer
Configure bind address via PALM_SERVER_HOST / PALM_SERVER_PORT or CLI --host / --port. Press Ctrl+C for graceful shutdown.
10. Django Admin
Add django.contrib.admin to INSTALLED_APPS to inspect Palm persistence models:
- Palm definitions — browse flows/processes/resources; admin action Start flow
- Palm process instances — browse instances; admin action Resume
- Palm storage entries — raw KV rows (projections, indexes, outbox)
python manage.py palm doctor # confirms admin registration status
Public API
| Symbol | Description |
|---|---|
get_host() |
Process-wide ApplicationHost |
get_app() |
PalmApp infrastructure layer |
get_runtime() |
PalmRuntime wrapper with discovery metadata |
bootstrap_palm() |
Idempotent manual bootstrap |
shutdown_palm() |
Graceful shutdown |
is_palm_started() |
Whether the host is running |
get_palm_settings() |
PalmSettings built from Django settings |
build_palm_settings_dict() |
Raw merged settings dict |
DjangoStorageBackend |
Palm BaseBackend backed by Django ORM |
palm_atomic() |
Context manager for transactional Palm + Django writes |
django_atomic() |
Join outer atomic() without redundant savepoints |
storage_health_report() |
ORM table readiness and row counts |
as_palm_resource |
Decorator to expose a Django model as Palm resources |
DjangoModelProvider |
Palm provider (django_model) backing ORM CRUD |
palm_resource_invoked |
Signal after successful provider invocation |
palm_model_saved |
Signal when a decorated model is saved via ORM |
PalmResourceModel |
Optional base class for class-level palm_resource config |
Django model resources
| Action | Params | State binding examples |
|---|---|---|
get |
model, pk (or custom lookup_field) |
state.pk |
list |
model, filters, order_by, limit |
state.filters |
create |
model, data |
state.data |
update |
model, pk, data |
state.pk, state.data |
delete |
model, pk |
state.pk |
Provider registry key: django_model. Mutating actions join the current Django transaction when one is active.
Auto-generated schemas
Enable Palm-compatible DictStateSchema documents from Django fields with schema=True:
@as_palm_resource(actions=["create", "get"], schema=True)
class Order(models.Model):
customer_id = models.IntegerField()
total = models.DecimalField(max_digits=12, decimal_places=2)
Or via class config:
class LineItem(models.Model):
class PalmResource:
actions = ["create"]
schema = True
When enabled, palm-django:
- Attaches
input_schema/output_schemato eachResourceDefinition - Registers reusable
StateSchemaDefinitionentries (myapp.order.data,myapp.order.instance) - Validates
create/updatepayloads inDjangoModelProvider(disable withschema={"validate": False})
Use schema refs in wizard steps:
{
"slug": "order_data",
"state_schema_ref": "myapp.order.data",
}
Programmatic access:
from palm_django.resources import (
get_palm_resource_config,
model_data_schema_name,
model_to_dict_state_schema,
)
config = get_palm_resource_config(Order)
schema = model_to_dict_state_schema(Order, config, writable_only=True)
ref = model_data_schema_name(Order, config) # myapp.order.data
Signals
Connect to Palm lifecycle events in your Django apps:
from django.dispatch import receiver
from palm_django import palm_model_saved, palm_resource_invoked
@receiver(palm_resource_invoked)
def on_resource_invoked(sender, provider, action, model_label, params, result, **kwargs):
audit_log.info("palm %s %s on %s", provider, action, model_label)
@receiver(palm_model_saved)
def on_model_saved(sender, instance, created, model_label, **kwargs):
if created:
notify_team(instance)
palm_model_saved fires for direct ORM saves on decorated models. Saves performed inside Palm provider mutations emit palm_resource_invoked instead (no duplicate model signal).
Storage
When palm_django is installed, django is the default storage_backend. Palm's key-value contract is preserved:
| Palm key pattern | Django model |
|---|---|
palm:definitions:{kind}:{id} |
PalmDefinition |
palm:instances:{instance_id} |
PalmProcessInstance (snapshots + status history in data) |
| Indexes, projections, outbox, other keys | PalmStorageEntry |
Override with PALM_STORAGE_BACKEND = "memory" or "filesystem" when needed.
Transaction bridging
palm_atomic() and internal django_atomic() join an existing transaction.atomic() block instead of opening redundant savepoints. Palm storage writes and model provider mutations roll back with surrounding Django work:
from django.db import transaction
from palm_django import get_app, palm_atomic
with transaction.atomic():
app.invoke_resource("myapp.order.create", state={...})
# raises → ORM row and Palm KV writes roll back together
Settings reference
Palm settings (PALM dict / PALM_* attrs)
All fields from PalmSettings are supported. Common ones:
| Key | Default (Django) | Notes |
|---|---|---|
storage_backend |
django |
Django ORM via palm_django models |
load_example_definitions |
False |
Avoid Palm demo definitions in production |
host_profile |
all_in_one |
Collapsed embedded runtime |
server_host |
127.0.0.1 |
Bind host for palm server |
server_port |
8080 |
Bind port for palm server |
default_scheduler |
inline |
Synchronous in-process execution |
palm-django integration settings
| Setting | Default | Description |
|---|---|---|
PALM_AUTO_START |
True |
Start ApplicationHost in AppConfig.ready() |
PALM_DISCOVERY_MODULES |
("palm_definitions", "palm") |
Module suffixes to scan per app |
PALM_DISCOVER_DEFINITIONS |
True |
Call register_definitions hooks |
PALM_DISCOVER_RESOURCES |
True |
Call register_resources hooks |
PALM_DISCOVER_COMMIT_HANDLERS |
True |
Call register_commit_handlers hooks |
Common patterns
| Goal | Approach |
|---|---|
| CRUD from flows/wizards | @as_palm_resource + step_kind: resource |
| Custom resource logic | register_resources() with ResourceDefinition |
| Atomic business transaction | with palm_atomic(): around ORM + Palm calls |
| React to Palm writes | @receiver(palm_resource_invoked) |
| React to direct ORM saves | @receiver(palm_model_saved) |
| Ops / debugging | python manage.py palm doctor |
| Browser Explorer | python manage.py palm server |
| New project bootstrap | python manage.py palm quickstart --app myapp |
Troubleshooting
| Symptom | Fix |
|---|---|
storage tables are missing |
python manage.py migrate palm_django |
ApplicationHost is not started |
Run migrations; check PALM_AUTO_START; run palm doctor |
| Resource not found | Use palm resource list; names are {app_label}.{model}.{action} |
Unknown Django model |
Use app_label.ModelName (e.g. myapp.Order), not dotted module path |
| Wizard resource step fails | Use step_kind: resource + resource_ref; set step action (e.g. create) — defaults to fetch if omitted |
| Flow not listed | Add myapp/palm_definitions.py with register_definitions() |
| Admin models missing | Add django.contrib.admin to INSTALLED_APPS |
| DB access during app init warning | Harmless during bootstrap before migrations; disappears after migrate |
python manage.py palm doctor # full report + next-step tips
python manage.py palm doctor --json # machine-readable output
Development
git clone https://github.com/JGabrielGruber/palm-django.git
cd palm-django
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
ruff check .
The tests/palm_sample/ app demonstrates discovery, model resources, wizard flows, commands, admin, signals, schemas, and transaction bridging.
Publishing (maintainers)
Same flow as palmengine: tag → GitHub release → CI publishes to PyPI.
Prerequisites: uv, just (optional), and repository secrets PYPI_TOKEN / TEST_PYPI_TOKEN.
# Bump version in pyproject.toml and palm_django/__init__.py; update CHANGELOG.md
just release-prep # ruff + pytest + uv build — see RELEASE-0.8.0.md
git add -A
git commit -m "Release 0.8.0"
git tag -a v0.8.0 -m "palm-django 0.8.0"
git push origin master --tags
On GitHub: Releases → Draft a new release from tag v0.8.0, paste the [0.8.0] section from CHANGELOG.md, and Publish release. The publish workflow uploads to PyPI automatically.
Manual options:
just publish-test # TestPyPI (TEST_PYPI_TOKEN)
just publish # production PyPI (PYPI_TOKEN)
Or trigger Actions → Publish to PyPI → Run workflow (testpypi or pypi) without creating a release.
Links
License
MIT — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file palm_django-0.8.1.tar.gz.
File metadata
- Download URL: palm_django-0.8.1.tar.gz
- Upload date:
- Size: 42.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d352cda67d6f7db9389b06f8067ac68946d5d826ca9b5143f6f266c573f877b6
|
|
| MD5 |
1bd56dbdec4d627f3d2156a22f706125
|
|
| BLAKE2b-256 |
2b92301960f4cb3b3cf6ceb8d46877d18843a14b1375ab3012622ffb647124ca
|
File details
Details for the file palm_django-0.8.1-py3-none-any.whl.
File metadata
- Download URL: palm_django-0.8.1-py3-none-any.whl
- Upload date:
- Size: 45.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c25ece92fd6abe508d901b9432a307f2c2f93112635e628189190087997c804b
|
|
| MD5 |
f74f60a11fcb1efd03ad536d64ef10a5
|
|
| BLAKE2b-256 |
6fab62d826f32a6a309e17ff61116fd68a5e6e85ac9b211758e4020fe7ce4599
|