Testcontainers module for AT Protocol PDS integration testing
Project description
testcontainers-atproto
Spin up ephemeral PDS instances in your Python test suite. Lexicon-agnostic — works with any application built on AT Protocol, not just Bluesky.
Installation
pip install testcontainers-atproto
Requires Python 3.10+ and a running Docker daemon.
Extras
| Extra | What it adds |
|---|---|
testcontainers-atproto[firehose] |
websockets, cbor2 for firehose subscription |
testcontainers-atproto[sdk] |
atproto (MarshalX SDK) for high-level record ops |
testcontainers-atproto[all] |
Both of the above |
Quick start
from testcontainers_atproto import PDSContainer
with PDSContainer() as pds:
account = pds.create_account("alice.test")
print(pds.base_url) # http://localhost:<port>
print(account.did) # did:plc:...
print(account.handle) # alice.test
A local PLC directory runs alongside the PDS on a shared Docker network — no public internet required. For Postgres-backed PLC parity with production, pass plc_mode="real":
with PDSContainer(plc_mode="real") as pds:
account = pds.create_account("alice.test")
Record operations
with PDSContainer() as pds:
alice = pds.create_account("alice.test")
# Create
ref = alice.create_record("app.bsky.feed.post", {
"$type": "app.bsky.feed.post",
"text": "hello from testcontainers",
"createdAt": "2026-01-01T00:00:00Z",
})
# Read
record = alice.get_record("app.bsky.feed.post", ref.rkey)
# Update
alice.put_record("app.bsky.feed.post", ref.rkey, {
"$type": "app.bsky.feed.post",
"text": "updated text",
"createdAt": "2026-01-01T00:00:00Z",
})
# List & delete
records = alice.list_records("app.bsky.feed.post")
alice.delete_record("app.bsky.feed.post", ref.rkey)
Firehose subscription
Observe real-time repository events via the AT Protocol firehose:
from testcontainers_atproto import PDSContainer
with PDSContainer() as pds:
alice = pds.create_account("alice.test")
alice.create_record("app.bsky.feed.post", {
"$type": "app.bsky.feed.post",
"text": "hello firehose",
"createdAt": "2026-01-01T00:00:00Z",
})
sub = pds.subscribe()
events = sub.collect(count=10, timeout=5.0)
commits = [e for e in events if e["header"].get("t") == "#commit"]
print(commits[-1]["body"]["ops"]) # [{"action": "create", ...}]
Requires the firehose extra: pip install testcontainers-atproto[firehose]
Error handling
XRPC failures raise XrpcError with structured fields:
from testcontainers_atproto import PDSContainer, XrpcError
with PDSContainer() as pds:
try:
pds.create_account("alice.invalid")
except XrpcError as e:
print(e.status_code) # 400
print(e.error) # "InvalidHandle"
print(e.message) # human-readable detail
Pytest fixtures
After installing the package, these fixtures are available automatically via the pytest11 entry point:
| Fixture | Scope | Description |
|---|---|---|
pds |
function | Fresh PDS instance per test |
pds_module |
module | Shared PDS instance within a test module |
pds_pair |
function | Two PDS instances for federation testing |
pds_image |
session | PDS image tag (override via ATP_PDS_IMAGE env var) |
def test_create_account(pds):
account = pds.create_account("bob.test")
assert account.did.startswith("did:plc:")
Development
make venv # Create virtual environment
source .testcontainers-atproto-3.12/bin/activate # Activate
make test # Run tests
make test-all # Run across all supported Python versions
Glossary
AT Protocol introduces many domain-specific terms. See docs/glossary.md for definitions of PDS, DID, PLC, XRPC, and other initialisms used in this project.
License
Apache-2.0. See LICENSE.
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 testcontainers_atproto-0.3.0.tar.gz.
File metadata
- Download URL: testcontainers_atproto-0.3.0.tar.gz
- Upload date:
- Size: 136.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd3f11c130c321c5de76901c4711a61ebd3a0f9d46e64fba940391cc4aae0a2a
|
|
| MD5 |
9f647bcc90ddbc310c8c1327bbdc87f9
|
|
| BLAKE2b-256 |
f225598e006d70230a71823acf225d072aea5570292ed38e039e9d99e10925fe
|
File details
Details for the file testcontainers_atproto-0.3.0-py3-none-any.whl.
File metadata
- Download URL: testcontainers_atproto-0.3.0-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a743a5c8ab487e047b1f5a80143cbad503e0d0c11532bf3d16b796b8f82b372e
|
|
| MD5 |
bab4ee538ea1091fe825577e3079cd7e
|
|
| BLAKE2b-256 |
0b5792eb619bb96a1a14cbb33aea35f448e0e34b9e4e3e09ad32011ae4d30050
|