threecommon 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.
Files changed (36) hide show
  1. threecommon-0.1.0/.gitignore +32 -0
  2. threecommon-0.1.0/CHANGELOG.md +16 -0
  3. threecommon-0.1.0/LICENSE +21 -0
  4. threecommon-0.1.0/PKG-INFO +399 -0
  5. threecommon-0.1.0/README.md +360 -0
  6. threecommon-0.1.0/pyproject.toml +194 -0
  7. threecommon-0.1.0/src/threecommon/__init__.py +79 -0
  8. threecommon-0.1.0/src/threecommon/_core/__init__.py +6 -0
  9. threecommon-0.1.0/src/threecommon/_core/headers.py +41 -0
  10. threecommon-0.1.0/src/threecommon/_core/http_client.py +424 -0
  11. threecommon-0.1.0/src/threecommon/_core/parse.py +77 -0
  12. threecommon-0.1.0/src/threecommon/_core/retry.py +80 -0
  13. threecommon-0.1.0/src/threecommon/_core/telemetry.py +77 -0
  14. threecommon-0.1.0/src/threecommon/_core/url.py +31 -0
  15. threecommon-0.1.0/src/threecommon/_generated/__init__.py +8 -0
  16. threecommon-0.1.0/src/threecommon/_generated/models.py +614 -0
  17. threecommon-0.1.0/src/threecommon/api_version.py +15 -0
  18. threecommon-0.1.0/src/threecommon/client.py +184 -0
  19. threecommon-0.1.0/src/threecommon/config.py +140 -0
  20. threecommon-0.1.0/src/threecommon/errors/__init__.py +35 -0
  21. threecommon-0.1.0/src/threecommon/errors/base.py +81 -0
  22. threecommon-0.1.0/src/threecommon/errors/classes.py +75 -0
  23. threecommon-0.1.0/src/threecommon/events/__init__.py +28 -0
  24. threecommon-0.1.0/src/threecommon/events/service.py +170 -0
  25. threecommon-0.1.0/src/threecommon/events/types.py +124 -0
  26. threecommon-0.1.0/src/threecommon/filters/__init__.py +51 -0
  27. threecommon-0.1.0/src/threecommon/filters/builder.py +188 -0
  28. threecommon-0.1.0/src/threecommon/filters/types.py +69 -0
  29. threecommon-0.1.0/src/threecommon/helpers.py +19 -0
  30. threecommon-0.1.0/src/threecommon/invoices/__init__.py +40 -0
  31. threecommon-0.1.0/src/threecommon/invoices/service.py +266 -0
  32. threecommon-0.1.0/src/threecommon/invoices/types.py +195 -0
  33. threecommon-0.1.0/src/threecommon/pagination/__init__.py +12 -0
  34. threecommon-0.1.0/src/threecommon/pagination/auto_paginator.py +98 -0
  35. threecommon-0.1.0/src/threecommon/py.typed +0 -0
  36. threecommon-0.1.0/src/threecommon/version.py +10 -0
@@ -0,0 +1,32 @@
1
+ # Build / dist
2
+ build/
3
+ dist/
4
+ *.egg-info/
5
+ src/threecommon/_version.py
6
+
7
+ # Test / coverage
8
+ .pytest_cache/
9
+ .coverage
10
+ .coverage.*
11
+ coverage.xml
12
+ htmlcov/
13
+
14
+ # Type checkers
15
+ .mypy_cache/
16
+ .pyright/
17
+ .ruff_cache/
18
+
19
+ # Virtualenvs
20
+ .venv/
21
+ venv/
22
+ env/
23
+
24
+ # Editor / OS
25
+ .vscode/
26
+ .idea/
27
+ *.swp
28
+ .DS_Store
29
+
30
+ # Python bytecode
31
+ __pycache__/
32
+ *.py[cod]
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
4
+ versions follow [SemVer](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## [Unreleased]
7
+
8
+ ### Added
9
+
10
+ - Initial scaffolding.
11
+ - `ThreeCommon` (sync) and `AsyncThreeCommon` (async) clients.
12
+ - Events resource: `list`, `retrieve`, `update`, `list_auto_paginate`.
13
+ - Invoices resource: `list`, `retrieve`, `create`, `update`, `finalize`, `void`,
14
+ `record_payment`, `list_auto_paginate`. Both sync and async surfaces.
15
+ - Typed exception tree (`AuthError`, `NotFoundError`, `RateLimitError`, …).
16
+ - Conformance harness running shared YAML scenarios against both clients.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 3Common
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.
@@ -0,0 +1,399 @@
1
+ Metadata-Version: 2.4
2
+ Name: threecommon
3
+ Version: 0.1.0
4
+ Summary: Official Python client for the 3Common Public API.
5
+ Project-URL: Homepage, https://github.com/3-Common/sdk/tree/main/sdk-python
6
+ Project-URL: Issues, https://github.com/3-Common/sdk/issues
7
+ Project-URL: Repository, https://github.com/3-Common/sdk
8
+ Author-email: 3Common <support@3common.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: 3common,api-client,events,invoices,sdk
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
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
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: httpx<1.0,>=0.27
26
+ Requires-Dist: pydantic<3.0,>=2.7
27
+ Provides-Extra: dev
28
+ Requires-Dist: datamodel-code-generator>=0.26; extra == 'dev'
29
+ Requires-Dist: mypy>=1.13; extra == 'dev'
30
+ Requires-Dist: pyright>=1.1.390; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
32
+ Requires-Dist: pytest-cov>=6.0; extra == 'dev'
33
+ Requires-Dist: pytest-httpx>=0.34; extra == 'dev'
34
+ Requires-Dist: pytest>=8.3; extra == 'dev'
35
+ Requires-Dist: pyyaml>=6.0; extra == 'dev'
36
+ Requires-Dist: ruff>=0.8; extra == 'dev'
37
+ Requires-Dist: types-pyyaml; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # `threecommon`
41
+
42
+ [![PyPI](https://img.shields.io/pypi/v/threecommon.svg)](https://pypi.org/project/threecommon/)
43
+ [![Python](https://img.shields.io/pypi/pyversions/threecommon.svg)](https://pypi.org/project/threecommon/)
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
45
+
46
+ Official Python client for the 3Common Public API. Sync **and** async, fully type-checked, Pydantic v2 models.
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ pip install threecommon
52
+ ```
53
+
54
+ Requires **Python ≥ 3.10**.
55
+
56
+ ## Quick start (sync)
57
+
58
+ ```python
59
+ from threecommon import ThreeCommon
60
+ from threecommon.events import ListParams, UpdateBody
61
+
62
+ with ThreeCommon(api_key="3co_...") as client:
63
+ # List
64
+ result = client.events.list(ListParams(status="open", page_size=50))
65
+
66
+ # Retrieve
67
+ ev = client.events.retrieve("evt_123")
68
+
69
+ # Update
70
+ updated = client.events.update("evt_123", UpdateBody(name="New name"))
71
+
72
+ # Auto-paginate
73
+ for ev in client.events.list_auto_paginate(ListParams(status="open")):
74
+ print(ev.name)
75
+ ```
76
+
77
+ ## Quick start (async)
78
+
79
+ ```python
80
+ import asyncio
81
+ from threecommon import AsyncThreeCommon
82
+ from threecommon.events import ListParams
83
+
84
+ async def main() -> None:
85
+ async with AsyncThreeCommon(api_key="3co_...") as client:
86
+ result = await client.events.list(ListParams(status="open"))
87
+ async for ev in client.events.list_auto_paginate(ListParams(status="open")):
88
+ print(ev.name)
89
+
90
+ asyncio.run(main())
91
+ ```
92
+
93
+ The API key may also be supplied via the `THREECOMMON_API_KEY` environment variable.
94
+
95
+ ## Configuration
96
+
97
+ ```python
98
+ from threecommon import ThreeCommon, RetryDelay
99
+
100
+ client = ThreeCommon(
101
+ api_key="3co_...", # required (or via env var)
102
+ base_url="https://api.3common.com", # default
103
+ api_version="2026-04-29", # pinned API version
104
+ timeout_seconds=30.0, # per-request deadline
105
+ max_retries=3, # automatic retries on 408/425/429/5xx
106
+ retry_delay=RetryDelay(
107
+ initial_seconds=0.5,
108
+ max_seconds=8.0,
109
+ jitter=True,
110
+ ),
111
+ telemetry=True, # opt-out of anonymous telemetry
112
+ )
113
+ ```
114
+
115
+ ## Error handling
116
+
117
+ Every error raised by the SDK inherits from `threecommon.APIError`. Catch the typed subclass you care about:
118
+
119
+ ```python
120
+ from threecommon import (
121
+ NotFoundError,
122
+ AuthError,
123
+ RateLimitError,
124
+ ConnectionError,
125
+ )
126
+
127
+ try:
128
+ client.events.retrieve("evt_missing")
129
+ except NotFoundError as e:
130
+ # 404 — e.request_id, e.code, e.details
131
+ ...
132
+ except AuthError as e:
133
+ # 401 — bad or expired API key
134
+ ...
135
+ except RateLimitError as e:
136
+ # 429 — e.retry_after_seconds tells you when to retry
137
+ ...
138
+ except ConnectionError as e:
139
+ # network error; original cause via e.__cause__
140
+ ...
141
+ ```
142
+
143
+ Every error carries `code`, `message`, `http_status`, `request_id`, `details`, and `raw_response`. The default `str(e)` format includes the request ID for log correlation:
144
+
145
+ ```
146
+ [not_found] Event evt_missing not found (request_id=req-dfx-abc)
147
+ ```
148
+
149
+ ## Pagination
150
+
151
+ Two flavors:
152
+
153
+ ```python
154
+ # One page at a time
155
+ result = client.events.list(ListParams(page_size=50))
156
+
157
+ # All pages, lazy
158
+ for ev in client.events.list_auto_paginate(ListParams(status="open")):
159
+ print(ev.name)
160
+
161
+ # Async
162
+ async for ev in async_client.events.list_auto_paginate(ListParams(status="open")):
163
+ print(ev.name)
164
+ ```
165
+
166
+ ## Filters
167
+
168
+ The `filters` subpackage provides a typed builder for the API's `filters` query parameter — never write the JSON by hand:
169
+
170
+ ```python
171
+ from threecommon import filters
172
+ from threecommon.events import ListParams
173
+
174
+ f = filters.and_(
175
+ filters.field("status").is_any_of(["open"]),
176
+ filters.field("ticket_sum").is_greater_than(10),
177
+ )
178
+
179
+ result = client.events.list(ListParams(filters=f.serialize()))
180
+ ```
181
+
182
+ The full operator set is enumerated in `threecommon.filters.types`.
183
+
184
+ ## Retries
185
+
186
+ Idempotent methods (`GET`, `PATCH`, `PUT`) retry automatically on `408`, `425`, `429`, `500`, `502`, `503`, `504` and on network errors. Backoff is exponential with full jitter, capped at `RetryDelay.max_seconds`. The SDK honors a server-provided `Retry-After` header on `429`.
187
+
188
+ `POST` and `DELETE` do not retry by default; pass an `idempotency_key` via per-request options to opt in (forward-compat — no v1 endpoints currently use this).
189
+
190
+ ## Telemetry
191
+
192
+ The SDK sends a small, anonymized `Threecommon-Client-Telemetry` header on every request (SDK version, language, last-request latency). This helps debug performance reports from real customers without instrumenting their code. Disable globally:
193
+
194
+ ```python
195
+ client = ThreeCommon(api_key="...", telemetry=False)
196
+ ```
197
+
198
+ Or at runtime:
199
+
200
+ ```python
201
+ client.disable_telemetry()
202
+ ```
203
+
204
+ The header never contains your API key, request bodies, or response bodies.
205
+
206
+ ## Repository layout
207
+
208
+ The package layout mirrors the Node and Go SDKs so behavior changes can be paired across languages:
209
+
210
+ ```
211
+ sdk-python/
212
+ ├── pyproject.toml
213
+ ├── src/threecommon/
214
+ │ ├── __init__.py # public surface re-exports
215
+ │ ├── py.typed # PEP 561 marker
216
+ │ ├── client.py # ThreeCommon + AsyncThreeCommon
217
+ │ ├── config.py # ClientConfig, RetryDelay, defaults
218
+ │ ├── api_version.py # pinned API version + path
219
+ │ ├── version.py # SDK package version
220
+ │ ├── helpers.py # small utility helpers
221
+ │ ├── errors/ # exception tree
222
+ │ │ ├── base.py # APIError
223
+ │ │ └── classes.py # AuthError, NotFoundError, RateLimitError, ...
224
+ │ ├── pagination/ # auto-paginating iterators
225
+ │ │ └── auto_paginator.py # Iter[T] + AsyncIter[T]
226
+ │ ├── filters/ # typed filter builder (shared across resources)
227
+ │ ├── events/ # events resource (sync + async + Pydantic types)
228
+ │ ├── invoices/ # invoices resource (sync + async + Pydantic types)
229
+ │ ├── _core/ # private HTTP machinery (decomposed)
230
+ │ └── _generated/ # datamodel-code-generator output (re-run via `make gen`)
231
+ ├── examples/
232
+ │ ├── events/
233
+ │ └── invoices/
234
+ └── tests/
235
+ ```
236
+
237
+ ## Development
238
+
239
+ The project uses [`uv`](https://github.com/astral-sh/uv) for venv + dependency management, [`ruff`](https://docs.astral.sh/ruff/) for lint and format, [`mypy`](https://mypy-lang.org/) and [`pyright`](https://github.com/microsoft/pyright) for type checking, and [`pytest`](https://docs.pytest.org/) for testing.
240
+
241
+ ### Set up the dev environment
242
+
243
+ macOS and Linux:
244
+
245
+ ```bash
246
+ # One-time: create a venv, activate, install runtime deps + dev tools
247
+ uv venv --python 3.10 .venv
248
+ source .venv/bin/activate
249
+ uv pip install -e ".[dev]"
250
+
251
+ # Verify:
252
+ uv pip list | grep threecommon # should print: threecommon 0.0.0.dev0 /path/to/sdk-python
253
+ pytest -q # all tests pass
254
+ ```
255
+
256
+ Windows:
257
+
258
+ ```powershell
259
+ # One-time: create a venv, activate, install runtime deps + dev tools
260
+ uv venv --python 3.10 .venv
261
+ .\.venv\Scripts\activate.ps1
262
+ uv pip install -e ".[dev]"
263
+
264
+ # Verify:
265
+ uv pip list | Select-String threecommon # should print: threecommon 0.0.0.dev0 \path\to\sdk-python
266
+ pytest -q # all tests pass
267
+ ```
268
+
269
+ If activation fails with an execution-policy error, run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once and retry.
270
+
271
+ Note: with the virtual environment active, all further bash snippets should work as-is in PowerShell on Windows, except where a separate PowerShell snippet is provided.
272
+
273
+ ### Run tests
274
+
275
+ ```bash
276
+ pytest # all tests
277
+ pytest tests/test_events.py # one file
278
+ pytest tests/test_events.py::test_list_decodes_response # one test
279
+ pytest -k "conformance" # match by name
280
+ pytest -q # quiet output
281
+ ```
282
+
283
+ The conformance harness (`tests/test_conformance.py`) parametrizes over the shared YAML scenarios at `../conformance/scenarios/*.yaml` and runs each one against both the sync and async clients.
284
+
285
+ ### Coverage
286
+
287
+ ```bash
288
+ pytest --cov=src/threecommon --cov-report=term # term summary
289
+ pytest --cov=src/threecommon --cov-report=html # HTML report at htmlcov/
290
+ pytest --cov=src/threecommon --cov-fail-under=90 # CI gate (≥ 90% line + branch)
291
+ ```
292
+
293
+ ### Lint and format
294
+
295
+ ```bash
296
+ ruff check . # lint
297
+ ruff check --fix . # auto-fix
298
+ ruff format . # format
299
+ ruff format --check . # CI-style check (no changes)
300
+ ```
301
+
302
+ ### Type check
303
+
304
+ Both run in CI; either failing blocks the PR.
305
+
306
+ ```bash
307
+ mypy src/threecommon tests scripts # mypy --strict via pyproject
308
+ pyright src/threecommon scripts # pyright with project config
309
+ ```
310
+
311
+ ### Regenerate OpenAPI models
312
+
313
+ `src/threecommon/_generated/models.py` is produced from `../openapi/spec.yaml`. Re-run after every spec update:
314
+
315
+ macOS and Linux:
316
+
317
+ ```bash
318
+ datamodel-codegen \
319
+ --input ../openapi/spec.yaml \
320
+ --input-file-type openapi \
321
+ --output src/threecommon/_generated/models.py \
322
+ --output-model-type pydantic_v2.BaseModel \
323
+ --target-python-version 3.10 \
324
+ --use-standard-collections --use-union-operator --use-double-quotes \
325
+ --field-constraints --use-schema-description --capitalise-enum-members \
326
+ --reuse-model --openapi-scopes paths schemas parameters
327
+ ```
328
+
329
+ Windows:
330
+
331
+ ```powershell
332
+ datamodel-codegen `
333
+ --input ..\openapi\spec.yaml `
334
+ --input-file-type openapi `
335
+ --output .\src\threecommon\_generated\models.py `
336
+ --output-model-type pydantic_v2.BaseModel `
337
+ --target-python-version 3.10 `
338
+ --use-standard-collections --use-union-operator --use-double-quotes `
339
+ --field-constraints --use-schema-description --capitalise-enum-members `
340
+ --reuse-model --openapi-scopes paths schemas parameters
341
+ ```
342
+
343
+ The generated package is treated as a contract reference; customer-facing types are hand-curated under `src/threecommon/<resource>/types.py`.
344
+
345
+ ### Live smoke (maintainer-only)
346
+
347
+ macOS and Linux:
348
+
349
+ ```bash
350
+ THREECOMMON_API_KEY=3co_real_key \
351
+ SMOKE_EVENT_ID=evt_known \
352
+ python scripts/livesmoke.py
353
+ ```
354
+
355
+ Windows:
356
+
357
+ ```powershell
358
+ $env:THREECOMMON_API_KEY = "3co_real_key"
359
+ $env:SMOKE_EVENT_ID = "evt_known"
360
+ python .\scripts\livesmoke.py
361
+ ```
362
+
363
+ Runs ≤ 10 real API calls and verifies the happy path + 401/404 error paths. Set `THREECOMMON_BASE_URL` to override the default `https://api.3common.com`.
364
+
365
+ ### Build a wheel locally
366
+
367
+ ```bash
368
+ uv build # produces sdist + wheel under dist/
369
+ ```
370
+
371
+ ## Versioning
372
+
373
+ The SDK follows SemVer. The pinned **API version** (sent as `Threecommon-Version`) is independent — the API can evolve without breaking already-deployed SDKs. Bump `api_version` to opt into newer server behavior.
374
+
375
+ PyPI distribution: `threecommon`. Tags use the path-prefixed form `sdk-python/vX.Y.Z` to share the monorepo with the Node and Go SDKs.
376
+
377
+ ## Examples
378
+
379
+ End-to-end runnable examples live under [`examples/events/`](./examples/events/):
380
+
381
+ ```bash
382
+ python examples/events/list_sync.py
383
+ python examples/events/list_async.py
384
+ python examples/events/retrieve.py
385
+ python examples/events/update.py
386
+ python examples/events/auto_paginate.py
387
+ python examples/events/error_handling.py
388
+ python examples/events/filters_demo.py
389
+ ```
390
+
391
+ Replace `3co_your_api_key_here` and `evt_replace_with_real_id` with real values before running.
392
+
393
+ ## Contributing
394
+
395
+ See the [repository CONTRIBUTING guide](https://github.com/3-Common/sdk/blob/main/CONTRIBUTING.md). Issues and PRs welcome.
396
+
397
+ ## License
398
+
399
+ [MIT](./LICENSE)