Foundation Service
This package contains the hosted Foundation Service executable, its internal async-first storage substrate, and its service-owned relational schema lifecycle. It is a workspace package, not a public SDK or an independently distributed provider library.
The substrate exposes capability-specific interfaces instead of one generic storage facade:
- SQL uses SQLAlchemy's async
AsyncEngineandAsyncSessionAPIs with PostgreSQL or SQLite. - Redis-compatible data structures use
redis.asyncio.Rediswith Redis or process-local fakeredis. - Objects use the small Foundation-owned
ObjectStoreprotocol with S3-compatible or local-directory adapters. - Local files and deployment-mounted NFS use the same confined
pathliband AnyIO helpers.
Backend selection happens once during process startup. A failed network backend never falls back to local state.
Runtime
ServiceSettings owns the FOUNDATION_* environment contract and maps it to the frozen StorageSettings model. The storage package accepts typed configuration and does not read process environment variables itself. foundation-service serve constructs all selected providers once in FastAPI lifespan, publishes the resulting StorageResources on app.state.storage, and closes the resources during shutdown.
The default service profile keeps the existing PostgreSQL and Redis endpoints and uses separate local roots for objects and files. Set FOUNDATION_OBJECT_BACKEND=s3 and FOUNDATION_OBJECT_BUCKET for a multi-process deployment; the local object adapter supports only one writing process. FOUNDATION_FILESYSTEM_ROOT may be an ordinary local directory or an NFS mount prepared by deployment.
from pathlib import Path
from a13n_service.storage import StorageSettings, open_storage
settings = StorageSettings.model_validate(
{
"database": {"backend": "sqlite", "path": Path("var/foundation.sqlite3")},
"redis": {"backend": "memory"},
"objects": {"backend": "local", "root": Path("var/objects")},
"filesystem": {"root": Path("var/files")},
}
)
async with open_storage(settings) as storage:
# Inject `storage` into application-owned services here.
...
open_storage() constructs one engine, session factory, Redis client, object store, and filesystem boundary. It checks required capabilities before yielding and closes all resources in reverse order.
A network deployment selects the corresponding backends without changing consumer code:
network_settings = StorageSettings.model_validate(
{
"database": {
"backend": "postgresql",
"url": "postgresql://foundation:password@postgres/foundation",
},
"redis": {"backend": "redis", "url": "redis://redis:6379/0"},
"objects": {
"backend": "s3",
"bucket": "foundation-objects",
"region": "us-east-1",
},
"filesystem": {"root": Path("/mnt/foundation-files")},
}
)
The S3 client uses the standard AWS credential chain. Set endpoint_url and force_path_style only for a compatible non-AWS endpoint. The deployment mounts NFS at the configured filesystem root before the process starts.
Relational Usage
Consumers use SQLAlchemy directly. Generic storage does not define get, insert, or do wrappers.
from sqlalchemy import select
from a13n_service.storage import transaction
async with transaction(storage.sessions) as session:
result = await session.execute(select(Record).where(Record.id == record_id))
record = result.scalar_one_or_none()
Each operation gets a short session. Never retain a session or transaction across external I/O, agent execution, sleeps, background work, or a streaming response.
PostgreSQL is the distributed-service backend. SQLite is intended for a single-process, zero-service profile and must not be placed on NFS.
Relational Schema and Migrations
Generic relational storage and service schema ownership are deliberately separate:
storage/relational.pyconstructs async engines and short sessions for application I/O.database/metadata.pyaggregates every service-owned ORM model.database/migrations/contains one linear Alembic history for the complete service database.database/migration.pyowns the dedicated synchronous migration connection, bounded PostgreSQL advisory locking, and Alembic invocation.
The Alembic environment does not read process settings or create an engine. The runner supplies one validated connection, so CLI settings, lock policy, and schema comparison have distinct owners.
Select the database backend, then use the stable service CLI or repository commands:
FOUNDATION_DATABASE_BACKEND=postgresql
FOUNDATION_DATABASE_URL=postgresql+psycopg://foundation:foundation@127.0.0.1:5432/foundation
make db-upgrade
make db-current
make db-check
make db-history
The corresponding executable commands are foundation-service db upgrade, foundation-service db current --check-heads, foundation-service db history, and foundation-service db migrate "description". There is one process CLI; migration implementation remains in database/migration.py rather than introducing a second database-only settings or command layer.
For the zero-service profile, set FOUNDATION_DATABASE_BACKEND=sqlite and FOUNDATION_DATABASE_SQLITE_PATH=var/foundation.sqlite3. The same accepted history is applied to both backends. A domain requiring PostgreSQL-only schema behavior must reject SQLite explicitly.
Add an ORM Model
- Define the model beside its owning domain using
a13n_service.database.Base. - Import that domain model module explicitly in
database/metadata.py; there is no package scanning or plugin discovery. - Run
make db-migrate msg="describe the schema change". The target rebuilds accepted history in a disposable PostgreSQL database before autogeneration. - Review the generated revision for names, constraints, data loss, lock behavior, rolling compatibility, interruption safety, and downgrade or forward repair.
- Run the database tests on SQLite and PostgreSQL plus
make db-checkagainst an upgraded database.
Do not create revision files by hand and do not use runtime metadata.create_all() as schema bootstrap. Application request paths use async SQLAlchemy; migrations use a separate synchronous NullPool connection because they run before traffic or in a dedicated deployment job.
Redis Usage
The injected async redis-py client exposes strings, hashes, lists, sets, sorted sets, Streams, pipelines, transactions, and Pub/Sub without local/network branches.
await storage.redis.hset(b"run:1", mapping={b"status": b"running"})
await storage.redis.rpush(b"run:1:steps", b"step-1")
await storage.redis.xadd(b"run-events", {b"payload": payload})
Response decoding is disabled for binary safety. The fakeredis backend is process-local and non-durable; use a real Redis service for multiple processes, persistence, modules, or exact server failure behavior.
Object Usage
Object keys are opaque names rather than filesystem paths. put supports unconditional, create-only, and expected-version publication.
from a13n_service.storage import ByteRange, ObjectConflict
created = await storage.objects.put(
"artifacts/result.json",
payload,
content_type="application/json",
metadata={"trace": trace_id},
if_none_match=True,
)
async with storage.objects.open("artifacts/result.json", byte_range=ByteRange(0, 64)) as reader:
prefix = b"".join([chunk async for chunk in reader])
try:
updated = await storage.objects.put(
"artifacts/result.json",
new_payload,
if_match=created.version,
)
except ObjectConflict:
# Re-read and make a new domain decision.
...
S3 endpoints must support AWS-compatible conditional writes and deletes, range reads, head requests, and ordered ListObjectsV2 pagination. Startup rejects endpoints that silently ignore these conditions. The local adapter provides the common behavior for exactly one writing service process.
Metadata keys are normalized to lowercase and, like S3 REST metadata, keys and values must be ASCII. Keys must also be valid HTTP field names. Returned metadata mappings are immutable.
Filesystem Usage
Deployment mounts NFS before process startup. Application code receives the mounted root and uses the same helpers as a local directory; there is no Python NFS provider.
import anyio
from a13n_service.storage.filesystem import atomic_write, resolve_under_root
destination = await resolve_under_root(
storage.files_root,
"exports/result.json",
limiter=storage.file_limiter,
)
await atomic_write(destination, chunks, limiter=storage.file_limiter)
async with await anyio.open_file(destination, "rb") as file:
prefix = await file.read(64 * 1024)
Confined resolution rejects absolute paths, parent traversal, and symlink escape. Atomic replacement is guaranteed only within one filesystem.
Verification
Run the infrastructure suites and package checks from the repository root:
uv run --package a13n-service pytest packages/foundation-service/tests/storage packages/foundation-service/tests/database -q
make lint
make typecheck
uv build --package a13n-service
Container-owned integration tests exercise PostgreSQL, Redis, and S3 HTTP behavior. The S3 startup-probe test also demonstrates that an endpoint missing required conditional-delete semantics is rejected rather than silently accepted.
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 a13n_service-0.0.3.tar.gz.
File metadata
- Download URL: a13n_service-0.0.3.tar.gz
- Upload date:
- Size: 41.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","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 |
58850e047af5cab5868ab66b92b9dcd715559075b3d8321bdaa5adfe791c4401
|
|
| MD5 |
d6f135c23b5ec8b411d5fe4164ba2957
|
|
| BLAKE2b-256 |
7b37da10bd6b38205a6abc03e5fcd5543bfa42ce18acd7bcf825c75ca2b4c237
|
File details
Details for the file a13n_service-0.0.3-py3-none-any.whl.
File metadata
- Download URL: a13n_service-0.0.3-py3-none-any.whl
- Upload date:
- Size: 40.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","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 |
d2eee1797b3b4acfe9b85ed01e23083701a56f173108d029d1c7d7c11030b04c
|
|
| MD5 |
f217c33f4d9da66058a7d53af9c27542
|
|
| BLAKE2b-256 |
ecfcd76adefceb7cc63d36664d76c7db808ba3b81adead2fff39b40a6aed259a
|