reconify-python 0.1.0__tar.gz → 2.0.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.
- {reconify_python-0.1.0 → reconify_python-2.0.0}/.bumpversion.cfg +1 -1
- reconify_python-2.0.0/.conductor/settings.toml +18 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/.github/workflows/ci.yml +1 -0
- reconify_python-2.0.0/.github/workflows/sync-contract.yml +36 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/.gitignore +1 -0
- reconify_python-2.0.0/.openapi-contract.json +4 -0
- reconify_python-2.0.0/AGENTS.md +29 -0
- reconify_python-2.0.0/CHANGELOG.md +16 -0
- reconify_python-2.0.0/PKG-INFO +96 -0
- reconify_python-2.0.0/README.md +78 -0
- reconify_python-2.0.0/UPGRADING.md +17 -0
- reconify_python-2.0.0/context7.json +15 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/pyproject.toml +1 -1
- reconify_python-2.0.0/scripts/fetch_contract.py +65 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/src/reconify/__init__.py +1 -1
- reconify_python-2.0.0/src/reconify/client.py +157 -0
- reconify_python-2.0.0/src/reconify/models.py +247 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/src/reconify/pagination.py +1 -1
- reconify_python-2.0.0/src/reconify/resources/__init__.py +71 -0
- reconify_python-2.0.0/src/reconify/resources/events.py +58 -0
- reconify_python-2.0.0/src/reconify/resources/ingestion.py +36 -0
- reconify_python-2.0.0/src/reconify/resources/issues.py +129 -0
- reconify_python-2.0.0/src/reconify/resources/metadata.py +24 -0
- reconify_python-2.0.0/src/reconify/resources/organization.py +41 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/src/reconify/transport.py +5 -5
- reconify_python-2.0.0/tests/test_client.py +218 -0
- reconify_python-2.0.0/tests/test_openapi_coverage.py +66 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/tests/test_pagination.py +12 -17
- {reconify_python-0.1.0 → reconify_python-2.0.0}/uv.lock +1 -1
- reconify_python-0.1.0/CHANGELOG.md +0 -8
- reconify_python-0.1.0/PKG-INFO +0 -125
- reconify_python-0.1.0/README.md +0 -107
- reconify_python-0.1.0/src/reconify/client.py +0 -264
- reconify_python-0.1.0/src/reconify/models.py +0 -1067
- reconify_python-0.1.0/src/reconify/resources/__init__.py +0 -124
- reconify_python-0.1.0/src/reconify/resources/alerts.py +0 -58
- reconify_python-0.1.0/src/reconify/resources/events.py +0 -79
- reconify_python-0.1.0/src/reconify/resources/ingestion.py +0 -94
- reconify_python-0.1.0/src/reconify/resources/issues.py +0 -207
- reconify_python-0.1.0/src/reconify/resources/ledger.py +0 -205
- reconify_python-0.1.0/src/reconify/resources/reconciliations.py +0 -236
- reconify_python-0.1.0/src/reconify/resources/search.py +0 -37
- reconify_python-0.1.0/src/reconify/resources/setup.py +0 -311
- reconify_python-0.1.0/src/reconify/resources/transactions.py +0 -58
- reconify_python-0.1.0/src/reconify/resources/wallets.py +0 -79
- reconify_python-0.1.0/tests/test_client.py +0 -204
- reconify_python-0.1.0/tests/test_openapi_coverage.py +0 -88
- {reconify_python-0.1.0 → reconify_python-2.0.0}/.github/workflows/release.yml +0 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/LICENSE +0 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/reports/api-error-data-report.md +0 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/src/reconify/errors.py +0 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/src/reconify/py.typed +0 -0
- {reconify_python-0.1.0 → reconify_python-2.0.0}/src/reconify/resources/base.py +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"$schema" = "https://conductor.build/schemas/settings.repo.schema.json"
|
|
2
|
+
|
|
3
|
+
[scripts]
|
|
4
|
+
setup = "python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'"
|
|
5
|
+
run_mode = "concurrent"
|
|
6
|
+
|
|
7
|
+
[scripts.run.verify]
|
|
8
|
+
command = ".venv/bin/python scripts/fetch_contract.py && .venv/bin/ruff check . && .venv/bin/mypy src && .venv/bin/pytest -q && .venv/bin/python -m build"
|
|
9
|
+
icon = "check"
|
|
10
|
+
default = true
|
|
11
|
+
|
|
12
|
+
[scripts.run.sync-contract]
|
|
13
|
+
command = ".venv/bin/python scripts/fetch_contract.py --latest && .venv/bin/python -m pytest -q tests/test_openapi_coverage.py"
|
|
14
|
+
icon = "refresh-cw"
|
|
15
|
+
|
|
16
|
+
[scripts.run.release-check]
|
|
17
|
+
command = ".venv/bin/ruff check . && .venv/bin/mypy src && .venv/bin/pytest -q && .venv/bin/python -m build"
|
|
18
|
+
icon = "package-check"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Sync public OpenAPI contract
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
schedule:
|
|
6
|
+
- cron: "23 6 * * 1"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
pull-requests: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
sync:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
- run: python -m pip install --upgrade pip
|
|
21
|
+
- run: pip install ".[dev]"
|
|
22
|
+
- run: python scripts/fetch_contract.py --latest
|
|
23
|
+
- run: python -m pytest -q tests/test_openapi_coverage.py
|
|
24
|
+
- run: ruff check .
|
|
25
|
+
- run: mypy src
|
|
26
|
+
- run: pytest -q
|
|
27
|
+
- run: python -m build
|
|
28
|
+
- uses: peter-evans/create-pull-request@v7
|
|
29
|
+
with:
|
|
30
|
+
branch: automation/openapi-contract-sync
|
|
31
|
+
delete-branch: true
|
|
32
|
+
commit-message: "chore: sync public OpenAPI contract"
|
|
33
|
+
title: "chore: sync public OpenAPI contract"
|
|
34
|
+
body: |
|
|
35
|
+
This automated update was generated from the public Reconify OpenAPI
|
|
36
|
+
manifest. Review generated API models, examples, and migration notes before merging.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Reconify Python SDK agent guide
|
|
2
|
+
|
|
3
|
+
This repository publishes reconify-python, the typed synchronous and
|
|
4
|
+
asynchronous client for the public Reconify /v1 API.
|
|
5
|
+
|
|
6
|
+
## Contract authority
|
|
7
|
+
|
|
8
|
+
The SaaS Go API owns the contract. Fetch the pinned public artifact with
|
|
9
|
+
python scripts/fetch_contract.py, or update to the manifest version with
|
|
10
|
+
python scripts/fetch_contract.py --latest.
|
|
11
|
+
|
|
12
|
+
For local SaaS work, set RECONIFY_OPENAPI_SPEC to an explicit OpenAPI JSON
|
|
13
|
+
file. Do not use sibling repositories or absolute workspace paths.
|
|
14
|
+
|
|
15
|
+
The resource and transport layers are handwritten for Python ergonomics.
|
|
16
|
+
tests/test_openapi_coverage.py is the contract boundary and must remain
|
|
17
|
+
spec-driven. Keep Pydantic models aligned with the downloaded schemas.
|
|
18
|
+
|
|
19
|
+
## Supported surface
|
|
20
|
+
|
|
21
|
+
The SDK exposes metadata, events, ingestion, issues, and organization
|
|
22
|
+
resources. Internal /business/v1 routes and the former ledger, wallet, setup,
|
|
23
|
+
search, alert, and reconciliation surface are not public SDK APIs.
|
|
24
|
+
|
|
25
|
+
## Verification
|
|
26
|
+
|
|
27
|
+
Run ruff check ., mypy src, pytest -q, and python -m build before releasing.
|
|
28
|
+
Keep credentials out of errors and logs, preserve request IDs, and update
|
|
29
|
+
examples and migration notes when the public contract changes.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
- Rebuilt the client for the current 13-operation public monitoring API.
|
|
6
|
+
- Added metadata, organization, issue notes, and issue-linked event resources.
|
|
7
|
+
- Removed the obsolete ledger, wallet, setup, search, alert, and reconciliation
|
|
8
|
+
surface.
|
|
9
|
+
- Added pinned public OpenAPI synchronization and contract coverage checks.
|
|
10
|
+
|
|
11
|
+
## 0.1.0
|
|
12
|
+
|
|
13
|
+
- Initial typed Reconify Python SDK.
|
|
14
|
+
- Added sync and async clients for the retained 50-operation API surface.
|
|
15
|
+
- Excluded deep reconciliation adjustment, evidence, lifecycle, report-item,
|
|
16
|
+
and signoff operations.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: reconify-python
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Typed Python client for the Reconify Public API
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: httpx<1,>=0.27
|
|
9
|
+
Requires-Dist: pydantic<3,>=2.7
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
12
|
+
Requires-Dist: bump2version>=1.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
16
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Reconify Python SDK
|
|
20
|
+
|
|
21
|
+
Typed synchronous and asynchronous clients for the public Reconify v2 API.
|
|
22
|
+
|
|
23
|
+
## Installation and quickstart
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pip install reconify-python
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from reconify import Reconify
|
|
31
|
+
|
|
32
|
+
with Reconify(api_key="rk_...") as client:
|
|
33
|
+
events = client.events.list_events(limit=25)
|
|
34
|
+
for event in events.events:
|
|
35
|
+
print(event.id, event.status)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The API key may also come from `RECONIFY_API_KEY`. The default endpoint is
|
|
39
|
+
`https://api.reconifyhq.com/v2`. `RECONIFY_API_URL` or `base_url` can select a
|
|
40
|
+
staging or self-hosted endpoint, and URLs with or without `/v2` are accepted.
|
|
41
|
+
|
|
42
|
+
## Public resources
|
|
43
|
+
|
|
44
|
+
The client exposes metadata, events, ingestion, issues, and organization. The
|
|
45
|
+
current public contract contains exactly 13 operations. Python methods use
|
|
46
|
+
`snake_case` names and typed Pydantic v2 models from `reconify.models`.
|
|
47
|
+
|
|
48
|
+
Sync and async clients provide cursor iterators:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from reconify import AsyncReconify
|
|
52
|
+
|
|
53
|
+
async with AsyncReconify() as client:
|
|
54
|
+
async for event in client.iter_events(limit=100):
|
|
55
|
+
print(event.id)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Every operation supports `raw=True` for `RawResponse` and per-request timeout
|
|
59
|
+
through the `timeout` keyword. API errors expose status, detail, code,
|
|
60
|
+
validation details, response headers, and request ID without including keys or
|
|
61
|
+
request bodies. Safe methods retry bounded `429`, `503`, and transport failures
|
|
62
|
+
by default; unsafe retries require `RetryConfig(retry_unsafe_methods=True)`.
|
|
63
|
+
|
|
64
|
+
## Contract synchronization
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
python scripts/fetch_contract.py
|
|
68
|
+
python scripts/fetch_contract.py --latest
|
|
69
|
+
pytest -q tests/test_openapi_coverage.py
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The default source is the public manifest at
|
|
73
|
+
<https://docs.reconifyhq.com/openapi/manifest.json>. For local SaaS changes,
|
|
74
|
+
set `RECONIFY_OPENAPI_SPEC` to an explicit OpenAPI JSON file. The SDK never
|
|
75
|
+
depends on another checkout or an absolute workspace path.
|
|
76
|
+
|
|
77
|
+
## Migration to 2.0.0
|
|
78
|
+
|
|
79
|
+
Version `2.0.0` targets the v2 monitoring and issue-investigation API at `/v2`.
|
|
80
|
+
Generated operation IDs use stable `resource_action` identifiers while the
|
|
81
|
+
Python resource methods retain their snake_case names. See
|
|
82
|
+
[UPGRADING.md](UPGRADING.md).
|
|
83
|
+
|
|
84
|
+
## Build and release
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
ruff check .
|
|
88
|
+
mypy src
|
|
89
|
+
pytest -q
|
|
90
|
+
python -m build
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The release workflow publishes the built wheel to PyPI after a GitHub release.
|
|
94
|
+
Additive contract changes require a minor SDK release; SDK fixes require a
|
|
95
|
+
patch release; breaking public API changes require a new API version and SDK
|
|
96
|
+
major release.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Reconify Python SDK
|
|
2
|
+
|
|
3
|
+
Typed synchronous and asynchronous clients for the public Reconify v2 API.
|
|
4
|
+
|
|
5
|
+
## Installation and quickstart
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pip install reconify-python
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from reconify import Reconify
|
|
13
|
+
|
|
14
|
+
with Reconify(api_key="rk_...") as client:
|
|
15
|
+
events = client.events.list_events(limit=25)
|
|
16
|
+
for event in events.events:
|
|
17
|
+
print(event.id, event.status)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The API key may also come from `RECONIFY_API_KEY`. The default endpoint is
|
|
21
|
+
`https://api.reconifyhq.com/v2`. `RECONIFY_API_URL` or `base_url` can select a
|
|
22
|
+
staging or self-hosted endpoint, and URLs with or without `/v2` are accepted.
|
|
23
|
+
|
|
24
|
+
## Public resources
|
|
25
|
+
|
|
26
|
+
The client exposes metadata, events, ingestion, issues, and organization. The
|
|
27
|
+
current public contract contains exactly 13 operations. Python methods use
|
|
28
|
+
`snake_case` names and typed Pydantic v2 models from `reconify.models`.
|
|
29
|
+
|
|
30
|
+
Sync and async clients provide cursor iterators:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from reconify import AsyncReconify
|
|
34
|
+
|
|
35
|
+
async with AsyncReconify() as client:
|
|
36
|
+
async for event in client.iter_events(limit=100):
|
|
37
|
+
print(event.id)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Every operation supports `raw=True` for `RawResponse` and per-request timeout
|
|
41
|
+
through the `timeout` keyword. API errors expose status, detail, code,
|
|
42
|
+
validation details, response headers, and request ID without including keys or
|
|
43
|
+
request bodies. Safe methods retry bounded `429`, `503`, and transport failures
|
|
44
|
+
by default; unsafe retries require `RetryConfig(retry_unsafe_methods=True)`.
|
|
45
|
+
|
|
46
|
+
## Contract synchronization
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
python scripts/fetch_contract.py
|
|
50
|
+
python scripts/fetch_contract.py --latest
|
|
51
|
+
pytest -q tests/test_openapi_coverage.py
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The default source is the public manifest at
|
|
55
|
+
<https://docs.reconifyhq.com/openapi/manifest.json>. For local SaaS changes,
|
|
56
|
+
set `RECONIFY_OPENAPI_SPEC` to an explicit OpenAPI JSON file. The SDK never
|
|
57
|
+
depends on another checkout or an absolute workspace path.
|
|
58
|
+
|
|
59
|
+
## Migration to 2.0.0
|
|
60
|
+
|
|
61
|
+
Version `2.0.0` targets the v2 monitoring and issue-investigation API at `/v2`.
|
|
62
|
+
Generated operation IDs use stable `resource_action` identifiers while the
|
|
63
|
+
Python resource methods retain their snake_case names. See
|
|
64
|
+
[UPGRADING.md](UPGRADING.md).
|
|
65
|
+
|
|
66
|
+
## Build and release
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
ruff check .
|
|
70
|
+
mypy src
|
|
71
|
+
pytest -q
|
|
72
|
+
python -m build
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The release workflow publishes the built wheel to PyPI after a GitHub release.
|
|
76
|
+
Additive contract changes require a minor SDK release; SDK fixes require a
|
|
77
|
+
patch release; breaking public API changes require a new API version and SDK
|
|
78
|
+
major release.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Upgrading to 2.0.0
|
|
2
|
+
|
|
3
|
+
The 2.0.0 client targets the public Reconify v2 contract at `/v2`.
|
|
4
|
+
Generated operation IDs now use stable `resource_action` identifiers. The
|
|
5
|
+
Python resource methods keep their existing snake_case names.
|
|
6
|
+
|
|
7
|
+
Use these resources:
|
|
8
|
+
|
|
9
|
+
- metadata: API information and health
|
|
10
|
+
- events: event listing, lookup, and issue-linked evidence
|
|
11
|
+
- ingestion: monitoring event batches
|
|
12
|
+
- issues: issue listing, lookup, assignment, notes, and linked data
|
|
13
|
+
- organization: organization and member reads
|
|
14
|
+
|
|
15
|
+
Python models use snake_case fields and preserve unknown enum values through
|
|
16
|
+
tolerant string enums. Existing v1 clients can continue using `/v1`; v2
|
|
17
|
+
clients must use the v2 artifact and endpoint.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://context7.com/schema/context7.json",
|
|
3
|
+
"projectTitle": "Reconify Python SDK",
|
|
4
|
+
"description": "Typed synchronous and asynchronous Python clients for the Reconify Public API.",
|
|
5
|
+
"folders": ["src"],
|
|
6
|
+
"excludeFolders": ["tests", "reports"],
|
|
7
|
+
"rules": [
|
|
8
|
+
"Use Reconify for synchronous code and AsyncReconify for asynchronous code.",
|
|
9
|
+
"Provide the API key with api_key or RECONIFY_API_KEY; configure staging or self-hosted deployments with base_url.",
|
|
10
|
+
"Treat pagination cursors as opaque. When an endpoint accepts both cursor and offset pagination, after takes precedence.",
|
|
11
|
+
"Safe methods retry 429, 503, and transient transport failures by default. Mutating methods require RetryConfig(retry_unsafe_methods=True) to opt into retries.",
|
|
12
|
+
"Reuse each row's idempotencyKey when retrying transaction ingestion.",
|
|
13
|
+
"Use raw=True when callers need status, headers, request IDs, and raw response bodies; use the per-request timeout override when needed."
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Fetch and verify the pinned public OpenAPI contract."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from urllib.parse import urljoin
|
|
11
|
+
from urllib.request import urlopen
|
|
12
|
+
|
|
13
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
14
|
+
PIN_PATH = ROOT / ".openapi-contract.json"
|
|
15
|
+
DEFAULT_OUTPUT = ROOT / ".contract" / "reconify.openapi.json"
|
|
16
|
+
DEFAULT_MANIFEST_URL = "https://docs.reconifyhq.com/openapi/manifest.json"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> None:
|
|
20
|
+
output = Path(os.environ.get("RECONIFY_OPENAPI_SPEC", str(DEFAULT_OUTPUT)))
|
|
21
|
+
update_to_latest = "--latest" in sys.argv
|
|
22
|
+
if os.environ.get("RECONIFY_OPENAPI_SPEC"):
|
|
23
|
+
content = output.read_bytes()
|
|
24
|
+
if update_to_latest:
|
|
25
|
+
document = json.loads(content)
|
|
26
|
+
version = document.get("info", {}).get("version")
|
|
27
|
+
if not version:
|
|
28
|
+
raise SystemExit("Local OpenAPI contract has no info.version")
|
|
29
|
+
PIN_PATH.write_text(
|
|
30
|
+
json.dumps(
|
|
31
|
+
{
|
|
32
|
+
"version": version,
|
|
33
|
+
"sha256": hashlib.sha256(content).hexdigest(),
|
|
34
|
+
},
|
|
35
|
+
indent=2,
|
|
36
|
+
)
|
|
37
|
+
+ "\n"
|
|
38
|
+
)
|
|
39
|
+
print(f"Using local OpenAPI contract: {output}")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
pinned = json.loads(PIN_PATH.read_text())
|
|
43
|
+
manifest_url = os.environ.get("RECONIFY_OPENAPI_MANIFEST_URL", DEFAULT_MANIFEST_URL)
|
|
44
|
+
with urlopen(manifest_url, timeout=30) as response:
|
|
45
|
+
manifest = json.loads(response.read())
|
|
46
|
+
version = manifest["contract_version"] if update_to_latest else pinned["version"]
|
|
47
|
+
artifact = manifest["versions"].get(version)
|
|
48
|
+
if artifact is None:
|
|
49
|
+
raise SystemExit(f"OpenAPI manifest has no version {version}")
|
|
50
|
+
if not update_to_latest and artifact["sha256"] != pinned["sha256"]:
|
|
51
|
+
raise SystemExit(f"OpenAPI manifest checksum changed for pinned version {version}")
|
|
52
|
+
with urlopen(urljoin(manifest_url, artifact["url"]), timeout=30) as response:
|
|
53
|
+
content = response.read()
|
|
54
|
+
digest = hashlib.sha256(content).hexdigest()
|
|
55
|
+
if update_to_latest:
|
|
56
|
+
PIN_PATH.write_text(json.dumps({"version": version, "sha256": digest}, indent=2) + "\n")
|
|
57
|
+
elif digest != pinned["sha256"]:
|
|
58
|
+
raise SystemExit(f"OpenAPI checksum mismatch: expected {pinned['sha256']}, got {digest}")
|
|
59
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
output.write_bytes(content)
|
|
61
|
+
print(f"Fetched OpenAPI {version} to {output}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
main()
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Top-level synchronous and asynchronous Reconify clients."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import AsyncIterator, Iterator
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from .errors import ReconifyValidationError
|
|
12
|
+
from .pagination import aiter_cursor_pages, iter_cursor_pages
|
|
13
|
+
from .resources import (
|
|
14
|
+
ASYNC_RESOURCE_CLASSES,
|
|
15
|
+
SYNC_RESOURCE_CLASSES,
|
|
16
|
+
AsyncEvents,
|
|
17
|
+
AsyncIngestion,
|
|
18
|
+
AsyncIssues,
|
|
19
|
+
AsyncMetadata,
|
|
20
|
+
AsyncOrganization,
|
|
21
|
+
Events,
|
|
22
|
+
Ingestion,
|
|
23
|
+
Issues,
|
|
24
|
+
Metadata,
|
|
25
|
+
Organization,
|
|
26
|
+
)
|
|
27
|
+
from .transport import AsyncTransport, RetryConfig, SyncTransport
|
|
28
|
+
|
|
29
|
+
DEFAULT_BASE_URL = "https://api.reconifyhq.com/v2"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _normalize_base_url(base_url: str | None) -> str:
|
|
33
|
+
value = (base_url or os.getenv("RECONIFY_API_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
34
|
+
return value if value.endswith("/v2") else f"{value}/v2"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _api_key(api_key: str | None) -> str:
|
|
38
|
+
value = api_key or os.getenv("RECONIFY_API_KEY")
|
|
39
|
+
if not value:
|
|
40
|
+
raise ReconifyValidationError("An API key is required; set api_key or RECONIFY_API_KEY")
|
|
41
|
+
if not value.startswith("rk_"):
|
|
42
|
+
raise ReconifyValidationError("Reconify public API keys must start with rk_")
|
|
43
|
+
return value
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Reconify:
|
|
47
|
+
"""Synchronous typed Reconify API client."""
|
|
48
|
+
|
|
49
|
+
metadata: Metadata
|
|
50
|
+
events: Events
|
|
51
|
+
ingestion: Ingestion
|
|
52
|
+
issues: Issues
|
|
53
|
+
organization: Organization
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
api_key: str | None = None,
|
|
58
|
+
*,
|
|
59
|
+
base_url: str | None = None,
|
|
60
|
+
timeout: float | httpx.Timeout = 30.0,
|
|
61
|
+
request_id: str | None = None,
|
|
62
|
+
retry: RetryConfig | None = None,
|
|
63
|
+
http_client: httpx.Client | None = None,
|
|
64
|
+
) -> None:
|
|
65
|
+
self._transport = SyncTransport(
|
|
66
|
+
base_url=_normalize_base_url(base_url),
|
|
67
|
+
api_key=_api_key(api_key),
|
|
68
|
+
timeout=timeout,
|
|
69
|
+
request_id=request_id,
|
|
70
|
+
retry=retry,
|
|
71
|
+
client=http_client,
|
|
72
|
+
)
|
|
73
|
+
for name, resource_class in SYNC_RESOURCE_CLASSES.items():
|
|
74
|
+
setattr(self, name, resource_class(self._transport))
|
|
75
|
+
|
|
76
|
+
def close(self) -> None:
|
|
77
|
+
self._transport.close()
|
|
78
|
+
|
|
79
|
+
def __enter__(self) -> Reconify:
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def __exit__(self, *_: Any) -> None:
|
|
83
|
+
self.close()
|
|
84
|
+
|
|
85
|
+
def iter_events(self, **query: Any) -> Iterator[Any]:
|
|
86
|
+
return iter_cursor_pages(
|
|
87
|
+
lambda params: self.events.list_events(**params), item_field="events", query=query
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def iter_issues(self, **query: Any) -> Iterator[Any]:
|
|
91
|
+
return iter_cursor_pages(
|
|
92
|
+
lambda params: self.issues.list_issues(**params), item_field="issues", query=query
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def iter_issue_events(self, issue_id: str, **query: Any) -> Iterator[Any]:
|
|
96
|
+
return iter_cursor_pages(
|
|
97
|
+
lambda params: self.events.list_issue_events(issue_id, **params),
|
|
98
|
+
item_field="events",
|
|
99
|
+
query=query,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class AsyncReconify:
|
|
104
|
+
"""Asynchronous typed Reconify API client."""
|
|
105
|
+
|
|
106
|
+
metadata: AsyncMetadata
|
|
107
|
+
events: AsyncEvents
|
|
108
|
+
ingestion: AsyncIngestion
|
|
109
|
+
issues: AsyncIssues
|
|
110
|
+
organization: AsyncOrganization
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
api_key: str | None = None,
|
|
115
|
+
*,
|
|
116
|
+
base_url: str | None = None,
|
|
117
|
+
timeout: float | httpx.Timeout = 30.0,
|
|
118
|
+
request_id: str | None = None,
|
|
119
|
+
retry: RetryConfig | None = None,
|
|
120
|
+
http_client: httpx.AsyncClient | None = None,
|
|
121
|
+
) -> None:
|
|
122
|
+
self._transport = AsyncTransport(
|
|
123
|
+
base_url=_normalize_base_url(base_url),
|
|
124
|
+
api_key=_api_key(api_key),
|
|
125
|
+
timeout=timeout,
|
|
126
|
+
request_id=request_id,
|
|
127
|
+
retry=retry,
|
|
128
|
+
client=http_client,
|
|
129
|
+
)
|
|
130
|
+
for name, resource_class in ASYNC_RESOURCE_CLASSES.items():
|
|
131
|
+
setattr(self, name, resource_class(self._transport))
|
|
132
|
+
|
|
133
|
+
async def aclose(self) -> None:
|
|
134
|
+
await self._transport.aclose()
|
|
135
|
+
|
|
136
|
+
async def __aenter__(self) -> AsyncReconify:
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
async def __aexit__(self, *_: Any) -> None:
|
|
140
|
+
await self.aclose()
|
|
141
|
+
|
|
142
|
+
def iter_events(self, **query: Any) -> AsyncIterator[Any]:
|
|
143
|
+
return aiter_cursor_pages(
|
|
144
|
+
lambda params: self.events.list_events(**params), item_field="events", query=query
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def iter_issues(self, **query: Any) -> AsyncIterator[Any]:
|
|
148
|
+
return aiter_cursor_pages(
|
|
149
|
+
lambda params: self.issues.list_issues(**params), item_field="issues", query=query
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def iter_issue_events(self, issue_id: str, **query: Any) -> AsyncIterator[Any]:
|
|
153
|
+
return aiter_cursor_pages(
|
|
154
|
+
lambda params: self.events.list_issue_events(issue_id, **params),
|
|
155
|
+
item_field="events",
|
|
156
|
+
query=query,
|
|
157
|
+
)
|