reconify-python 0.1.0__py3-none-any.whl
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/__init__.py +36 -0
- reconify/client.py +264 -0
- reconify/errors.py +132 -0
- reconify/models.py +1067 -0
- reconify/pagination.py +111 -0
- reconify/py.typed +0 -0
- reconify/resources/__init__.py +124 -0
- reconify/resources/alerts.py +58 -0
- reconify/resources/base.py +96 -0
- reconify/resources/events.py +79 -0
- reconify/resources/ingestion.py +94 -0
- reconify/resources/issues.py +207 -0
- reconify/resources/ledger.py +205 -0
- reconify/resources/reconciliations.py +236 -0
- reconify/resources/search.py +37 -0
- reconify/resources/setup.py +311 -0
- reconify/resources/transactions.py +58 -0
- reconify/resources/wallets.py +79 -0
- reconify/transport.py +299 -0
- reconify_python-0.1.0.dist-info/METADATA +125 -0
- reconify_python-0.1.0.dist-info/RECORD +23 -0
- reconify_python-0.1.0.dist-info/WHEEL +4 -0
- reconify_python-0.1.0.dist-info/licenses/LICENSE +21 -0
reconify/transport.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""HTTP transport, retry policy, and raw response handling."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import random
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from email.utils import parsedate_to_datetime
|
|
12
|
+
from typing import Any, Generic, TypeVar, cast
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
from pydantic import BaseModel, ValidationError
|
|
16
|
+
|
|
17
|
+
from .errors import ReconifyError, ReconifyValidationError, error_for_response
|
|
18
|
+
from .models import model_dump
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T", bound=BaseModel)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class RetryConfig:
|
|
25
|
+
"""Bounded retry settings. Retries apply to GET by default."""
|
|
26
|
+
|
|
27
|
+
max_retries: int = 2
|
|
28
|
+
base_delay: float = 0.25
|
|
29
|
+
max_delay: float = 8.0
|
|
30
|
+
jitter: float = 0.25
|
|
31
|
+
retry_unsafe_methods: bool = False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class RawResponse(Generic[T]):
|
|
36
|
+
"""Unparsed response data returned by an operation with ``raw=True``."""
|
|
37
|
+
|
|
38
|
+
status_code: int
|
|
39
|
+
headers: httpx.Headers
|
|
40
|
+
body: bytes
|
|
41
|
+
request_id: str | None
|
|
42
|
+
|
|
43
|
+
def json(self) -> Any:
|
|
44
|
+
return json.loads(self.body) if self.body else None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _retry_after(value: str | None) -> float | None:
|
|
48
|
+
if not value:
|
|
49
|
+
return None
|
|
50
|
+
try:
|
|
51
|
+
return max(0.0, float(value))
|
|
52
|
+
except ValueError:
|
|
53
|
+
try:
|
|
54
|
+
parsed = parsedate_to_datetime(value)
|
|
55
|
+
if parsed.tzinfo is None:
|
|
56
|
+
parsed = parsed.replace(tzinfo=timezone.utc)
|
|
57
|
+
return max(0.0, (parsed - datetime.now(timezone.utc)).total_seconds())
|
|
58
|
+
except (TypeError, ValueError, OverflowError):
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _sleep_for(attempt: int, config: RetryConfig, retry_after: str | None) -> float:
|
|
63
|
+
server_delay = _retry_after(retry_after)
|
|
64
|
+
if server_delay is not None:
|
|
65
|
+
return min(config.max_delay, server_delay)
|
|
66
|
+
exponential = min(config.max_delay, config.base_delay * (2**attempt))
|
|
67
|
+
return float(min(config.max_delay, exponential + random.uniform(0.0, config.jitter)))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _can_retry_exception(method: str, attempts: int, config: RetryConfig) -> bool:
|
|
71
|
+
if attempts >= config.max_retries:
|
|
72
|
+
return False
|
|
73
|
+
return method.upper() in {"GET", "HEAD", "OPTIONS"} or config.retry_unsafe_methods
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _payload(response: httpx.Response) -> Any:
|
|
77
|
+
if not response.content:
|
|
78
|
+
return None
|
|
79
|
+
try:
|
|
80
|
+
return response.json()
|
|
81
|
+
except (ValueError, json.JSONDecodeError):
|
|
82
|
+
return {"detail": response.text}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _validate_request_body(body: Any) -> Any:
|
|
86
|
+
if isinstance(body, BaseModel):
|
|
87
|
+
try:
|
|
88
|
+
return model_dump(body)
|
|
89
|
+
except ValidationError as exc:
|
|
90
|
+
raise ReconifyValidationError(str(exc)) from exc
|
|
91
|
+
return body
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _validate_integrity_payload(path: str, body: Any) -> None:
|
|
95
|
+
if path not in {"/integrity/events", "/integrity/test-events"} or body is None:
|
|
96
|
+
return
|
|
97
|
+
payload_size = len(json.dumps(body, separators=(",", ":"), default=str).encode("utf-8"))
|
|
98
|
+
if payload_size > 5 * 1024 * 1024:
|
|
99
|
+
raise ReconifyValidationError("Integrity event requests must not exceed 5 MiB")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class SyncTransport:
|
|
103
|
+
def __init__(
|
|
104
|
+
self,
|
|
105
|
+
*,
|
|
106
|
+
base_url: str,
|
|
107
|
+
api_key: str,
|
|
108
|
+
timeout: float | httpx.Timeout = 30.0,
|
|
109
|
+
request_id: str | None = None,
|
|
110
|
+
retry: RetryConfig | None = None,
|
|
111
|
+
client: httpx.Client | None = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
self.base_url = base_url.rstrip("/")
|
|
114
|
+
self.api_key = api_key
|
|
115
|
+
self.request_id = request_id
|
|
116
|
+
self.retry = retry or RetryConfig()
|
|
117
|
+
self._client = client or httpx.Client(timeout=timeout)
|
|
118
|
+
self._owns_client = client is None
|
|
119
|
+
|
|
120
|
+
def request(
|
|
121
|
+
self,
|
|
122
|
+
method: str,
|
|
123
|
+
path: str,
|
|
124
|
+
*,
|
|
125
|
+
query: dict[str, Any] | None = None,
|
|
126
|
+
body: Any = None,
|
|
127
|
+
headers: dict[str, str] | None = None,
|
|
128
|
+
model: type[T] | None = None,
|
|
129
|
+
raw: bool = False,
|
|
130
|
+
timeout: float | httpx.Timeout | None = None,
|
|
131
|
+
) -> T | RawResponse[T] | None:
|
|
132
|
+
request_headers = {"Authorization": f"Bearer {self.api_key}"}
|
|
133
|
+
if body is not None:
|
|
134
|
+
request_headers["Content-Type"] = "application/json"
|
|
135
|
+
if self.request_id:
|
|
136
|
+
request_headers["X-Request-ID"] = self.request_id
|
|
137
|
+
request_headers.update(headers or {})
|
|
138
|
+
data = _validate_request_body(body)
|
|
139
|
+
_validate_integrity_payload(path, data)
|
|
140
|
+
params = {key: value for key, value in (query or {}).items() if value is not None}
|
|
141
|
+
attempts = 0
|
|
142
|
+
while True:
|
|
143
|
+
try:
|
|
144
|
+
response = self._client.request(
|
|
145
|
+
method,
|
|
146
|
+
f"{self.base_url}{path}",
|
|
147
|
+
params=params,
|
|
148
|
+
json=data if data is not None else None,
|
|
149
|
+
headers=request_headers,
|
|
150
|
+
timeout=timeout,
|
|
151
|
+
)
|
|
152
|
+
except httpx.TransportError:
|
|
153
|
+
if not _can_retry_exception(method, attempts, self.retry):
|
|
154
|
+
raise
|
|
155
|
+
time.sleep(_sleep_for(attempts, self.retry, None))
|
|
156
|
+
attempts += 1
|
|
157
|
+
continue
|
|
158
|
+
if response.status_code not in {429, 503} or not self._can_retry(method, attempts):
|
|
159
|
+
return self._finish(response, model=model, raw=raw)
|
|
160
|
+
delay = _sleep_for(attempts, self.retry, response.headers.get("Retry-After"))
|
|
161
|
+
time.sleep(delay)
|
|
162
|
+
attempts += 1
|
|
163
|
+
|
|
164
|
+
def _can_retry(self, method: str, attempts: int) -> bool:
|
|
165
|
+
if attempts >= self.retry.max_retries:
|
|
166
|
+
return False
|
|
167
|
+
return method.upper() in {"GET", "HEAD", "OPTIONS"} or self.retry.retry_unsafe_methods
|
|
168
|
+
|
|
169
|
+
def _finish(
|
|
170
|
+
self, response: httpx.Response, *, model: type[T] | None, raw: bool
|
|
171
|
+
) -> T | RawResponse[T] | None:
|
|
172
|
+
request_id = response.headers.get("X-Request-ID")
|
|
173
|
+
if response.is_error:
|
|
174
|
+
raise error_for_response(
|
|
175
|
+
response.status_code,
|
|
176
|
+
dict(response.headers),
|
|
177
|
+
_payload(response),
|
|
178
|
+
request_id=request_id,
|
|
179
|
+
)
|
|
180
|
+
if response.status_code == 204:
|
|
181
|
+
return None
|
|
182
|
+
if raw:
|
|
183
|
+
return RawResponse(response.status_code, response.headers, response.content, request_id)
|
|
184
|
+
payload = _payload(response)
|
|
185
|
+
if model is None or payload is None:
|
|
186
|
+
return cast(T | RawResponse[T] | None, payload)
|
|
187
|
+
try:
|
|
188
|
+
return model.model_validate(payload)
|
|
189
|
+
except ValidationError as exc:
|
|
190
|
+
raise ReconifyError(
|
|
191
|
+
"The Reconify response did not match the expected schema",
|
|
192
|
+
status_code=response.status_code,
|
|
193
|
+
request_id=request_id,
|
|
194
|
+
response_headers=dict(response.headers),
|
|
195
|
+
) from exc
|
|
196
|
+
|
|
197
|
+
def close(self) -> None:
|
|
198
|
+
if self._owns_client:
|
|
199
|
+
self._client.close()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class AsyncTransport:
|
|
203
|
+
def __init__(
|
|
204
|
+
self,
|
|
205
|
+
*,
|
|
206
|
+
base_url: str,
|
|
207
|
+
api_key: str,
|
|
208
|
+
timeout: float | httpx.Timeout = 30.0,
|
|
209
|
+
request_id: str | None = None,
|
|
210
|
+
retry: RetryConfig | None = None,
|
|
211
|
+
client: httpx.AsyncClient | None = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
self.base_url = base_url.rstrip("/")
|
|
214
|
+
self.api_key = api_key
|
|
215
|
+
self.request_id = request_id
|
|
216
|
+
self.retry = retry or RetryConfig()
|
|
217
|
+
self._client = client or httpx.AsyncClient(timeout=timeout)
|
|
218
|
+
self._owns_client = client is None
|
|
219
|
+
|
|
220
|
+
async def request(
|
|
221
|
+
self,
|
|
222
|
+
method: str,
|
|
223
|
+
path: str,
|
|
224
|
+
*,
|
|
225
|
+
query: dict[str, Any] | None = None,
|
|
226
|
+
body: Any = None,
|
|
227
|
+
headers: dict[str, str] | None = None,
|
|
228
|
+
model: type[T] | None = None,
|
|
229
|
+
raw: bool = False,
|
|
230
|
+
timeout: float | httpx.Timeout | None = None,
|
|
231
|
+
) -> T | RawResponse[T] | None:
|
|
232
|
+
request_headers = {"Authorization": f"Bearer {self.api_key}"}
|
|
233
|
+
if body is not None:
|
|
234
|
+
request_headers["Content-Type"] = "application/json"
|
|
235
|
+
if self.request_id:
|
|
236
|
+
request_headers["X-Request-ID"] = self.request_id
|
|
237
|
+
request_headers.update(headers or {})
|
|
238
|
+
data = _validate_request_body(body)
|
|
239
|
+
_validate_integrity_payload(path, data)
|
|
240
|
+
params = {key: value for key, value in (query or {}).items() if value is not None}
|
|
241
|
+
attempts = 0
|
|
242
|
+
while True:
|
|
243
|
+
try:
|
|
244
|
+
response = await self._client.request(
|
|
245
|
+
method,
|
|
246
|
+
f"{self.base_url}{path}",
|
|
247
|
+
params=params,
|
|
248
|
+
json=data if data is not None else None,
|
|
249
|
+
headers=request_headers,
|
|
250
|
+
timeout=timeout,
|
|
251
|
+
)
|
|
252
|
+
except httpx.TransportError:
|
|
253
|
+
if not _can_retry_exception(method, attempts, self.retry):
|
|
254
|
+
raise
|
|
255
|
+
await asyncio.sleep(_sleep_for(attempts, self.retry, None))
|
|
256
|
+
attempts += 1
|
|
257
|
+
continue
|
|
258
|
+
if response.status_code not in {429, 503} or not self._can_retry(method, attempts):
|
|
259
|
+
return self._finish(response, model=model, raw=raw)
|
|
260
|
+
delay = _sleep_for(attempts, self.retry, response.headers.get("Retry-After"))
|
|
261
|
+
await asyncio.sleep(delay)
|
|
262
|
+
attempts += 1
|
|
263
|
+
|
|
264
|
+
def _can_retry(self, method: str, attempts: int) -> bool:
|
|
265
|
+
if attempts >= self.retry.max_retries:
|
|
266
|
+
return False
|
|
267
|
+
return method.upper() in {"GET", "HEAD", "OPTIONS"} or self.retry.retry_unsafe_methods
|
|
268
|
+
|
|
269
|
+
def _finish(
|
|
270
|
+
self, response: httpx.Response, *, model: type[T] | None, raw: bool
|
|
271
|
+
) -> T | RawResponse[T] | None:
|
|
272
|
+
request_id = response.headers.get("X-Request-ID")
|
|
273
|
+
if response.is_error:
|
|
274
|
+
raise error_for_response(
|
|
275
|
+
response.status_code,
|
|
276
|
+
dict(response.headers),
|
|
277
|
+
_payload(response),
|
|
278
|
+
request_id=request_id,
|
|
279
|
+
)
|
|
280
|
+
if response.status_code == 204:
|
|
281
|
+
return None
|
|
282
|
+
if raw:
|
|
283
|
+
return RawResponse(response.status_code, response.headers, response.content, request_id)
|
|
284
|
+
payload = _payload(response)
|
|
285
|
+
if model is None or payload is None:
|
|
286
|
+
return cast(T | RawResponse[T] | None, payload)
|
|
287
|
+
try:
|
|
288
|
+
return model.model_validate(payload)
|
|
289
|
+
except ValidationError as exc:
|
|
290
|
+
raise ReconifyError(
|
|
291
|
+
"The Reconify response did not match the expected schema",
|
|
292
|
+
status_code=response.status_code,
|
|
293
|
+
request_id=request_id,
|
|
294
|
+
response_headers=dict(response.headers),
|
|
295
|
+
) from exc
|
|
296
|
+
|
|
297
|
+
async def aclose(self) -> None:
|
|
298
|
+
if self._owns_client:
|
|
299
|
+
await self._client.aclose()
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reconify-python
|
|
3
|
+
Version: 0.1.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 Reconify Public API.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install reconify-python
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quickstart
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from reconify import Reconify
|
|
33
|
+
|
|
34
|
+
with Reconify(api_key="rk_...") as client:
|
|
35
|
+
sources = client.ledger.list_ledger_sources(limit=25)
|
|
36
|
+
for source in sources.sources or []:
|
|
37
|
+
print(source.id, source.name)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The key may also be supplied through `RECONIFY_API_KEY`. The default endpoint is
|
|
41
|
+
`https://api.reconifyhq.com/v1`; pass `base_url="https://staging.example/v1"`
|
|
42
|
+
for staging or self-hosted deployments. `/v1` is added when it is absent.
|
|
43
|
+
|
|
44
|
+
## Async usage and pagination
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from reconify import AsyncReconify
|
|
48
|
+
|
|
49
|
+
async with AsyncReconify() as client:
|
|
50
|
+
async for event in client.iter_events(limit=100):
|
|
51
|
+
print(event.id)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Cursor and offset iterators preserve opaque cursors and the server's page size.
|
|
55
|
+
When an endpoint accepts both cursor and offset pagination, `after` takes
|
|
56
|
+
precedence.
|
|
57
|
+
|
|
58
|
+
Every list operation also has a natural iterator on the client, for example
|
|
59
|
+
`client.iter_reconciliations(limit=100)` or
|
|
60
|
+
`client.iter_wallet_transactions(after="cursor")`. The async equivalent is
|
|
61
|
+
an async iterator. Iterators forward query parameters using keyword arguments,
|
|
62
|
+
so they never expose transport details.
|
|
63
|
+
|
|
64
|
+
## Errors and retries
|
|
65
|
+
|
|
66
|
+
HTTP failures raise typed `ReconifyError` subclasses. Every HTTP error exposes
|
|
67
|
+
`status_code`, `detail`, `code`, validation details, response headers, and the
|
|
68
|
+
response `request_id` without including credentials or request bodies.
|
|
69
|
+
|
|
70
|
+
429, 503, and transient HTTP transport failures such as timeouts are retried
|
|
71
|
+
for safe methods with bounded exponential backoff and jitter. Mutating methods
|
|
72
|
+
are not retried unless `RetryConfig(retry_unsafe_methods=True)` is supplied.
|
|
73
|
+
Transaction ingestion retries must reuse each row's `idempotencyKey`.
|
|
74
|
+
|
|
75
|
+
The client default timeout is 30 seconds. Individual operations can override
|
|
76
|
+
it with `timeout=...`, including an `httpx.Timeout` object. Async operations
|
|
77
|
+
also support normal `asyncio` cancellation, which is the Python equivalent of
|
|
78
|
+
context cancellation in other SDKs.
|
|
79
|
+
|
|
80
|
+
Use `raw=True` on any operation to receive status, headers, request ID, and raw
|
|
81
|
+
body through `RawResponse`.
|
|
82
|
+
|
|
83
|
+
## Test-session and ingestion headers
|
|
84
|
+
|
|
85
|
+
Integrity ingestion and test-session submission accept
|
|
86
|
+
`integrity_test_session=...`, which is sent as `X-Integrity-Test-Session`.
|
|
87
|
+
Integrity batches support 1–500 events and ledger transaction batches support
|
|
88
|
+
1–5000 transactions; the SDK does not truncate caller input.
|
|
89
|
+
|
|
90
|
+
The SDK intentionally excludes reconciliation adjustment, evidence, lifecycle,
|
|
91
|
+
report-item, and signoff operations. The retained reconciliation surface is
|
|
92
|
+
integrity sources, reconciliation list/create/get, and all schedule operations.
|
|
93
|
+
|
|
94
|
+
## API reference
|
|
95
|
+
|
|
96
|
+
The public operation methods are grouped by API module. Request bodies use the
|
|
97
|
+
typed Pydantic models exported from `reconify.models`; list query parameters use
|
|
98
|
+
the OpenAPI names in snake_case. Every operation accepts `raw=True` and a
|
|
99
|
+
per-request `timeout` override.
|
|
100
|
+
|
|
101
|
+
| Module | Methods |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| Alerts | `list_alert_rules`, `put_alert_rule` |
|
|
104
|
+
| Events | `list_events`, `get_event`, `reveal_event_field` |
|
|
105
|
+
| Ingestion | `ingest_integrity_events`, `ingest_integrity_test_events` |
|
|
106
|
+
| Issues | `list_issues`, `get_issue_summary`, `get_issue`, `update_issue`, `list_issue_deliveries`, `retry_issue_delivery`, `add_issue_note`, `resolve_issue` |
|
|
107
|
+
| Ledger | `list_ledger_sources`, `create_ledger_source`, `delete_ledger_source`, `get_ledger_source`, `update_ledger_source`, `list_source_periods`, `list_transactions`, `ingest_transactions` |
|
|
108
|
+
| Reconciliations | `list_integrity_sources_for_reconciliation`, `list_reconciliation_schedules`, `create_reconciliation_schedule`, `delete_reconciliation_schedule`, `get_reconciliation_schedule`, `update_reconciliation_schedule`, `list_reconciliations`, `create_reconciliation`, `get_reconciliation` |
|
|
109
|
+
| Search | `search_integrity_resources` |
|
|
110
|
+
| Setup | `list_setup_integrations`, `get_setup_integration`, `list_setup_sources`, `create_setup_source`, `get_setup_source`, `update_setup_source`, `disable_setup_source`, `create_test_session`, `get_test_session`, `get_test_session_result`, `retry_test_session`, `submit_test_session_events` |
|
|
111
|
+
| Transactions | `list_wallet_transactions`, `get_wallet_transaction` |
|
|
112
|
+
| Wallets | `list_wallets`, `get_wallet`, `get_wallet_balance` |
|
|
113
|
+
|
|
114
|
+
## Build and deploy
|
|
115
|
+
|
|
116
|
+
Build the distributable artifacts locally or in CI:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
python -m pip install build
|
|
120
|
+
python -m build
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The resulting wheel and source archive in `dist/` are ready for publication to
|
|
124
|
+
an internal or public Python package registry. CI builds both artifacts after
|
|
125
|
+
running lint, type checking, and tests.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
reconify/__init__.py,sha256=zd4a21ozt8VkPdgPQO2PXRv_Wz2IepC-x4Q9g9FIufY,878
|
|
2
|
+
reconify/client.py,sha256=2smGF9y6v7sVO1xXp5SYbXXaGFrD3o7Q30Zkc3z1iPg,8349
|
|
3
|
+
reconify/errors.py,sha256=Dt9UVBSt34jL09AnYWRDKk0UEu70UpmDhQI_MyOzrkM,4185
|
|
4
|
+
reconify/models.py,sha256=SBzHBV8Chd5BT0eFa-IbtCk8eYZI7-5eJLCB7yiuTCM,45483
|
|
5
|
+
reconify/pagination.py,sha256=R0kJgeFYmuTujl9VEHCMEarTjp_J3aLuxEM_tRiruss,3486
|
|
6
|
+
reconify/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
reconify/transport.py,sha256=WFJ3zTjoaUd_PkV9JU3KaA-ahRc7fi0pY-iz9SezhIc,10709
|
|
8
|
+
reconify/resources/__init__.py,sha256=XN-fcvMI5TsG8G1EnVq47NkUgAOWBvOd_k3u4JkmnRo,5325
|
|
9
|
+
reconify/resources/alerts.py,sha256=hZ15beElYoGaPefFSlsU-NyswUwj92w-zjzC4QbTvvs,1605
|
|
10
|
+
reconify/resources/base.py,sha256=qHZm6LuIPJmLTKAjj221QaWuUi0aorIa8GWeWB4qtDs,3279
|
|
11
|
+
reconify/resources/events.py,sha256=tc-5Fkb9G59W9ee1r0Yd7Nv_Kn6FuMeNCoCiQ0T3qHk,2213
|
|
12
|
+
reconify/resources/ingestion.py,sha256=7uzumDHPbiY-dPe4HPIZc250LqxMFA3Izm78s5iLC8E,2652
|
|
13
|
+
reconify/resources/issues.py,sha256=ulTlQVcOtgPCthg5MLlxWpAe3u3MWvaJ2xP8CVPaznM,6113
|
|
14
|
+
reconify/resources/ledger.py,sha256=I1ReJqEo-Qin5vvT126Y36YPaaXwk-DmuOGix2PkXm0,6391
|
|
15
|
+
reconify/resources/reconciliations.py,sha256=uCZqG8tMiBz_pzgD0htdEx5aELL-F2XoTv8AyQHv98g,7458
|
|
16
|
+
reconify/resources/search.py,sha256=CtRzVyD3fsYLWUtzNd7HA0S2JkNakg905-97JlLRHe8,885
|
|
17
|
+
reconify/resources/setup.py,sha256=SxBXerZzQVB9Xkw17PLngQg8z5Z8S_zLYKNfyCFKn_k,9756
|
|
18
|
+
reconify/resources/transactions.py,sha256=4pRd7MW9DFp9U_J-mBj3x_cYcMor8FlhdVia0OaeAC4,1678
|
|
19
|
+
reconify/resources/wallets.py,sha256=R0dbzK5DpqvT54FmCkXXw0rcH3LepI22Pl94voC5e_Q,2261
|
|
20
|
+
reconify_python-0.1.0.dist-info/METADATA,sha256=nyF8H5D8zRyeXJyuXDHCMKHAaJNj0u1b8WbcDAlvS0M,5328
|
|
21
|
+
reconify_python-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
22
|
+
reconify_python-0.1.0.dist-info/licenses/LICENSE,sha256=q4Eqlx9sTPEY6DaOMuTjXAtVA7uEjxZkcvaJzcuYaIw,1065
|
|
23
|
+
reconify_python-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Reconify
|
|
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.
|