Embedded Python SDK for building multi-tenant SaaS products with users, services, groups, and AI agents.
Project description
SaaSBase — Python SDK
Embedded Python SDK for building multi-tenant SaaS products with first-class support for users, services, groups, and AI agents.
Mirrors the Java SDK 1:1 in behavior and database schema, but with Pythonic snake_case naming and a familiar fluent builder.
Install
pip install saasbase # core (SQLite + local storage + in-memory authz)
pip install "saasbase[postgres]" # + Postgres support
pip install "saasbase[s3]" # + S3 storage
pip install "saasbase[openfga]" # + OpenFGA authz
pip install "saasbase[fastapi]" # + async FastAPI REST surface
pip install "saasbase[all]" # everything
Quick start
from saasbase import SaaSBase
sb = (
SaaSBase.builder()
.sqlite()
.local_storage("./data")
.build()
)
with sb.as_user("alice") as ctx:
org = ctx.organizations.create("acme")
project = ctx.projects.create(org.id, "alpha")
doc = ctx.documents.upload(
project.id,
"report.pdf",
content=open("report.pdf", "rb"),
media_type="application/pdf",
)
sb.close()
FastAPI integration (optional)
The saasbase.fastapi subpackage exposes every domain API over async HTTP, mirroring the Java
Spring Boot starter. Handlers are async def and offload blocking SaaSBase calls onto a thread
pool via anyio.to_thread.run_sync, so the event loop stays non-blocking under concurrent load.
from saasbase import SaaSBase
from saasbase.fastapi import create_app
sb = SaaSBase.builder().sqlite().local_storage("./data/documents").build()
app = create_app(sb)
# uvicorn my_module:app --reload
Or let the module build a SaaSBase from env vars (prefix SAASBASE_, double-underscore for
nested keys) — matches the Java starter's YAML property names:
export SAASBASE_DB_URL=jdbc:sqlite:./data/saasbase.db
export SAASBASE_STORAGE__LOCAL__BASE_PATH=./data/documents
export SAASBASE_WEB__BASE_PATH=/api
python -c "from saasbase.fastapi import create_app; app = create_app()"
Every request is attributed to an actor via the X-SaaSBase-Actor header, parsed as type:id:
curl -H "X-SaaSBase-Actor: user:alice" \
-H "Content-Type: application/json" \
-d '{"slug":"acme","name":"Acme Corp"}' \
http://localhost:8000/api/organizations
create_app(actor_resolver=...) accepts a custom async resolver (e.g. a JWT-aware one). When a
resolver returns None, the request falls back to saasbase.actor.anonymous (default
service:anonymous) — set it to empty to force 401 on unauthenticated requests.
Endpoints under /api (configurable): /organizations, /projects, /documents (incl.
multipart upload + stream download + versioning), /api-calls, /executions, /memberships,
/audit-events, /authz/check|explain|effective-roles|who-can|who-can-explain. Interactive
OpenAPI docs at /docs.
API keys
Each key binds to one resource (ORGANIZATION or PROJECT) and carries a single Role. When a
key authenticates, it acts as an API_KEY actor whose permissions are exactly the role's actions
on the scoped resource — and inherited scopes (e.g. an ORG_OWNER key can act on the org's
projects).
from saasbase import CreateApiKeyRequest, Role, SaaSBase
sb = SaaSBase.builder().sqlite().local_storage("./data").build()
with sb.as_user("alice") as ctx:
created = ctx.api_keys.create(
CreateApiKeyRequest(scope=project.ref, role=Role.PROJECT_EDITOR, name="ci")
)
print(created.token) # sbk_<prefix>.<secret> — only visible once
print(created.api_key.id, created.api_key.is_active)
Over HTTP (FastAPI), authenticate by passing the token as Authorization: Bearer sbk_...:
curl -H "Authorization: Bearer sbk_abcdef123456.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
http://127.0.0.1:8000/api/projects/$PROJECT_ID
Revocation is soft (POST /api/api-keys/{id}/revoke) — the key is kept for audit purposes and
immediately fails authentication. expires_at, last_used_at, and arbitrary metadata are also
supported.
Database schema
The SQL migration files in src/saasbase/sqlite/migrations/ and src/saasbase/postgres/migrations/ are kept byte-identical to the Java SDK's migrations so a single database can be used by both SDKs.
Module layout
src/saasbase/
├── common/ # shared enums, records, pagination, queries, exceptions
├── api/ # public API Protocols + request/response DTOs
├── spi/ # SPI record types + Protocols for backends
├── persistence/ # DB-API-backed repository implementations
├── sqlite/ # SQLite connection factory + migrations
├── postgres/ # Postgres connection factory + migrations
├── storage/ # local + S3 document storage adapters
├── authz_inmemory/ # in-memory authorization engine
├── authz_openfga/ # OpenFGA authorization engine
├── audit_inmemory/ # in-memory audit sink
├── core/ # default runtime (builder, SaaSBase/Context, API impls)
└── fastapi/ # optional async FastAPI app + routers
Examples
See examples/:
-
example_app.py— end-to-end demo (runs once and exits) -
document_centric.py— upload + version documents -
apicall_centric.py— API call tracking with correlation IDs -
agent_assisted.py— agent execution lifecycle -
document_versioning.py— named document versions -
fastapi_server.py— run the async REST API with uvicorn:pip install 'saasbase[fastapi]' uvicorn python examples/fastapi_server.py # serves http://127.0.0.1:8000/docs # or with auto-reload: uvicorn examples.fastapi_server:app --reload # in another shell: curl -H 'X-SaaSBase-Actor: user:alice' \ -H 'Content-Type: application/json' \ -d '{"slug":"acme","name":"Acme Corp"}' \ http://127.0.0.1:8000/api/organizations
Publishing to PyPI
Releases are published by the GitHub Actions workflow .github/workflows/publish-python.yml.
The workflow authenticates via PyPI Trusted Publishers (OIDC) — no API tokens are stored in the repo. A one-time setup is required on PyPI before the first run:
-
Create the project on PyPI (or TestPyPI) by uploading the first version manually with
twine, or register the name as a Pending Publisher: https://pypi.org/manage/account/publishing/. -
Add a Trusted Publisher (PyPI → project → Publishing → Add a new pending / trusted publisher) with these values:
Field Value Owner your GitHub org/user (e.g. sireto)Repository saasbaseWorkflow filename publish-python.ymlEnvironment pypi(andtestpypifor TestPyPI) -
Create matching GitHub environments under the repo's Settings → Environments:
pypiandtestpypi. Add reviewers on thepypienvironment if you want a manual approval gate.
Cutting a release
The version is derived from the git tag — there is nothing to bump in pyproject.toml.
hatch-vcs reads the nearest tag and stamps the built wheel
with it, also writing src/saasbase/_version.py so saasbase.__version__ returns the same value
at runtime.
- Push a GitHub Release whose tag is the version you're cutting. Both
v0.2.0and0.2.0are accepted and both produce PyPI version0.2.0. - The workflow: checks out with full history, resolves the version via
hatch version, verifies the tag matches it (catches stray commits between tag and HEAD), runs tests, builds sdist + wheel, and publishes to PyPI with Sigstore attestations attached.
Between releases, local builds resolve to a dev version such as 0.2.0.dev3+g<sha> based on the
commit distance from the last tag.
Dry-runs against TestPyPI
Run the workflow manually from the Actions tab → Publish Python package → Run workflow and
pick testpypi as the target.
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 saasbase-0.1.0.tar.gz.
File metadata
- Download URL: saasbase-0.1.0.tar.gz
- Upload date:
- Size: 58.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5828d3cc2f8873454c721d7e50ebcbf28aa2076dfc293fa232fe054a9cbc661
|
|
| MD5 |
e520a0868373e31ef61b97d6d927a378
|
|
| BLAKE2b-256 |
92e179de9c478edbb4215ef46d23cefe748e3c4753f9121983e3a73b8c9c6592
|
Provenance
The following attestation bundles were made for saasbase-0.1.0.tar.gz:
Publisher:
publish-python.yml on sireto/saasbase-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
saasbase-0.1.0.tar.gz -
Subject digest:
f5828d3cc2f8873454c721d7e50ebcbf28aa2076dfc293fa232fe054a9cbc661 - Sigstore transparency entry: 1343325500
- Sigstore integration time:
-
Permalink:
sireto/saasbase-sdk@d7d1b8c4f2b081959ad944573bc258680dfeb6fd -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sireto
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@d7d1b8c4f2b081959ad944573bc258680dfeb6fd -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file saasbase-0.1.0-py3-none-any.whl.
File metadata
- Download URL: saasbase-0.1.0-py3-none-any.whl
- Upload date:
- Size: 105.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c77fc205c11f645a47d60eb723857e520938b0dc4981af3cfa9984dbe727f8e5
|
|
| MD5 |
4e37f0927439d90d22da2e57385aceaa
|
|
| BLAKE2b-256 |
8424c44699d1d3bac8a88464b4ea450a060e722fff8b4e4f8e4c4a31c8dec0a7
|
Provenance
The following attestation bundles were made for saasbase-0.1.0-py3-none-any.whl:
Publisher:
publish-python.yml on sireto/saasbase-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
saasbase-0.1.0-py3-none-any.whl -
Subject digest:
c77fc205c11f645a47d60eb723857e520938b0dc4981af3cfa9984dbe727f8e5 - Sigstore transparency entry: 1343325507
- Sigstore integration time:
-
Permalink:
sireto/saasbase-sdk@d7d1b8c4f2b081959ad944573bc258680dfeb6fd -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sireto
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@d7d1b8c4f2b081959ad944573bc258680dfeb6fd -
Trigger Event:
workflow_dispatch
-
Statement type: