opedd 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opedd-0.1.0/.github/workflows/ci.yml +43 -0
- opedd-0.1.0/.gitignore +55 -0
- opedd-0.1.0/CHANGELOG.md +40 -0
- opedd-0.1.0/LICENSE +21 -0
- opedd-0.1.0/PKG-INFO +212 -0
- opedd-0.1.0/README.md +180 -0
- opedd-0.1.0/RELEASE.md +106 -0
- opedd-0.1.0/pyproject.toml +65 -0
- opedd-0.1.0/src/opedd/__init__.py +173 -0
- opedd-0.1.0/src/opedd/_exceptions.py +80 -0
- opedd-0.1.0/src/opedd/_http.py +143 -0
- opedd-0.1.0/src/opedd/audit.py +68 -0
- opedd-0.1.0/src/opedd/compliance.py +88 -0
- opedd-0.1.0/src/opedd/content.py +50 -0
- opedd-0.1.0/src/opedd/feed.py +133 -0
- opedd-0.1.0/src/opedd/licenses.py +94 -0
- opedd-0.1.0/tests/__init__.py +0 -0
- opedd-0.1.0/tests/conftest.py +23 -0
- opedd-0.1.0/tests/test_integration.py +95 -0
- opedd-0.1.0/tests/test_unit.py +384 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
unit-tests:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install package + dev deps
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install -e ".[dev]"
|
|
28
|
+
|
|
29
|
+
- name: Lint with ruff
|
|
30
|
+
run: ruff check src/ tests/
|
|
31
|
+
|
|
32
|
+
- name: Type-check with mypy
|
|
33
|
+
run: mypy src/
|
|
34
|
+
|
|
35
|
+
- name: Run unit tests
|
|
36
|
+
run: pytest tests/test_unit.py -v
|
|
37
|
+
|
|
38
|
+
# Integration tests intentionally NOT run in CI.
|
|
39
|
+
# Per Phase 11 M6 ratification 6: SDK live tests running autonomously in CI
|
|
40
|
+
# could pollute usage_records, distort metered-publisher-payouts aggregation,
|
|
41
|
+
# fire production API calls with founder identity at scale. Integration suite
|
|
42
|
+
# is documented in RELEASE.md as "run locally with your JWT before each SDK
|
|
43
|
+
# release."
|
opedd-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
share/python-wheels/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.installed.cfg
|
|
22
|
+
*.egg
|
|
23
|
+
MANIFEST
|
|
24
|
+
|
|
25
|
+
# Virtual env
|
|
26
|
+
.venv/
|
|
27
|
+
venv/
|
|
28
|
+
ENV/
|
|
29
|
+
env/
|
|
30
|
+
|
|
31
|
+
# Type checker / linter caches
|
|
32
|
+
.mypy_cache/
|
|
33
|
+
.ruff_cache/
|
|
34
|
+
.pytest_cache/
|
|
35
|
+
.tox/
|
|
36
|
+
.nox/
|
|
37
|
+
htmlcov/
|
|
38
|
+
.coverage
|
|
39
|
+
.coverage.*
|
|
40
|
+
nosetests.xml
|
|
41
|
+
coverage.xml
|
|
42
|
+
*.cover
|
|
43
|
+
.hypothesis/
|
|
44
|
+
|
|
45
|
+
# IDE
|
|
46
|
+
.vscode/
|
|
47
|
+
.idea/
|
|
48
|
+
*.swp
|
|
49
|
+
*.swo
|
|
50
|
+
.DS_Store
|
|
51
|
+
|
|
52
|
+
# Secrets
|
|
53
|
+
.env
|
|
54
|
+
.env.*
|
|
55
|
+
!.env.example
|
opedd-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to opedd-python are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] — 2026-05-15
|
|
8
|
+
|
|
9
|
+
Initial public release. Phase 11 M6 ship.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `Opedd` client class with 5 namespaces:
|
|
14
|
+
- `client.content.get(article_id)` — fetch licensed article (`GET /content-delivery`)
|
|
15
|
+
- `client.feed.list(since, cursor, limit)` — paginated JSON catalog (`GET /enterprise-license?format=json`)
|
|
16
|
+
- `client.feed.stream_ndjson(since, cursor, limit)` — NDJSON streaming generator (`GET /enterprise-license?format=ndjson`)
|
|
17
|
+
- `client.audit.events(from_, to, event_type, cursor, limit)` — per-event audit browse (`GET /buyer-audit`)
|
|
18
|
+
- `client.compliance.report(from_, to, cursor)` — procurement-defense dossier (`GET /buyer-compliance-report`)
|
|
19
|
+
- `client.licenses.purchase(...)` — enterprise license purchase (`POST /enterprise-license`)
|
|
20
|
+
- `client.licenses.list()` — buyer profile + masked key list (`GET /buyer-account`)
|
|
21
|
+
- Three credential modes:
|
|
22
|
+
- `buyer_token` (bearer) for `/content-delivery`
|
|
23
|
+
- `access_key` (query param) for `/enterprise-license` GET feed
|
|
24
|
+
- `buyer_jwt` (Supabase session) for `/buyer-audit` + `/buyer-compliance-report` + `/buyer-account`
|
|
25
|
+
- Env-var fallbacks: `OPEDD_BUYER_TOKEN`, `OPEDD_BUYER_JWT`, `OPEDD_ACCESS_KEY`, `OPEDD_BASE_URL`
|
|
26
|
+
- `Opedd.from_access_key(access_key, buyer_email)` constructor that exchanges `eak_*` for a bearer token via `POST /enterprise-auth`
|
|
27
|
+
- Context manager support (`with Opedd(...) as client:` closes the underlying HTTP pool)
|
|
28
|
+
- Typed exception hierarchy: `OpeddError`, `OpeddAuthError`, `OpeddNotFoundError`, `OpeddValidationError`, `OpeddRateLimitError`, `OpeddServerError`. Each carries `status_code`, `request_id`, `body`. `OpeddRateLimitError` additionally carries `retry_after_seconds`.
|
|
29
|
+
- NDJSON streaming with auto-pagination across `_meta.next_cursor` until `_meta.truncated == false`.
|
|
30
|
+
- 29 unit tests with mocked HTTPX (`pytest tests/test_unit.py`).
|
|
31
|
+
- Integration test suite gated on `pytest --integration` flag (manual local execution before release; not run in CI per institutional risk discipline).
|
|
32
|
+
- Schema version pin: `phase-11-m4`. Surfaced as `opedd.__schema_version__`.
|
|
33
|
+
|
|
34
|
+
### Wire-format contract
|
|
35
|
+
|
|
36
|
+
- Successful response body is flat (no nested `data.data` envelope on most endpoints).
|
|
37
|
+
- Error response: `{"success": false, "error": "..."}` or `{"success": false, "error": {"code": "...", "message": "...", "details": {...}}}` (Phase 8 nested envelope).
|
|
38
|
+
- `X-Opedd-Request-Id` propagated to all SDK exception `request_id` fields.
|
|
39
|
+
|
|
40
|
+
[0.1.0]: https://github.com/Opedd/opedd-python/releases/tag/v0.1.0
|
opedd-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Opedd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
opedd-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opedd
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Opedd content licensing API (buyer-side)
|
|
5
|
+
Project-URL: Homepage, https://opedd.com
|
|
6
|
+
Project-URL: Documentation, https://docs.opedd.com
|
|
7
|
+
Project-URL: Repository, https://github.com/Opedd/opedd-python
|
|
8
|
+
Project-URL: Issues, https://github.com/Opedd/opedd-python/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/Opedd/opedd-python/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Opedd <support@opedd.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: ai-licensing,ai-training,content-licensing,opedd,rag
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: httpx>=0.27.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# opedd
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/opedd/)
|
|
36
|
+
[](https://pypi.org/project/opedd/)
|
|
37
|
+
[](https://github.com/Opedd/opedd-python/blob/main/LICENSE)
|
|
38
|
+
|
|
39
|
+
Official Python SDK for the [Opedd](https://opedd.com) content licensing API (buyer-side).
|
|
40
|
+
|
|
41
|
+
Opedd is programmatic licensing infrastructure between AI buyers and publishers — rights, usage tracking, and payment. "Stripe for content licensing."
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install opedd
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Requires Python 3.10+.
|
|
50
|
+
|
|
51
|
+
## Quickstart
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from opedd import Opedd
|
|
55
|
+
|
|
56
|
+
client = Opedd(buyer_token="opedd_buyer_live_...")
|
|
57
|
+
|
|
58
|
+
# Fetch a single licensed article
|
|
59
|
+
article = client.content.get("article-uuid")
|
|
60
|
+
print(article["title"], article["author"], article["word_count"])
|
|
61
|
+
|
|
62
|
+
# Stream the full licensed catalog as NDJSON
|
|
63
|
+
client = Opedd(access_key="eak_xxx", buyer_email="eng@yourlab.com")
|
|
64
|
+
for row in client.feed.stream_ndjson(limit=5000):
|
|
65
|
+
train_model.ingest(row["content"], metadata=row)
|
|
66
|
+
|
|
67
|
+
# Pull a procurement-defense compliance dossier
|
|
68
|
+
client = Opedd(buyer_jwt="eyJhbGc...")
|
|
69
|
+
dossier = client.compliance.report(from_="2026-04-01", to="2026-04-30")
|
|
70
|
+
print(f"Retrievals: {dossier['dossier_metadata']['summary']['total_retrievals']}")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For end-to-end walkthroughs, see the [cookbook](https://docs.opedd.com/cookbook.md).
|
|
74
|
+
|
|
75
|
+
## Credentials
|
|
76
|
+
|
|
77
|
+
The SDK supports three credential types depending on which endpoint you call:
|
|
78
|
+
|
|
79
|
+
| Endpoint | Credential | Construction |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `client.content.get(...)` | Bearer buyer token | `Opedd(buyer_token="opedd_buyer_live_...")` |
|
|
82
|
+
| `client.feed.list(...)` / `client.feed.stream_ndjson(...)` | Access key (query param) | `Opedd(access_key="eak_...")` |
|
|
83
|
+
| `client.audit.events(...)` | Supabase JWT | `Opedd(buyer_jwt="eyJhbGc...")` |
|
|
84
|
+
| `client.compliance.report(...)` | Supabase JWT | `Opedd(buyer_jwt="eyJhbGc...")` |
|
|
85
|
+
| `client.licenses.purchase(...)` | None (returns Stripe `client_secret`) | `Opedd(buyer_token="...")` |
|
|
86
|
+
| `client.licenses.list()` | Supabase JWT | `Opedd(buyer_jwt="eyJhbGc...")` |
|
|
87
|
+
|
|
88
|
+
Multiple credentials can be supplied at once and the SDK selects the correct one per endpoint.
|
|
89
|
+
|
|
90
|
+
### Env-var fallbacks
|
|
91
|
+
|
|
92
|
+
The constructor reads from these env vars when arguments are omitted:
|
|
93
|
+
|
|
94
|
+
- `OPEDD_BUYER_TOKEN`
|
|
95
|
+
- `OPEDD_BUYER_JWT`
|
|
96
|
+
- `OPEDD_ACCESS_KEY`
|
|
97
|
+
- `OPEDD_BASE_URL` (default `https://api.opedd.com`)
|
|
98
|
+
|
|
99
|
+
### Exchanging an access key for a bearer token
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
client = Opedd.from_access_key(
|
|
103
|
+
access_key="eak_xyz...",
|
|
104
|
+
buyer_email="eng@yourlab.com",
|
|
105
|
+
)
|
|
106
|
+
# client.buyer_token is now set; you can call /content-delivery
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## API surface
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
client.content.get(article_id)
|
|
113
|
+
|
|
114
|
+
client.feed.list(since=None, cursor=None, limit=200)
|
|
115
|
+
client.feed.stream_ndjson(since=None, cursor=None, limit=5000) # generator
|
|
116
|
+
|
|
117
|
+
client.audit.events(from_=None, to=None, event_type=None, cursor=None, limit=100)
|
|
118
|
+
|
|
119
|
+
client.compliance.report(from_, to, cursor=None)
|
|
120
|
+
|
|
121
|
+
client.licenses.purchase(publisher_ids, buyer_email, buyer_org, ...)
|
|
122
|
+
client.licenses.list()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
All methods return dicts matching the backend wire format. See [docs.opedd.com](https://docs.opedd.com) for full response shapes.
|
|
126
|
+
|
|
127
|
+
## Error handling
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from opedd import (
|
|
131
|
+
OpeddError,
|
|
132
|
+
OpeddAuthError,
|
|
133
|
+
OpeddNotFoundError,
|
|
134
|
+
OpeddRateLimitError,
|
|
135
|
+
OpeddServerError,
|
|
136
|
+
OpeddValidationError,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
article = client.content.get(uuid)
|
|
141
|
+
except OpeddRateLimitError as e:
|
|
142
|
+
time.sleep(e.retry_after_seconds or 60)
|
|
143
|
+
retry()
|
|
144
|
+
except OpeddAuthError:
|
|
145
|
+
refresh_token()
|
|
146
|
+
except OpeddNotFoundError:
|
|
147
|
+
log.warning("article gone; skipping")
|
|
148
|
+
except OpeddError as e:
|
|
149
|
+
log.error(f"{e} request_id={e.request_id}")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Every error carries `status_code`, `request_id`, and `body` for forensic correlation.
|
|
153
|
+
|
|
154
|
+
## Schema version pinning
|
|
155
|
+
|
|
156
|
+
The SDK ships pinned to backend schema version **`phase-11-m4`** (`opedd.__schema_version__`).
|
|
157
|
+
|
|
158
|
+
When the backend bumps `X-Opedd-Schema-Version`, the SDK ships a follow-up release within 1 sprint per the [schema-pin invariant](https://github.com/Opedd/opedd-backend/blob/main/INVARIANTS.md#python-sdk--mcp-server-pin-to-backend-x-opedd-schema-version-phase-11-m6).
|
|
159
|
+
|
|
160
|
+
Additive field bumps are absorbed transparently (dict pass-through). Subtractive or rename bumps require an SDK release; ensure your `requirements.txt` pins to a tested version.
|
|
161
|
+
|
|
162
|
+
## Tests
|
|
163
|
+
|
|
164
|
+
Two test suites:
|
|
165
|
+
|
|
166
|
+
### Unit tests (run in CI, no live API calls)
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pip install -e ".[dev]"
|
|
170
|
+
pytest tests/test_unit.py
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
29+ unit tests covering: client construction, credential precedence, env-var fallback, auth-header building per credential type, HTTP error mapping (401/403/404/400/422/429/5xx), NDJSON streaming + cursor pagination, all 5 namespaces' request shapes.
|
|
174
|
+
|
|
175
|
+
### Integration tests (manual, before each release)
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
export OPEDD_BUYER_JWT="..." # for /buyer-audit + /buyer-compliance-report
|
|
179
|
+
export OPEDD_BUYER_TOKEN="..." # for /content-delivery
|
|
180
|
+
export OPEDD_ACCESS_KEY="..." # for /enterprise-license GET feed
|
|
181
|
+
pytest --integration
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Live tests against `api.opedd.com`. Read-only. Skipped by default (require `--integration` flag).
|
|
185
|
+
|
|
186
|
+
Per [release discipline](./RELEASE.md), integration tests must pass locally before any version tag is pushed. CI does NOT run integration tests — per institutional risk discipline, autonomous CI runs against production state can pollute `usage_records`, distort metered-publisher payouts, and fire production API calls at scale.
|
|
187
|
+
|
|
188
|
+
## Development
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
git clone https://github.com/Opedd/opedd-python.git
|
|
192
|
+
cd opedd-python
|
|
193
|
+
pip install -e ".[dev]"
|
|
194
|
+
pytest tests/test_unit.py # unit only (default)
|
|
195
|
+
pytest --integration # unit + integration (requires env vars)
|
|
196
|
+
ruff check src/ tests/ # lint
|
|
197
|
+
mypy src/ # type-check
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Contributing
|
|
201
|
+
|
|
202
|
+
Issues and PRs welcome at [github.com/Opedd/opedd-python](https://github.com/Opedd/opedd-python). For broader questions about Opedd as a platform, email [support@opedd.com](mailto:support@opedd.com).
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
[MIT](./LICENSE)
|
|
207
|
+
|
|
208
|
+
## See also
|
|
209
|
+
|
|
210
|
+
- [docs.opedd.com](https://docs.opedd.com) — full API reference + cookbook
|
|
211
|
+
- [opedd-mcp](https://github.com/Opedd/opedd-mcp) — Model Context Protocol server for Claude Desktop / Cursor
|
|
212
|
+
- [opedd-backend](https://github.com/Opedd/opedd-backend) — backend implementation (private)
|
opedd-0.1.0/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# opedd
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/opedd/)
|
|
4
|
+
[](https://pypi.org/project/opedd/)
|
|
5
|
+
[](https://github.com/Opedd/opedd-python/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
Official Python SDK for the [Opedd](https://opedd.com) content licensing API (buyer-side).
|
|
8
|
+
|
|
9
|
+
Opedd is programmatic licensing infrastructure between AI buyers and publishers — rights, usage tracking, and payment. "Stripe for content licensing."
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install opedd
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Python 3.10+.
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from opedd import Opedd
|
|
23
|
+
|
|
24
|
+
client = Opedd(buyer_token="opedd_buyer_live_...")
|
|
25
|
+
|
|
26
|
+
# Fetch a single licensed article
|
|
27
|
+
article = client.content.get("article-uuid")
|
|
28
|
+
print(article["title"], article["author"], article["word_count"])
|
|
29
|
+
|
|
30
|
+
# Stream the full licensed catalog as NDJSON
|
|
31
|
+
client = Opedd(access_key="eak_xxx", buyer_email="eng@yourlab.com")
|
|
32
|
+
for row in client.feed.stream_ndjson(limit=5000):
|
|
33
|
+
train_model.ingest(row["content"], metadata=row)
|
|
34
|
+
|
|
35
|
+
# Pull a procurement-defense compliance dossier
|
|
36
|
+
client = Opedd(buyer_jwt="eyJhbGc...")
|
|
37
|
+
dossier = client.compliance.report(from_="2026-04-01", to="2026-04-30")
|
|
38
|
+
print(f"Retrievals: {dossier['dossier_metadata']['summary']['total_retrievals']}")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For end-to-end walkthroughs, see the [cookbook](https://docs.opedd.com/cookbook.md).
|
|
42
|
+
|
|
43
|
+
## Credentials
|
|
44
|
+
|
|
45
|
+
The SDK supports three credential types depending on which endpoint you call:
|
|
46
|
+
|
|
47
|
+
| Endpoint | Credential | Construction |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `client.content.get(...)` | Bearer buyer token | `Opedd(buyer_token="opedd_buyer_live_...")` |
|
|
50
|
+
| `client.feed.list(...)` / `client.feed.stream_ndjson(...)` | Access key (query param) | `Opedd(access_key="eak_...")` |
|
|
51
|
+
| `client.audit.events(...)` | Supabase JWT | `Opedd(buyer_jwt="eyJhbGc...")` |
|
|
52
|
+
| `client.compliance.report(...)` | Supabase JWT | `Opedd(buyer_jwt="eyJhbGc...")` |
|
|
53
|
+
| `client.licenses.purchase(...)` | None (returns Stripe `client_secret`) | `Opedd(buyer_token="...")` |
|
|
54
|
+
| `client.licenses.list()` | Supabase JWT | `Opedd(buyer_jwt="eyJhbGc...")` |
|
|
55
|
+
|
|
56
|
+
Multiple credentials can be supplied at once and the SDK selects the correct one per endpoint.
|
|
57
|
+
|
|
58
|
+
### Env-var fallbacks
|
|
59
|
+
|
|
60
|
+
The constructor reads from these env vars when arguments are omitted:
|
|
61
|
+
|
|
62
|
+
- `OPEDD_BUYER_TOKEN`
|
|
63
|
+
- `OPEDD_BUYER_JWT`
|
|
64
|
+
- `OPEDD_ACCESS_KEY`
|
|
65
|
+
- `OPEDD_BASE_URL` (default `https://api.opedd.com`)
|
|
66
|
+
|
|
67
|
+
### Exchanging an access key for a bearer token
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
client = Opedd.from_access_key(
|
|
71
|
+
access_key="eak_xyz...",
|
|
72
|
+
buyer_email="eng@yourlab.com",
|
|
73
|
+
)
|
|
74
|
+
# client.buyer_token is now set; you can call /content-delivery
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## API surface
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
client.content.get(article_id)
|
|
81
|
+
|
|
82
|
+
client.feed.list(since=None, cursor=None, limit=200)
|
|
83
|
+
client.feed.stream_ndjson(since=None, cursor=None, limit=5000) # generator
|
|
84
|
+
|
|
85
|
+
client.audit.events(from_=None, to=None, event_type=None, cursor=None, limit=100)
|
|
86
|
+
|
|
87
|
+
client.compliance.report(from_, to, cursor=None)
|
|
88
|
+
|
|
89
|
+
client.licenses.purchase(publisher_ids, buyer_email, buyer_org, ...)
|
|
90
|
+
client.licenses.list()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
All methods return dicts matching the backend wire format. See [docs.opedd.com](https://docs.opedd.com) for full response shapes.
|
|
94
|
+
|
|
95
|
+
## Error handling
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from opedd import (
|
|
99
|
+
OpeddError,
|
|
100
|
+
OpeddAuthError,
|
|
101
|
+
OpeddNotFoundError,
|
|
102
|
+
OpeddRateLimitError,
|
|
103
|
+
OpeddServerError,
|
|
104
|
+
OpeddValidationError,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
article = client.content.get(uuid)
|
|
109
|
+
except OpeddRateLimitError as e:
|
|
110
|
+
time.sleep(e.retry_after_seconds or 60)
|
|
111
|
+
retry()
|
|
112
|
+
except OpeddAuthError:
|
|
113
|
+
refresh_token()
|
|
114
|
+
except OpeddNotFoundError:
|
|
115
|
+
log.warning("article gone; skipping")
|
|
116
|
+
except OpeddError as e:
|
|
117
|
+
log.error(f"{e} request_id={e.request_id}")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Every error carries `status_code`, `request_id`, and `body` for forensic correlation.
|
|
121
|
+
|
|
122
|
+
## Schema version pinning
|
|
123
|
+
|
|
124
|
+
The SDK ships pinned to backend schema version **`phase-11-m4`** (`opedd.__schema_version__`).
|
|
125
|
+
|
|
126
|
+
When the backend bumps `X-Opedd-Schema-Version`, the SDK ships a follow-up release within 1 sprint per the [schema-pin invariant](https://github.com/Opedd/opedd-backend/blob/main/INVARIANTS.md#python-sdk--mcp-server-pin-to-backend-x-opedd-schema-version-phase-11-m6).
|
|
127
|
+
|
|
128
|
+
Additive field bumps are absorbed transparently (dict pass-through). Subtractive or rename bumps require an SDK release; ensure your `requirements.txt` pins to a tested version.
|
|
129
|
+
|
|
130
|
+
## Tests
|
|
131
|
+
|
|
132
|
+
Two test suites:
|
|
133
|
+
|
|
134
|
+
### Unit tests (run in CI, no live API calls)
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install -e ".[dev]"
|
|
138
|
+
pytest tests/test_unit.py
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
29+ unit tests covering: client construction, credential precedence, env-var fallback, auth-header building per credential type, HTTP error mapping (401/403/404/400/422/429/5xx), NDJSON streaming + cursor pagination, all 5 namespaces' request shapes.
|
|
142
|
+
|
|
143
|
+
### Integration tests (manual, before each release)
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
export OPEDD_BUYER_JWT="..." # for /buyer-audit + /buyer-compliance-report
|
|
147
|
+
export OPEDD_BUYER_TOKEN="..." # for /content-delivery
|
|
148
|
+
export OPEDD_ACCESS_KEY="..." # for /enterprise-license GET feed
|
|
149
|
+
pytest --integration
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Live tests against `api.opedd.com`. Read-only. Skipped by default (require `--integration` flag).
|
|
153
|
+
|
|
154
|
+
Per [release discipline](./RELEASE.md), integration tests must pass locally before any version tag is pushed. CI does NOT run integration tests — per institutional risk discipline, autonomous CI runs against production state can pollute `usage_records`, distort metered-publisher payouts, and fire production API calls at scale.
|
|
155
|
+
|
|
156
|
+
## Development
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
git clone https://github.com/Opedd/opedd-python.git
|
|
160
|
+
cd opedd-python
|
|
161
|
+
pip install -e ".[dev]"
|
|
162
|
+
pytest tests/test_unit.py # unit only (default)
|
|
163
|
+
pytest --integration # unit + integration (requires env vars)
|
|
164
|
+
ruff check src/ tests/ # lint
|
|
165
|
+
mypy src/ # type-check
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Contributing
|
|
169
|
+
|
|
170
|
+
Issues and PRs welcome at [github.com/Opedd/opedd-python](https://github.com/Opedd/opedd-python). For broader questions about Opedd as a platform, email [support@opedd.com](mailto:support@opedd.com).
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
[MIT](./LICENSE)
|
|
175
|
+
|
|
176
|
+
## See also
|
|
177
|
+
|
|
178
|
+
- [docs.opedd.com](https://docs.opedd.com) — full API reference + cookbook
|
|
179
|
+
- [opedd-mcp](https://github.com/Opedd/opedd-mcp) — Model Context Protocol server for Claude Desktop / Cursor
|
|
180
|
+
- [opedd-backend](https://github.com/Opedd/opedd-backend) — backend implementation (private)
|
opedd-0.1.0/RELEASE.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Release process for opedd-python
|
|
2
|
+
|
|
3
|
+
This document codifies the SDK release discipline established in Phase 11 M6.
|
|
4
|
+
|
|
5
|
+
## Trigger
|
|
6
|
+
|
|
7
|
+
A release ships when:
|
|
8
|
+
|
|
9
|
+
1. The backend bumps `X-Opedd-Schema-Version` on any buyer-facing endpoint, AND the bump requires a wrapper change (additive bumps are absorbed transparently; subtractive/rename bumps require code).
|
|
10
|
+
2. A new buyer-side endpoint ships in the backend (e.g., Phase 12 onwards).
|
|
11
|
+
3. A bug is fixed in the SDK itself.
|
|
12
|
+
4. A new minor feature lands in the SDK (e.g., new error type, retry helper).
|
|
13
|
+
|
|
14
|
+
Per the [schema-pin invariant](https://github.com/Opedd/opedd-backend/blob/main/INVARIANTS.md#python-sdk--mcp-server-pin-to-backend-x-opedd-schema-version-phase-11-m6), backend schema bumps trigger a follow-up SDK release within 1 sprint.
|
|
15
|
+
|
|
16
|
+
## Version-bump rules
|
|
17
|
+
|
|
18
|
+
- **MAJOR** (1.0.0 → 2.0.0): breaking SDK API change — method removed/renamed, parameter signature change.
|
|
19
|
+
- **MINOR** (0.1.0 → 0.2.0): new endpoint surface, new namespace, new optional parameter, backend schema-pin bump.
|
|
20
|
+
- **PATCH** (0.1.0 → 0.1.1): bug fix, doc update, dependency-version bump that doesn't change behavior.
|
|
21
|
+
|
|
22
|
+
While the SDK is pre-1.0, MINOR bumps are also acceptable for behavior-breaking changes; the README + CHANGELOG must call them out explicitly.
|
|
23
|
+
|
|
24
|
+
## Pre-release checklist
|
|
25
|
+
|
|
26
|
+
1. **Update `__version__`** in `src/opedd/__init__.py` AND `version` in `pyproject.toml`.
|
|
27
|
+
2. **Update `__schema_version__`** if the backend bumped any buyer-facing endpoint schema version.
|
|
28
|
+
3. **Update `CHANGELOG.md`** with a new section dated today's UTC date.
|
|
29
|
+
4. **Run unit tests**: `pytest tests/test_unit.py` — must be all-green.
|
|
30
|
+
5. **Run integration tests locally** with founder credentials:
|
|
31
|
+
```bash
|
|
32
|
+
export OPEDD_BUYER_JWT="..."
|
|
33
|
+
export OPEDD_BUYER_TOKEN="..."
|
|
34
|
+
export OPEDD_ACCESS_KEY="..."
|
|
35
|
+
pytest --integration
|
|
36
|
+
```
|
|
37
|
+
Per founder M6 ratification 6, integration tests do NOT run in CI — they run locally before each release. Verify all-green.
|
|
38
|
+
6. **Lint + type-check**: `ruff check src/ tests/` AND `mypy src/`. Must be clean.
|
|
39
|
+
7. **Build the package**: `pip install --upgrade build && python -m build`. Confirm `dist/opedd-<version>-py3-none-any.whl` and `dist/opedd-<version>.tar.gz` produced.
|
|
40
|
+
8. **Verify install from local wheel**: `pip install --force-reinstall dist/opedd-<version>-py3-none-any.whl && python -c "import opedd; print(opedd.__version__)"`.
|
|
41
|
+
|
|
42
|
+
## PyPI publish
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install --upgrade twine
|
|
46
|
+
twine check dist/*
|
|
47
|
+
twine upload dist/*
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
You'll need a PyPI API token (`pypi-AgEIcHlw...`) stored either in `~/.pypirc`:
|
|
51
|
+
|
|
52
|
+
```ini
|
|
53
|
+
[pypi]
|
|
54
|
+
username = __token__
|
|
55
|
+
password = pypi-AgEIcHlw...
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Or passed via `TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-...`.
|
|
59
|
+
|
|
60
|
+
Verify install from PyPI after upload (give the CDN ~30s to propagate):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install --upgrade opedd
|
|
64
|
+
python -c "import opedd; print(opedd.__version__, opedd.__schema_version__)"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Git tag + GitHub release
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
git tag -a v0.1.0 -m "v0.1.0 — initial buyer-side SDK"
|
|
71
|
+
git push origin v0.1.0
|
|
72
|
+
gh release create v0.1.0 \
|
|
73
|
+
--title "v0.1.0 — initial buyer-side SDK" \
|
|
74
|
+
--notes-file CHANGELOG.md
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Post-release
|
|
78
|
+
|
|
79
|
+
- Cross-link from opedd-docs `python-sdk.md` if the README example block changed.
|
|
80
|
+
- If schema pin changed: update `opedd-backend/INVARIANTS.md` "Python SDK + MCP server pin to backend X-Opedd-Schema-Version" section with the new version.
|
|
81
|
+
- Email `support@opedd.com` (or whatever the buyer-facing channel is) if the bump is breaking.
|
|
82
|
+
|
|
83
|
+
## Yank / unpublish
|
|
84
|
+
|
|
85
|
+
If a release ships with a critical bug:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Yank (preferred — removes from default index but keeps for pinned installs)
|
|
89
|
+
twine upload --skip-existing dist/opedd-<patched-version>-* # ship the patch first
|
|
90
|
+
pip install --upgrade twine
|
|
91
|
+
# Then yank the broken version via PyPI web UI: https://pypi.org/manage/project/opedd/release/<version>/
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
NEVER fully delete a PyPI release; PyPI does not allow re-publishing the same version number, so deletion creates a permanent gap. Yank-then-patch is the canonical recovery path.
|
|
95
|
+
|
|
96
|
+
## CI
|
|
97
|
+
|
|
98
|
+
CI runs unit tests on every push (see `.github/workflows/ci.yml`). Integration tests run locally only (per Phase 11 M6 ratification 6: "SDK live tests running autonomously in CI could pollute usage_records, distort metered-publisher-payouts aggregation, fire production API calls with founder identity at scale").
|
|
99
|
+
|
|
100
|
+
## Maintenance schedule
|
|
101
|
+
|
|
102
|
+
- **Backend X-Opedd-Schema-Version bump**: ship follow-up SDK within 1 sprint per the schema-pin invariant.
|
|
103
|
+
- **Dependency security advisory (httpx, pytest)**: ship patch release within 1 week.
|
|
104
|
+
- **Python EOL versions (3.10 EOL October 2026)**: drop EOL'd versions in a MINOR bump, document in CHANGELOG.
|
|
105
|
+
|
|
106
|
+
See [opedd-backend/docs/cleanup/active-deferrals.md](https://github.com/Opedd/opedd-backend/blob/main/docs/cleanup/active-deferrals.md) for the institutional schema-pin maintenance discipline entry.
|