reconify-python 2.0.0__tar.gz → 2.2.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-2.2.0/.openapi-contract.json +4 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/CHANGELOG.md +13 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/PKG-INFO +7 -3
- {reconify_python-2.0.0 → reconify_python-2.2.0}/README.md +6 -2
- reconify_python-2.2.0/UPGRADING.md +61 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/pyproject.toml +1 -1
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/__init__.py +1 -1
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/client.py +4 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/models.py +111 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/resources/__init__.py +7 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/resources/ingestion.py +9 -3
- reconify_python-2.2.0/src/reconify/resources/onchain.py +57 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/transport.py +2 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/tests/test_client.py +116 -4
- {reconify_python-2.0.0 → reconify_python-2.2.0}/tests/test_openapi_coverage.py +1 -1
- {reconify_python-2.0.0 → reconify_python-2.2.0}/uv.lock +1 -1
- reconify_python-2.0.0/.openapi-contract.json +0 -4
- reconify_python-2.0.0/UPGRADING.md +0 -17
- {reconify_python-2.0.0 → reconify_python-2.2.0}/.bumpversion.cfg +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/.conductor/settings.toml +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/.github/workflows/ci.yml +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/.github/workflows/release.yml +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/.github/workflows/sync-contract.yml +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/.gitignore +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/AGENTS.md +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/LICENSE +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/context7.json +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/reports/api-error-data-report.md +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/scripts/fetch_contract.py +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/errors.py +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/pagination.py +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/py.typed +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/resources/base.py +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/resources/events.py +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/resources/issues.py +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/resources/metadata.py +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/src/reconify/resources/organization.py +0 -0
- {reconify_python-2.0.0 → reconify_python-2.2.0}/tests/test_pagination.py +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0
|
|
4
|
+
|
|
5
|
+
- Synced with the public API contract 2.3.0.
|
|
6
|
+
- Added settlement event types, causation and revision metadata, financial
|
|
7
|
+
breakdowns, and settlement allocations.
|
|
8
|
+
- Added single-event and bare-array ingestion payloads alongside wrapped batches.
|
|
9
|
+
- Added synchronous and asynchronous on-chain evidence source registration.
|
|
10
|
+
|
|
11
|
+
## 2.1.0
|
|
12
|
+
|
|
13
|
+
- Added optional `correlation_id` support to the typed monitoring event request
|
|
14
|
+
model and ingestion serialization.
|
|
15
|
+
|
|
3
16
|
## 1.0.0
|
|
4
17
|
|
|
5
18
|
- Rebuilt the client for the current 13-operation public monitoring API.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: reconify-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Typed Python client for the Reconify Public API
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -41,8 +41,9 @@ staging or self-hosted endpoint, and URLs with or without `/v2` are accepted.
|
|
|
41
41
|
|
|
42
42
|
## Public resources
|
|
43
43
|
|
|
44
|
-
The client exposes metadata, events, ingestion, issues,
|
|
45
|
-
current public contract contains exactly
|
|
44
|
+
The client exposes metadata, events, ingestion, issues, organization, and
|
|
45
|
+
on-chain evidence registration. The current public contract contains exactly
|
|
46
|
+
14 operations. Python methods use
|
|
46
47
|
`snake_case` names and typed Pydantic v2 models from `reconify.models`.
|
|
47
48
|
|
|
48
49
|
Sync and async clients provide cursor iterators:
|
|
@@ -81,6 +82,9 @@ Generated operation IDs use stable `resource_action` identifiers while the
|
|
|
81
82
|
Python resource methods retain their snake_case names. See
|
|
82
83
|
[UPGRADING.md](UPGRADING.md).
|
|
83
84
|
|
|
85
|
+
Version `2.2.0` adds settlement event fields, flexible ingestion payloads, and
|
|
86
|
+
on-chain evidence source registration. See [UPGRADING.md](UPGRADING.md).
|
|
87
|
+
|
|
84
88
|
## Build and release
|
|
85
89
|
|
|
86
90
|
```sh
|
|
@@ -23,8 +23,9 @@ staging or self-hosted endpoint, and URLs with or without `/v2` are accepted.
|
|
|
23
23
|
|
|
24
24
|
## Public resources
|
|
25
25
|
|
|
26
|
-
The client exposes metadata, events, ingestion, issues,
|
|
27
|
-
current public contract contains exactly
|
|
26
|
+
The client exposes metadata, events, ingestion, issues, organization, and
|
|
27
|
+
on-chain evidence registration. The current public contract contains exactly
|
|
28
|
+
14 operations. Python methods use
|
|
28
29
|
`snake_case` names and typed Pydantic v2 models from `reconify.models`.
|
|
29
30
|
|
|
30
31
|
Sync and async clients provide cursor iterators:
|
|
@@ -63,6 +64,9 @@ Generated operation IDs use stable `resource_action` identifiers while the
|
|
|
63
64
|
Python resource methods retain their snake_case names. See
|
|
64
65
|
[UPGRADING.md](UPGRADING.md).
|
|
65
66
|
|
|
67
|
+
Version `2.2.0` adds settlement event fields, flexible ingestion payloads, and
|
|
68
|
+
on-chain evidence source registration. See [UPGRADING.md](UPGRADING.md).
|
|
69
|
+
|
|
66
70
|
## Build and release
|
|
67
71
|
|
|
68
72
|
```sh
|
|
@@ -0,0 +1,61 @@
|
|
|
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.
|
|
18
|
+
|
|
19
|
+
## Upgrading to 2.1.0
|
|
20
|
+
|
|
21
|
+
The 2.1.0 client adds the optional `correlation_id` field to
|
|
22
|
+
`MonitoringEvent`. Use it when the same transaction spans services that report
|
|
23
|
+
different references:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
MonitoringEvent(
|
|
27
|
+
flow="payment_to_wallet",
|
|
28
|
+
type="payment.succeeded",
|
|
29
|
+
reference="order-123",
|
|
30
|
+
entity_id="wallet-123",
|
|
31
|
+
correlation_id="checkout-123",
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The value is sent as event metadata and does not change operation grouping or
|
|
36
|
+
evaluation behavior.
|
|
37
|
+
|
|
38
|
+
## Upgrading to 2.2.0
|
|
39
|
+
|
|
40
|
+
The 2.2.0 client follows public API contract 2.3.0. Monitoring ingestion now
|
|
41
|
+
accepts a single `MonitoringEvent`, a list of events, or the existing
|
|
42
|
+
`MonitoringBatchRequest` wrapper. Settlement flows and event types support
|
|
43
|
+
financial breakdowns, allocations, causation IDs, and revision IDs.
|
|
44
|
+
|
|
45
|
+
On-chain evidence enrichment can be queued for an accepted event:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
client.onchain.register_onchain_source(
|
|
49
|
+
OnchainSourceRequest(
|
|
50
|
+
flow="provider_to_settlement_account",
|
|
51
|
+
operation_reference="settlement-123",
|
|
52
|
+
source_event_id="evt_01J3Y0M8VJQ5W1R3E4J4K7N8P9",
|
|
53
|
+
kind="transaction",
|
|
54
|
+
locator=OnchainSourceLocator(
|
|
55
|
+
network="ethereum-mainnet",
|
|
56
|
+
transaction_reference="0x...",
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
idempotency_key="source-settlement-123",
|
|
60
|
+
)
|
|
61
|
+
```
|
|
@@ -17,11 +17,13 @@ from .resources import (
|
|
|
17
17
|
AsyncIngestion,
|
|
18
18
|
AsyncIssues,
|
|
19
19
|
AsyncMetadata,
|
|
20
|
+
AsyncOnchain,
|
|
20
21
|
AsyncOrganization,
|
|
21
22
|
Events,
|
|
22
23
|
Ingestion,
|
|
23
24
|
Issues,
|
|
24
25
|
Metadata,
|
|
26
|
+
Onchain,
|
|
25
27
|
Organization,
|
|
26
28
|
)
|
|
27
29
|
from .transport import AsyncTransport, RetryConfig, SyncTransport
|
|
@@ -51,6 +53,7 @@ class Reconify:
|
|
|
51
53
|
ingestion: Ingestion
|
|
52
54
|
issues: Issues
|
|
53
55
|
organization: Organization
|
|
56
|
+
onchain: Onchain
|
|
54
57
|
|
|
55
58
|
def __init__(
|
|
56
59
|
self,
|
|
@@ -108,6 +111,7 @@ class AsyncReconify:
|
|
|
108
111
|
ingestion: AsyncIngestion
|
|
109
112
|
issues: AsyncIssues
|
|
110
113
|
organization: AsyncOrganization
|
|
114
|
+
onchain: AsyncOnchain
|
|
111
115
|
|
|
112
116
|
def __init__(
|
|
113
117
|
self,
|
|
@@ -36,6 +36,7 @@ class Flow(TolerantStrEnum):
|
|
|
36
36
|
PAYMENT_TO_ORDER = "payment_to_order"
|
|
37
37
|
WALLET_TO_WALLET = "wallet_to_wallet"
|
|
38
38
|
WALLET_TO_PAYOUT = "wallet_to_payout"
|
|
39
|
+
PROVIDER_TO_SETTLEMENT_ACCOUNT = "provider_to_settlement_account"
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
class EventType(TolerantStrEnum):
|
|
@@ -49,6 +50,13 @@ class EventType(TolerantStrEnum):
|
|
|
49
50
|
WALLET_CREDITED = "wallet.credited"
|
|
50
51
|
WALLET_DEBITED = "wallet.debited"
|
|
51
52
|
WALLET_REFUNDED = "wallet.refunded"
|
|
53
|
+
SETTLEMENT_ALLOCATIONS_RECORDED = "settlement.allocations_recorded"
|
|
54
|
+
SETTLEMENT_CREATED = "settlement.created"
|
|
55
|
+
SETTLEMENT_DISBURSED = "settlement.disbursed"
|
|
56
|
+
SETTLEMENT_FAILED = "settlement.failed"
|
|
57
|
+
SETTLEMENT_FINALIZED = "settlement.finalized"
|
|
58
|
+
SETTLEMENT_RECEIVED = "settlement.received"
|
|
59
|
+
SETTLEMENT_REVISED = "settlement.revised"
|
|
52
60
|
|
|
53
61
|
|
|
54
62
|
class EntityType(TolerantStrEnum):
|
|
@@ -102,6 +110,44 @@ class MonitoringErrorCode(TolerantStrEnum):
|
|
|
102
110
|
MALFORMED_REQUEST = "malformed_request"
|
|
103
111
|
|
|
104
112
|
|
|
113
|
+
class OnchainSourceKind(TolerantStrEnum):
|
|
114
|
+
PROVIDER_PAYMENT = "provider_payment"
|
|
115
|
+
TRANSACTION = "transaction"
|
|
116
|
+
ADDRESS_REFERENCE = "address_reference"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class OnchainNetwork(TolerantStrEnum):
|
|
120
|
+
ETHEREUM_MAINNET = "ethereum-mainnet"
|
|
121
|
+
ETHEREUM_SEPOLIA = "ethereum-sepolia"
|
|
122
|
+
SOLANA_MAINNET = "solana-mainnet"
|
|
123
|
+
SOLANA_DEVNET = "solana-devnet"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class OnchainSourceStatus(TolerantStrEnum):
|
|
127
|
+
QUEUED = "queued"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class FinancialComponentKind(TolerantStrEnum):
|
|
131
|
+
FEE = "fee"
|
|
132
|
+
COMMISSION = "commission"
|
|
133
|
+
TAX = "tax"
|
|
134
|
+
RESERVE = "reserve"
|
|
135
|
+
ADJUSTMENT = "adjustment"
|
|
136
|
+
OTHER = "other"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class FinancialComponentEffect(TolerantStrEnum):
|
|
140
|
+
ADD = "add"
|
|
141
|
+
DEDUCT = "deduct"
|
|
142
|
+
INCLUDED = "included"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class SettlementAllocationType(TolerantStrEnum):
|
|
146
|
+
PAYMENT = "payment"
|
|
147
|
+
REFUND = "refund"
|
|
148
|
+
CHARGEBACK = "chargeback"
|
|
149
|
+
|
|
150
|
+
|
|
105
151
|
class APIInfo(ResponseModel):
|
|
106
152
|
name: str
|
|
107
153
|
version: str
|
|
@@ -113,6 +159,31 @@ class Health(ResponseModel):
|
|
|
113
159
|
status: str
|
|
114
160
|
|
|
115
161
|
|
|
162
|
+
class FinancialComponent(RequestModel):
|
|
163
|
+
amount: str
|
|
164
|
+
kind: FinancialComponentKind
|
|
165
|
+
effect: FinancialComponentEffect
|
|
166
|
+
description: str | None = None
|
|
167
|
+
provider_type: str | None = None
|
|
168
|
+
reference: str | None = None
|
|
169
|
+
metadata: dict[str, str | int | float | bool] | None = None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class FinancialBreakdown(RequestModel):
|
|
173
|
+
gross: str
|
|
174
|
+
net: str
|
|
175
|
+
components: list[FinancialComponent] = Field(max_length=100)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class SettlementAllocation(RequestModel):
|
|
179
|
+
allocation_id: str
|
|
180
|
+
allocation_type: SettlementAllocationType
|
|
181
|
+
flow: str
|
|
182
|
+
reference: str
|
|
183
|
+
amount: str
|
|
184
|
+
event_id: str | None = None
|
|
185
|
+
|
|
186
|
+
|
|
116
187
|
class MonitoringEventData(RequestModel):
|
|
117
188
|
provider: str | None = None
|
|
118
189
|
integration_ref: str | None = None
|
|
@@ -121,6 +192,8 @@ class MonitoringEventData(RequestModel):
|
|
|
121
192
|
failure_code: str | None = None
|
|
122
193
|
failure_message: str | None = None
|
|
123
194
|
retryable: bool | None = None
|
|
195
|
+
revises_event_id: str | None = None
|
|
196
|
+
allocations: list[SettlementAllocation] | None = Field(default=None, max_length=500)
|
|
124
197
|
|
|
125
198
|
|
|
126
199
|
class MonitoringEvent(RequestModel):
|
|
@@ -131,8 +204,11 @@ class MonitoringEvent(RequestModel):
|
|
|
131
204
|
entity_id: str
|
|
132
205
|
occurred_at: datetime | None = None
|
|
133
206
|
amount: str | None = None
|
|
207
|
+
correlation_id: str | None = None
|
|
208
|
+
causation_id: str | None = None
|
|
134
209
|
currency: str | None = None
|
|
135
210
|
data: MonitoringEventData | None = None
|
|
211
|
+
financial_breakdown: FinancialBreakdown | None = None
|
|
136
212
|
metadata: dict[str, str | int | float | bool] | None = None
|
|
137
213
|
|
|
138
214
|
|
|
@@ -171,8 +247,17 @@ class Event(ResponseModel):
|
|
|
171
247
|
received_at: datetime
|
|
172
248
|
amount: str | None = None
|
|
173
249
|
currency: str | None = None
|
|
250
|
+
causation_id: str | None = None
|
|
251
|
+
correlation_id: str | None = None
|
|
252
|
+
revises_event_id: str | None = None
|
|
174
253
|
provider: str | None = None
|
|
175
254
|
status: ReceiptStatus
|
|
255
|
+
gross: str | None = None
|
|
256
|
+
net: str | None = None
|
|
257
|
+
component_count: int | None = None
|
|
258
|
+
allocation_count: int | None = None
|
|
259
|
+
financial_breakdown: FinancialBreakdown | None = None
|
|
260
|
+
allocations: list[SettlementAllocation] | None = None
|
|
176
261
|
|
|
177
262
|
|
|
178
263
|
class ListEventsResponse(ResponseModel):
|
|
@@ -243,5 +328,31 @@ class Error(ResponseModel):
|
|
|
243
328
|
detail: str | None = None
|
|
244
329
|
|
|
245
330
|
|
|
331
|
+
class OnchainSourceLocator(RequestModel):
|
|
332
|
+
integration_ref: str | None = None
|
|
333
|
+
network: OnchainNetwork | None = None
|
|
334
|
+
protocol_reference: str | None = None
|
|
335
|
+
provider_reference: str | None = None
|
|
336
|
+
provider_transaction_id: str | None = None
|
|
337
|
+
receiving_address: str | None = None
|
|
338
|
+
transaction_reference: str | None = None
|
|
339
|
+
window_end: datetime | None = None
|
|
340
|
+
window_start: datetime | None = None
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class OnchainSourceRequest(RequestModel):
|
|
344
|
+
flow: str
|
|
345
|
+
operation_reference: str = Field(min_length=1, max_length=256)
|
|
346
|
+
source_event_id: str = Field(min_length=1, max_length=256)
|
|
347
|
+
kind: OnchainSourceKind
|
|
348
|
+
locator: OnchainSourceLocator
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class OnchainSourceResponse(ResponseModel):
|
|
352
|
+
id: str
|
|
353
|
+
status: OnchainSourceStatus
|
|
354
|
+
duplicate: bool
|
|
355
|
+
|
|
356
|
+
|
|
246
357
|
def model_dump(model: BaseModel) -> dict[str, Any]:
|
|
247
358
|
return model.model_dump(mode="json", by_alias=True, exclude_none=True, exclude_unset=True)
|
|
@@ -4,6 +4,7 @@ from .events import AsyncEvents, Events
|
|
|
4
4
|
from .ingestion import AsyncIngestion, Ingestion
|
|
5
5
|
from .issues import AsyncIssues, Issues
|
|
6
6
|
from .metadata import AsyncMetadata, Metadata
|
|
7
|
+
from .onchain import AsyncOnchain, Onchain
|
|
7
8
|
from .organization import AsyncOrganization, Organization
|
|
8
9
|
|
|
9
10
|
SYNC_RESOURCE_CLASSES = {
|
|
@@ -12,6 +13,7 @@ SYNC_RESOURCE_CLASSES = {
|
|
|
12
13
|
"ingestion": Ingestion,
|
|
13
14
|
"issues": Issues,
|
|
14
15
|
"organization": Organization,
|
|
16
|
+
"onchain": Onchain,
|
|
15
17
|
}
|
|
16
18
|
ASYNC_RESOURCE_CLASSES = {
|
|
17
19
|
"metadata": AsyncMetadata,
|
|
@@ -19,6 +21,7 @@ ASYNC_RESOURCE_CLASSES = {
|
|
|
19
21
|
"ingestion": AsyncIngestion,
|
|
20
22
|
"issues": AsyncIssues,
|
|
21
23
|
"organization": AsyncOrganization,
|
|
24
|
+
"onchain": AsyncOnchain,
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
OPERATION_SPECS = {
|
|
@@ -35,6 +38,7 @@ OPERATION_SPECS = {
|
|
|
35
38
|
"issues_add_note": ("issues", "POST", "/issues/{issue_id}/notes"),
|
|
36
39
|
"organization_get": ("organization", "GET", "/organization"),
|
|
37
40
|
"organization_list_members": ("organization", "GET", "/organization/members"),
|
|
41
|
+
"register-onchain-source": ("onchain", "POST", "/onchain-sources"),
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
OPERATION_METHODS = {
|
|
@@ -51,6 +55,7 @@ OPERATION_METHODS = {
|
|
|
51
55
|
"issues_add_note": "add_issue_note",
|
|
52
56
|
"organization_get": "get_organization",
|
|
53
57
|
"organization_list_members": "list_organization_members",
|
|
58
|
+
"register-onchain-source": "register_onchain_source",
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
__all__ = [
|
|
@@ -59,11 +64,13 @@ __all__ = [
|
|
|
59
64
|
"AsyncIssues",
|
|
60
65
|
"AsyncMetadata",
|
|
61
66
|
"AsyncOrganization",
|
|
67
|
+
"AsyncOnchain",
|
|
62
68
|
"Events",
|
|
63
69
|
"Ingestion",
|
|
64
70
|
"Issues",
|
|
65
71
|
"Metadata",
|
|
66
72
|
"Organization",
|
|
73
|
+
"Onchain",
|
|
67
74
|
"ASYNC_RESOURCE_CLASSES",
|
|
68
75
|
"OPERATION_SPECS",
|
|
69
76
|
"OPERATION_METHODS",
|
|
@@ -4,13 +4,16 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
-
from ..models import MonitoringBatchRequest, MonitoringBatchResponse
|
|
7
|
+
from ..models import MonitoringBatchRequest, MonitoringBatchResponse, MonitoringEvent
|
|
8
8
|
from .base import AsyncResource, SyncResource
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Ingestion(SyncResource):
|
|
12
12
|
def ingest_monitoring_events(
|
|
13
|
-
self,
|
|
13
|
+
self,
|
|
14
|
+
body: MonitoringEvent | list[MonitoringEvent] | MonitoringBatchRequest,
|
|
15
|
+
raw: bool = False,
|
|
16
|
+
**query: Any,
|
|
14
17
|
) -> Any:
|
|
15
18
|
return self._request(
|
|
16
19
|
"POST",
|
|
@@ -24,7 +27,10 @@ class Ingestion(SyncResource):
|
|
|
24
27
|
|
|
25
28
|
class AsyncIngestion(AsyncResource):
|
|
26
29
|
async def ingest_monitoring_events(
|
|
27
|
-
self,
|
|
30
|
+
self,
|
|
31
|
+
body: MonitoringEvent | list[MonitoringEvent] | MonitoringBatchRequest,
|
|
32
|
+
raw: bool = False,
|
|
33
|
+
**query: Any,
|
|
28
34
|
) -> Any:
|
|
29
35
|
return await self._request(
|
|
30
36
|
"POST",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""On-chain evidence source registration resource client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..errors import ReconifyValidationError
|
|
8
|
+
from ..models import OnchainSourceRequest, OnchainSourceResponse
|
|
9
|
+
from .base import AsyncResource, SyncResource
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Onchain(SyncResource):
|
|
13
|
+
def register_onchain_source(
|
|
14
|
+
self,
|
|
15
|
+
body: OnchainSourceRequest,
|
|
16
|
+
*,
|
|
17
|
+
idempotency_key: str,
|
|
18
|
+
raw: bool = False,
|
|
19
|
+
**query: Any,
|
|
20
|
+
) -> Any:
|
|
21
|
+
if not idempotency_key or len(idempotency_key) > 200:
|
|
22
|
+
raise ReconifyValidationError("idempotency_key must be between 1 and 200 characters")
|
|
23
|
+
return self._request(
|
|
24
|
+
"POST",
|
|
25
|
+
"/onchain-sources",
|
|
26
|
+
params=query,
|
|
27
|
+
body=body,
|
|
28
|
+
response_model=OnchainSourceResponse,
|
|
29
|
+
raw=raw,
|
|
30
|
+
headers={"Idempotency-Key": idempotency_key},
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
register_source = register_onchain_source
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AsyncOnchain(AsyncResource):
|
|
37
|
+
async def register_onchain_source(
|
|
38
|
+
self,
|
|
39
|
+
body: OnchainSourceRequest,
|
|
40
|
+
*,
|
|
41
|
+
idempotency_key: str,
|
|
42
|
+
raw: bool = False,
|
|
43
|
+
**query: Any,
|
|
44
|
+
) -> Any:
|
|
45
|
+
if not idempotency_key or len(idempotency_key) > 200:
|
|
46
|
+
raise ReconifyValidationError("idempotency_key must be between 1 and 200 characters")
|
|
47
|
+
return await self._request(
|
|
48
|
+
"POST",
|
|
49
|
+
"/onchain-sources",
|
|
50
|
+
params=query,
|
|
51
|
+
body=body,
|
|
52
|
+
response_model=OnchainSourceResponse,
|
|
53
|
+
raw=raw,
|
|
54
|
+
headers={"Idempotency-Key": idempotency_key},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
register_source = register_onchain_source
|
|
@@ -88,6 +88,8 @@ def _validate_request_body(body: Any) -> Any:
|
|
|
88
88
|
return model_dump(body)
|
|
89
89
|
except ValidationError as exc:
|
|
90
90
|
raise ReconifyValidationError(str(exc)) from exc
|
|
91
|
+
if isinstance(body, list):
|
|
92
|
+
return [_validate_request_body(item) for item in body]
|
|
91
93
|
return body
|
|
92
94
|
|
|
93
95
|
|
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
import httpx
|
|
6
6
|
import pytest
|
|
7
7
|
|
|
8
|
-
from reconify import Reconify
|
|
8
|
+
from reconify import AsyncReconify, Reconify
|
|
9
9
|
from reconify.errors import (
|
|
10
10
|
ReconifyAuthenticationError,
|
|
11
11
|
ReconifyConflictError,
|
|
@@ -19,6 +19,8 @@ from reconify.models import (
|
|
|
19
19
|
AddNoteRequest,
|
|
20
20
|
MonitoringBatchRequest,
|
|
21
21
|
MonitoringEvent,
|
|
22
|
+
OnchainSourceLocator,
|
|
23
|
+
OnchainSourceRequest,
|
|
22
24
|
PatchIssueRequest,
|
|
23
25
|
)
|
|
24
26
|
from reconify.transport import RetryConfig
|
|
@@ -92,6 +94,7 @@ def test_ingestion_serializes_typed_body() -> None:
|
|
|
92
94
|
reference="order-1",
|
|
93
95
|
entity_id="wallet-1",
|
|
94
96
|
amount="10.00",
|
|
97
|
+
correlation_id="checkout-123",
|
|
95
98
|
currency="USD",
|
|
96
99
|
)
|
|
97
100
|
]
|
|
@@ -104,6 +107,7 @@ def test_ingestion_serializes_typed_body() -> None:
|
|
|
104
107
|
client.ingestion.ingest_monitoring_events(body)
|
|
105
108
|
|
|
106
109
|
assert json.loads(requests[0].content)["events"][0]["entity_id"] == "wallet-1"
|
|
110
|
+
assert json.loads(requests[0].content)["events"][0]["correlation_id"] == "checkout-123"
|
|
107
111
|
assert requests[0].url.path == "/v2/events"
|
|
108
112
|
|
|
109
113
|
|
|
@@ -142,6 +146,116 @@ def test_note_idempotency_header_and_issue_assignment() -> None:
|
|
|
142
146
|
assert requests[1].method == "PATCH"
|
|
143
147
|
|
|
144
148
|
|
|
149
|
+
def test_onchain_source_registration() -> None:
|
|
150
|
+
requests: list[httpx.Request] = []
|
|
151
|
+
|
|
152
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
153
|
+
requests.append(request)
|
|
154
|
+
return httpx.Response(
|
|
155
|
+
202, json={"id": "src-1", "status": "queued", "duplicate": False}, request=request
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
body = OnchainSourceRequest(
|
|
159
|
+
flow="provider_to_settlement_account",
|
|
160
|
+
operation_reference="settlement-1",
|
|
161
|
+
source_event_id="evt_1",
|
|
162
|
+
kind="transaction",
|
|
163
|
+
locator=OnchainSourceLocator(network="ethereum-mainnet", transaction_reference="0xabc"),
|
|
164
|
+
)
|
|
165
|
+
with Reconify(
|
|
166
|
+
"rk_test",
|
|
167
|
+
base_url="http://api.test",
|
|
168
|
+
http_client=httpx.Client(transport=httpx.MockTransport(handler)),
|
|
169
|
+
) as client:
|
|
170
|
+
result = client.onchain.register_onchain_source(body, idempotency_key="source-1")
|
|
171
|
+
|
|
172
|
+
assert result.id == "src-1"
|
|
173
|
+
assert requests[0].url.path == "/v2/onchain-sources"
|
|
174
|
+
assert requests[0].headers["Idempotency-Key"] == "source-1"
|
|
175
|
+
assert json.loads(requests[0].content)["locator"]["transaction_reference"] == "0xabc"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
async def test_async_onchain_source_registration() -> None:
|
|
179
|
+
requests: list[httpx.Request] = []
|
|
180
|
+
|
|
181
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
182
|
+
requests.append(request)
|
|
183
|
+
return httpx.Response(
|
|
184
|
+
202, json={"id": "src-1", "status": "queued", "duplicate": False}, request=request
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
body = OnchainSourceRequest(
|
|
188
|
+
flow="provider_to_settlement_account",
|
|
189
|
+
operation_reference="settlement-1",
|
|
190
|
+
source_event_id="evt_1",
|
|
191
|
+
kind="transaction",
|
|
192
|
+
locator=OnchainSourceLocator(network="ethereum-mainnet"),
|
|
193
|
+
)
|
|
194
|
+
async with AsyncReconify(
|
|
195
|
+
"rk_test",
|
|
196
|
+
base_url="http://api.test",
|
|
197
|
+
http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)),
|
|
198
|
+
) as client:
|
|
199
|
+
result = await client.onchain.register_onchain_source(body, idempotency_key="source-1")
|
|
200
|
+
|
|
201
|
+
assert result.status == "queued"
|
|
202
|
+
assert requests[0].headers["Idempotency-Key"] == "source-1"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_expanded_event_fields_parse() -> None:
|
|
206
|
+
from reconify.models import Event
|
|
207
|
+
|
|
208
|
+
event = Event.model_validate(
|
|
209
|
+
{
|
|
210
|
+
**_event(),
|
|
211
|
+
"flow": "provider_to_settlement_account",
|
|
212
|
+
"event_type": "settlement.created",
|
|
213
|
+
"entity_type": "settlement_account",
|
|
214
|
+
"causation_id": "evt_prior",
|
|
215
|
+
"correlation_id": "settlement-1",
|
|
216
|
+
"revises_event_id": None,
|
|
217
|
+
"gross": "100.00",
|
|
218
|
+
"net": "98.00",
|
|
219
|
+
"component_count": 1,
|
|
220
|
+
"allocation_count": 0,
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
assert event.event_type.value == "settlement.created"
|
|
225
|
+
assert event.gross == "100.00"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@pytest.mark.parametrize(
|
|
229
|
+
"body",
|
|
230
|
+
[
|
|
231
|
+
MonitoringEvent(
|
|
232
|
+
flow="payment_to_wallet", type="payment.succeeded", reference="r", entity_id="w"
|
|
233
|
+
),
|
|
234
|
+
[
|
|
235
|
+
MonitoringEvent(
|
|
236
|
+
flow="payment_to_wallet", type="payment.succeeded", reference="r", entity_id="w"
|
|
237
|
+
)
|
|
238
|
+
],
|
|
239
|
+
],
|
|
240
|
+
)
|
|
241
|
+
def test_ingestion_accepts_single_and_bare_array(body: object) -> None:
|
|
242
|
+
requests: list[httpx.Request] = []
|
|
243
|
+
|
|
244
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
245
|
+
requests.append(request)
|
|
246
|
+
return httpx.Response(202, json={"results": []}, request=request)
|
|
247
|
+
|
|
248
|
+
with Reconify(
|
|
249
|
+
"rk_test",
|
|
250
|
+
base_url="http://api.test",
|
|
251
|
+
http_client=httpx.Client(transport=httpx.MockTransport(handler)),
|
|
252
|
+
) as client:
|
|
253
|
+
client.ingestion.ingest_monitoring_events(body) # type: ignore[arg-type]
|
|
254
|
+
|
|
255
|
+
payload = json.loads(requests[0].content)
|
|
256
|
+
assert isinstance(payload, (dict, list))
|
|
257
|
+
|
|
258
|
+
|
|
145
259
|
def test_errors_are_typed_and_safe() -> None:
|
|
146
260
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
147
261
|
return httpx.Response(
|
|
@@ -177,9 +291,7 @@ def test_errors_are_typed_and_safe() -> None:
|
|
|
177
291
|
(503, ReconifyServiceUnavailableError),
|
|
178
292
|
],
|
|
179
293
|
)
|
|
180
|
-
def test_public_error_statuses_are_typed(
|
|
181
|
-
status: int, error_type: type[Exception]
|
|
182
|
-
) -> None:
|
|
294
|
+
def test_public_error_statuses_are_typed(status: int, error_type: type[Exception]) -> None:
|
|
183
295
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
184
296
|
return httpx.Response(
|
|
185
297
|
status,
|
|
@@ -40,7 +40,7 @@ def test_openapi_contains_only_public_routes() -> None:
|
|
|
40
40
|
|
|
41
41
|
def test_every_openapi_operation_has_a_public_method() -> None:
|
|
42
42
|
operations = _operations()
|
|
43
|
-
assert len(operations) ==
|
|
43
|
+
assert len(operations) == 14
|
|
44
44
|
assert len({operation_id for operation_id, _, _ in operations}) == len(operations)
|
|
45
45
|
assert len(OPERATION_SPECS) == len(operations)
|
|
46
46
|
for operation_id, verb, route in operations:
|
|
@@ -1,17 +0,0 @@
|
|
|
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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|