WeltenHub Client Framework — typed REST client and Pydantic schemas for the WeltenHub Story Universe API
Project description
weltenfw — v0.3.0
WeltenHub Client Framework — typed REST client, Pydantic v2 schemas, and a pluggable storage-backend pattern for the WeltenHub Story Universe API.
Installation
pip install iil-weltenfw # core (httpx + pydantic)
pip install iil-weltenfw[django] # + Django AppConfig + DjangoCache
Quick Start — Low-Level Client
from weltenfw import WeltenClient
with WeltenClient(
base_url="https://weltenforger.com/api/v1",
token="your-token",
) as client:
world = client.worlds.create(WorldCreateInput(
name="Aldoria", setting_era="medieval",
))
chars = client.characters.list(world=str(world.id))
locs = client.locations.list(world=str(world.id))
stories = client.stories.list(world=str(world.id))
scenes = client.scenes.list(story=str(stories.results[0].id))
Async
async with WeltenClient(base_url=..., token=...) as client:
world = await client.worlds.aget(world_id)
locs = await client.locations.alist(world=world_id)
Django Integration
# settings.py
INSTALLED_APPS = [..., "weltenfw.django"]
WELTENHUB_URL = "https://weltenforger.com/api/v1"
WELTENHUB_TOKEN = env("WELTENHUB_TOKEN")
# services.py
from weltenfw.django import get_client
client = get_client() # lazy singleton, one per worker
Storage Backend Pattern (ADR-117)
weltenfw ships WeltenhubBackend — a higher-level write/read facade
on top of WeltenClient. Consumer apps (bfagent, travel-beat, …) use this
to keep Weltenhub as the single source of truth without duplicating data.
Two Scenarios — same API
from weltenfw.backends import WeltenhubBackend
backend = WeltenhubBackend(
base_url="https://weltenforger.com/api/v1",
token=token, # user token (A) or service token (B)
)
Scenario A — user has Weltenhub account:
token = user.weltenhub_token → data visible in Weltenhub UI immediately.
Scenario B — user not yet linked:
token = WELTENHUB_API_KEY (service token) → data stored, UUID returned,
UI locked until user links their account. No duplication; consumer stores only UUID.
Backend API Reference
Worlds
| Method | Signature | Returns |
|---|---|---|
create_world |
(name, description, setting_era, **kw) |
WorldResult |
get_world |
(world_id) |
WorldResult |
list_worlds |
(search, page) |
WorldPage |
update_world |
(world_id, **fields) |
WorldResult |
result = backend.create_world(name="Aldoria", description="Kingdom of runes")
result.id # Weltenhub UUID
result.ok # True on success
result.error # str | None
result.backend # "weltenhub"
Characters
| Method | Signature | Returns |
|---|---|---|
create_character |
(world_id, name, personality, backstory, is_protagonist, **kw) |
CharacterResult |
get_character |
(character_id) |
CharacterResult |
list_characters |
(world_id, page) |
CharacterPage |
chars = backend.list_characters(world_id="<uuid>")
for c in chars.results:
print(c.name, c.role_name)
Locations
| Method | Signature | Returns |
|---|---|---|
create_location |
(world_id, name, description, parent_id, **kw) |
LocationResult |
list_locations |
(world_id, page, page_size) |
LocationPage |
locs = backend.list_locations(world_id="<uuid>")
for loc in locs.results:
print(loc.name, loc.location_type_name, loc.full_path)
new_loc = backend.create_location(
world_id="<uuid>", name="Eisenhain",
description="A fortress city in the northern mountains.",
)
Stories
| Method | Signature | Returns |
|---|---|---|
create_story |
(world_id, title, synopsis, **kw) |
StoryResult |
list_stories |
(world_id, page, page_size) |
StoryPage |
stories = backend.list_stories(world_id="<uuid>")
for s in stories.results:
print(s.title, s.status)
story = backend.create_story(world_id="<uuid>", title="Der Ruf der Runen")
Scenes
| Method | Signature | Returns |
|---|---|---|
create_scene |
(story_id, title, summary, order, **kw) |
SceneResult |
list_scenes |
(story_id, page, page_size) |
ScenePage |
scenes = backend.list_scenes(story_id="<uuid>")
for s in scenes.results:
print(s.title, s.location_name, s.order)
Result Types
All result types are frozen dataclasses with an .ok property:
| Type | Key fields |
|---|---|
WorldResult |
id, name, description, setting_era, genre_name |
CharacterResult |
id, name, world_id, role_name, description, personality |
LocationResult |
id, name, world_id, parent_id, location_type_name, description, full_path |
SceneResult |
id, title, story_id, summary, location_name, order |
StoryResult |
id, title, world_id, genre_name, status, synopsis |
All *Page types have .results: list[*Result] and .count: int.
User Provisioning (S2S, idempotent)
token = WeltenhubBackend.provision_user(
username="bf_hugo",
email="hugo@example.com",
base_url="https://weltenforger.com/api/v1",
service_token=WELTENHUB_API_KEY,
)
# Returns per-user token (Scenario A) or None on failure
LocalWorldBackend (tests / offline)
from weltenfw.backends.local import LocalWorldBackend
backend = LocalWorldBackend()
result = backend.create_world(name="Test")
# result.id = "" — caller manages local DB
# result.backend = "local"
Custom Backend (Protocol)
Implement AbstractWorldBackend without inheriting — structural subtyping:
from weltenfw.backends.base import AbstractWorldBackend, WorldResult
class MyBackend:
def create_world(self, name: str, **kw) -> WorldResult: ...
def get_world(self, world_id: str) -> WorldResult: ...
def list_worlds(self, search: str = "", page: int = 1): ...
def update_world(self, world_id: str, **fields): ...
def create_character(self, world_id, name, **kw): ...
def get_character(self, character_id): ...
def list_characters(self, world_id, page=1): ...
def list_locations(self, world_id, page=1, page_size=100): ...
def create_location(self, world_id, name, **kw): ...
def list_stories(self, world_id, page=1, page_size=100): ...
def create_story(self, world_id, title, **kw): ...
def list_scenes(self, story_id, page=1, page_size=100): ...
def create_scene(self, story_id, title, **kw): ...
Architecture
- 1 Client = 1 Token = 1 Tenant — no global singleton; multi-tenant callers instantiate one client per token.
- Pydantic v2 — all API responses validated into typed, frozen schemas.
- Separate Input schemas —
*CreateInput(POST) /*UpdateInput(PATCH). - Lookup cache — pluggable
CacheBackend(NullCache → MemoryCache → DjangoCache). - Storage Backend Pattern — Weltenhub-DB is SSoT; consumer apps store UUID only.
Changelog
See CHANGELOG.md.
Links
Project details
Release history Release notifications | RSS feed
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 iil_weltenfw-0.3.0.tar.gz.
File metadata
- Download URL: iil_weltenfw-0.3.0.tar.gz
- Upload date:
- Size: 34.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
afd281bb31449ecbac7a1213afa69d3bcb3947bb6767a60078c459ecd370e033
|
|
| MD5 |
b4fa4b63a8ea54e2374c7fa2137d05a7
|
|
| BLAKE2b-256 |
78bbce440386432dab8b0c06277fd4e6b40218df7beb94a9ea25f1db1ef8d70e
|
File details
Details for the file iil_weltenfw-0.3.0-py3-none-any.whl.
File metadata
- Download URL: iil_weltenfw-0.3.0-py3-none-any.whl
- Upload date:
- Size: 34.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0cdf779e06e7857cd318b041b34be0a90bb0a999c6db7cb8a883833eb6ecb417
|
|
| MD5 |
b6489cfef346279a8b3ffa619f6853d9
|
|
| BLAKE2b-256 |
1bfe5c06ec7e1fa8c6bec0e525aa72f8dadcb7ca5a7073fe8b1137191aeabb2d
|