Azure Cosmos DB (NoSQL API) database adapter for the Protean framework
Project description
protean-cosmosdb
Azure Cosmos DB (NoSQL / Core API) database adapter for the
Protean framework. Built against
Protean 0.16 and azure-cosmos 4.x.
Install
pip install protean-cosmosdb # brings in protean + azure-cosmos
The provider self-registers via the protean.providers entry point, so it's
available under the name cosmosdb once installed.
Configure
# domain.toml
[databases.default]
provider = "cosmosdb"
database_uri = "https://<account>.documents.azure.com:443/"
key = "<primary-key>"
database = "myapp" # optional, default "protean"
throughput = 400 # optional RU/s for created containers
domain.providers["default"]._create_database_artifacts() # create db + containers
Quickstart
After pip install protean-cosmosdb, no registration code is needed — the
provider is discovered automatically. Just name it in config and use the
repository:
from protean.domain import Domain
from protean.fields import String
domain = Domain(name="MyApp")
domain.config["databases"]["default"] = {
"provider": "cosmosdb", # auto-discovered, no import needed
"database_uri": "https://<account>.documents.azure.com:443/",
"key": "<primary-key>",
"database": "myapp",
}
@domain.aggregate
class Note:
text = String(max_length=100)
domain.init(traverse=False)
with domain.domain_context():
domain.providers["default"]._create_database_artifacts() # one-time setup
repo = domain.repository_for(Note)
note = Note(text="hello")
repo.add(note)
assert repo.get(note.id).text == "hello" # point read
Design
| Component | Maps to |
|---|---|
| Provider | CosmosClient, one Cosmos database, one container per aggregate/entity |
| DAO | create_item / read_item / replace_item / delete_item / query_items |
| Model | entity ⇄ JSON item (dict-based) |
| Lookups | Protean filter ops → Cosmos SQL (=, CONTAINS, ARRAY_CONTAINS, STARTSWITH, …) |
Partition key. Defaults to the aggregate's id. This is the principled
mapping, not a shortcut: Protean's consistency boundary is the aggregate, and
Cosmos's consistency boundary is the logical partition — partitioning by id
makes them coincide, and repository.get(id) becomes a cheap single-partition
point read. Override per aggregate with a custom model:
@domain.database_model(part_of=Order)
class OrderModel:
_partition_key = "tenant_id" # container partitioned by /tenant_id
Identity & types. id is stored as a string (Cosmos requires it); UUIDs,
datetimes, dates, Decimals and Enums are JSON-coerced on write and restored on
read. Entity _version is stored as entity_version to avoid colliding with
Cosmos system fields.
Optimistic locking. Aggregate updates are guarded with the item's _etag
via If-Match, so a concurrent write is rejected with ExpectedVersionError
rather than silently overwritten.
Protean compliance
Verified against Protean's official adapter conformance suite (the same generic test battery its built-in adapters run), executed against the Linux Cosmos emulator:
147 passed, 5 skipped
The only 5 skips are the transaction tests (transactions,
atomic_transactions) — genuinely impossible on Cosmos, which has no
cross-document transactions, so the capability isn't declared and the plugin
skips them. Everything else passes: CRUD, filtering, ordering, bulk
operations, raw queries, schema management, optimistic locking, value
objects, associations, complex fields, persistence, querysets, and native
JSON / array storage. This same suite runs in CI on every push (see
.github/workflows/ci.yml), with the Cosmos emulator as a service.
Running the conformance suite
The suite ships in Protean's source tree (not the wheel). To run it against this adapter:
# 1. Protean source at the matching version provides the generic tests
git clone --branch v0.16.0 https://github.com/proteanhq/protean.git
GEN="protean/tests/adapters/repository/generic"
# 2. A conftest that loads the official plugin and points it at Cosmos:
cat > "$GEN/conftest_cosmos.py" <<'PY'
# (see tests/conformance_conftest.py in this repo)
PY
# 3. Run with the emulator up:
export COSMOS_ENDPOINT="http://localhost:8081/" COSMOS_KEY="<emulator-key>"
pytest "$GEN" -p no:cacheprovider
tests/conformance_conftest.py in this repo is the ready-made conftest
(loads protean.integrations.pytest.adapter_conformance and supplies the
Cosmos db_config); copy it next to the generic tests, isolated from
Protean's in-tree conftests.
Contract coverage
Implements the full adapter contract for protean 0.16:
- All 9 DAO abstract methods:
_filter(withwith_total+only()column projection),_create,_update,_update_all,_delete,_delete_all,_count,_raw,has_table. - All 12 provider abstract methods + the
capabilitiesproperty.validate_lookups()reports no missing lookups. - All 12 required lookups: exact, iexact, contains, icontains, startswith, endswith, gt, gte, lt, lte, in, isnull.
- Bulk / batch surface:
QuerySet.update()→_update_all,QuerySet.delete()→_delete_all, and the outbox primitives_delete_top(bounded batch delete) and_claim(atomic select-and-stamp, via etag-conditional replace) — all exercised in the live suite. - Raw queries:
provider.raw()andQuerySet.raw()run Cosmos SQL (parameterized) and return raw results / entities respectively. - Capabilities =
DOCUMENT_STORE | RAW_QUERIES | NATIVE_JSON | NATIVE_ARRAY: CRUD, FILTER, BULK_OPERATIONS, ORDERING, SCHEMA_MANAGEMENT, OPTIMISTIC_LOCKING, RAW_QUERIES, NATIVE_JSON, NATIVE_ARRAY.
Covered by the conformance suite: value objects, aggregate associations, and native nested JSON / array fields.
Performance
repository.get(id) is served by a Cosmos point read (read_item by id +
partition key) — the cheapest operation (~1 RU) — rather than a
cross-partition query. _filter detects a sole id == value lookup on an
id-partitioned container and takes this fast path automatically; all other
queries go through the SQL path.
Known ceilings
- No transactions / rollback. Cosmos has no cross-document transactions, so
the provider declares
DOCUMENT_STOREcapabilities (noTRANSACTIONS). A Unit of Work gives copy-forward semantics, not rollback — same as the Elasticsearch adapter. _filterruns a separateCOUNTquery to populate the total for pagination; callers that passwith_total=Falseskip it to save RUs. (get(id)bypasses this entirely via the point-read fast path above.)- Bulk
update_all/delete_allloop client-side (Cosmos has no server-side update-by-query)._claim, by contrast, is atomic: it uses an etag-conditional replace per row, so a concurrent consumer that loses the race is rejected (412) and skips — no double-claim.
Test
pytest tests/ # pure logic, no Cosmos needed
python tests/test_cosmosdb.py # same checks, plain CLI
The live suite (test_live_*) is opt-in and runs the full CRUD + every
lookup + ordering + pagination + optimistic-locking path against a real
endpoint. It auto-skips unless COSMOS_ENDPOINT and COSMOS_KEY are set.
Verified against the Linux Cosmos emulator (NoSQL API, gateway mode):
docker run --detach --name cosmos-emu \
-p 8081:8081 -p 8080:8080 -p 1234:1234 \
mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-latest
# wait for readiness, then run:
curl -s http://localhost:8080/ready # 200 when ready
export COSMOS_ENDPOINT="http://localhost:8081/"
export COSMOS_KEY="C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
pytest tests/ -q
The emulator defaults to HTTP, which the Python SDK supports directly (only
the .NET/Java SDKs require HTTPS + certificate install). Note the emulator
treats Request Units / throughput as a no-op, so offer_throughput is
accepted but not enforced there.
Releasing to PyPI
Publishing is automated via PyPI Trusted Publishing (OIDC) — no API
tokens are stored in the repo. The .github/workflows/release.yml workflow
builds and publishes on any v* tag.
One-time setup on PyPI (project owner):
- Create the project on PyPI (or let the first trusted-publishing run create it via a pending publisher).
- On PyPI → the project → Publishing, add a GitHub trusted publisher:
- Owner:
sachyyn, Repository:protean-cosmosdb - Workflow:
release.yml, Environment:pypi
- Owner:
- In the GitHub repo, create an environment named
pypi(Settings → Environments) to match.
Then cut a release:
# bump `version` in pyproject.toml first, then:
git tag v0.1.0
git push origin v0.1.0 # triggers build + publish
Locally, python -m build && python -m twine check dist/* reproduces exactly
what CI builds and validates before upload.
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 protean_cosmosdb-0.1.0.tar.gz.
File metadata
- Download URL: protean_cosmosdb-0.1.0.tar.gz
- Upload date:
- Size: 18.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2d2aab244fcb38f8d10560e3e508fbf5de52d5294544443481b3cd82e1637cd
|
|
| MD5 |
d721dddc4884701fa02893f612a1d104
|
|
| BLAKE2b-256 |
8aa1daaf286a9c9f570bb34e544a9b676fda822e4ddadb2b968fe4977a9e9487
|
Provenance
The following attestation bundles were made for protean_cosmosdb-0.1.0.tar.gz:
Publisher:
release.yml on sachyyn/protean-cosmosdb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
protean_cosmosdb-0.1.0.tar.gz -
Subject digest:
d2d2aab244fcb38f8d10560e3e508fbf5de52d5294544443481b3cd82e1637cd - Sigstore transparency entry: 2020012329
- Sigstore integration time:
-
Permalink:
sachyyn/protean-cosmosdb@880947626bb5f662b3c4abbccc5febdc7c233e60 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/sachyyn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@880947626bb5f662b3c4abbccc5febdc7c233e60 -
Trigger Event:
push
-
Statement type:
File details
Details for the file protean_cosmosdb-0.1.0-py3-none-any.whl.
File metadata
- Download URL: protean_cosmosdb-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.7 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 |
44f596b74a43932690eb9fd11b304073ccbe8acb043f36d70b985db0079ab4ac
|
|
| MD5 |
f228e5e5699afd17cfc8adfb9ef15267
|
|
| BLAKE2b-256 |
0be642145755d21fe4051781c19e41fdc2a617c9996b9b0abf8d9a4c32997f74
|
Provenance
The following attestation bundles were made for protean_cosmosdb-0.1.0-py3-none-any.whl:
Publisher:
release.yml on sachyyn/protean-cosmosdb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
protean_cosmosdb-0.1.0-py3-none-any.whl -
Subject digest:
44f596b74a43932690eb9fd11b304073ccbe8acb043f36d70b985db0079ab4ac - Sigstore transparency entry: 2020012445
- Sigstore integration time:
-
Permalink:
sachyyn/protean-cosmosdb@880947626bb5f662b3c4abbccc5febdc7c233e60 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/sachyyn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@880947626bb5f662b3c4abbccc5febdc7c233e60 -
Trigger Event:
push
-
Statement type: