Metadata-Version: 2.4
Name: testcontainers-atproto
Version: 0.2.0
Summary: Testcontainers module for AT Protocol PDS integration testing
Author-email: "Emmanuel I. Obi" <withtwoemms@gmail.com>
License: Apache-2.0
Project-URL: Repository, https://github.com/withtwoemms/testcontainers-atproto
Project-URL: Issues, https://github.com/withtwoemms/testcontainers-atproto/issues
Keywords: atproto,bluesky,pds,testcontainers,testing,integration
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: testcontainers>=4.0
Requires-Dist: httpx>=0.24
Provides-Extra: firehose
Requires-Dist: websockets>=12.0; extra == "firehose"
Requires-Dist: cbor2>=5.0; extra == "firehose"
Provides-Extra: sdk
Requires-Dist: atproto>=0.0.50; extra == "sdk"
Provides-Extra: all
Requires-Dist: testcontainers-atproto[firehose,sdk]; extra == "all"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Requires-Dist: coverage>=7.0; extra == "test"
Requires-Dist: testcontainers-atproto[all]; extra == "test"
Dynamic: license-file

# testcontainers-atproto

[![tests](https://github.com/withtwoemms/testcontainers-atproto/workflows/tests/badge.svg)](https://github.com/withtwoemms/testcontainers-atproto/actions?query=workflow%3Atests)
[![codecov](https://codecov.io/gh/withtwoemms/testcontainers-atproto/graph/badge.svg)](https://codecov.io/gh/withtwoemms/testcontainers-atproto)
[![publish](https://github.com/withtwoemms/testcontainers-atproto/workflows/publish/badge.svg)](https://github.com/withtwoemms/testcontainers-atproto/actions?query=workflow%3Apublish)

> Spin up ephemeral PDS instances in your Python test suite. Lexicon-agnostic — works with any application built on [AT Protocol](https://atproto.com), not just Bluesky.

---

## Installation

```bash
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

```python
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"`:

```python
with PDSContainer(plc_mode="real") as pds:
    account = pds.create_account("alice.test")
```

### Record operations

```python
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)
```

### Error handling

XRPC failures raise `XrpcError` with structured fields:

```python
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) |

```python
def test_create_account(pds):
    account = pds.create_account("bob.test")
    assert account.did.startswith("did:plc:")
```

---

## Development

```bash
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](./docs/glossary.md) for definitions of PDS, DID, PLC, XRPC, and other initialisms used in this project.

---

## License

Apache-2.0. See [LICENSE](./LICENSE).
