reconify-python 1.0.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-2.0.0/.openapi-contract.json +4 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/PKG-INFO +8 -8
- {reconify_python-1.0.0 → reconify_python-2.0.0}/README.md +7 -7
- {reconify_python-1.0.0 → reconify_python-2.0.0}/UPGRADING.md +6 -6
- {reconify_python-1.0.0 → reconify_python-2.0.0}/pyproject.toml +1 -1
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/client.py +2 -2
- reconify_python-2.0.0/src/reconify/resources/__init__.py +71 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/tests/test_client.py +5 -5
- {reconify_python-1.0.0 → reconify_python-2.0.0}/tests/test_openapi_coverage.py +5 -5
- reconify_python-1.0.0/.openapi-contract.json +0 -4
- reconify_python-1.0.0/src/reconify/resources/__init__.py +0 -54
- {reconify_python-1.0.0 → reconify_python-2.0.0}/.bumpversion.cfg +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/.conductor/settings.toml +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/.github/workflows/ci.yml +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/.github/workflows/release.yml +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/.github/workflows/sync-contract.yml +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/.gitignore +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/AGENTS.md +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/CHANGELOG.md +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/LICENSE +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/context7.json +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/reports/api-error-data-report.md +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/scripts/fetch_contract.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/__init__.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/errors.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/models.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/pagination.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/py.typed +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/resources/base.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/resources/events.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/resources/ingestion.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/resources/issues.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/resources/metadata.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/resources/organization.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/src/reconify/transport.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/tests/test_pagination.py +0 -0
- {reconify_python-1.0.0 → reconify_python-2.0.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: reconify-python
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Typed Python client for the Reconify Public API
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -18,7 +18,7 @@ Description-Content-Type: text/markdown
|
|
|
18
18
|
|
|
19
19
|
# Reconify Python SDK
|
|
20
20
|
|
|
21
|
-
Typed synchronous and asynchronous clients for the public Reconify
|
|
21
|
+
Typed synchronous and asynchronous clients for the public Reconify v2 API.
|
|
22
22
|
|
|
23
23
|
## Installation and quickstart
|
|
24
24
|
|
|
@@ -36,8 +36,8 @@ with Reconify(api_key="rk_...") as client:
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
The API key may also come from `RECONIFY_API_KEY`. The default endpoint is
|
|
39
|
-
`https://api.reconifyhq.com/
|
|
40
|
-
staging or self-hosted endpoint, and URLs with or without `/
|
|
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
41
|
|
|
42
42
|
## Public resources
|
|
43
43
|
|
|
@@ -74,11 +74,11 @@ The default source is the public manifest at
|
|
|
74
74
|
set `RECONIFY_OPENAPI_SPEC` to an explicit OpenAPI JSON file. The SDK never
|
|
75
75
|
depends on another checkout or an absolute workspace path.
|
|
76
76
|
|
|
77
|
-
## Migration to
|
|
77
|
+
## Migration to 2.0.0
|
|
78
78
|
|
|
79
|
-
Version `
|
|
80
|
-
|
|
81
|
-
|
|
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
82
|
[UPGRADING.md](UPGRADING.md).
|
|
83
83
|
|
|
84
84
|
## Build and release
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Reconify Python SDK
|
|
2
2
|
|
|
3
|
-
Typed synchronous and asynchronous clients for the public Reconify
|
|
3
|
+
Typed synchronous and asynchronous clients for the public Reconify v2 API.
|
|
4
4
|
|
|
5
5
|
## Installation and quickstart
|
|
6
6
|
|
|
@@ -18,8 +18,8 @@ with Reconify(api_key="rk_...") as client:
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
The API key may also come from `RECONIFY_API_KEY`. The default endpoint is
|
|
21
|
-
`https://api.reconifyhq.com/
|
|
22
|
-
staging or self-hosted endpoint, and URLs with or without `/
|
|
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
23
|
|
|
24
24
|
## Public resources
|
|
25
25
|
|
|
@@ -56,11 +56,11 @@ The default source is the public manifest at
|
|
|
56
56
|
set `RECONIFY_OPENAPI_SPEC` to an explicit OpenAPI JSON file. The SDK never
|
|
57
57
|
depends on another checkout or an absolute workspace path.
|
|
58
58
|
|
|
59
|
-
## Migration to
|
|
59
|
+
## Migration to 2.0.0
|
|
60
60
|
|
|
61
|
-
Version `
|
|
62
|
-
|
|
63
|
-
|
|
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
64
|
[UPGRADING.md](UPGRADING.md).
|
|
65
65
|
|
|
66
66
|
## Build and release
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Upgrading to
|
|
1
|
+
# Upgrading to 2.0.0
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
|
|
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
6
|
|
|
7
7
|
Use these resources:
|
|
8
8
|
|
|
@@ -13,5 +13,5 @@ Use these resources:
|
|
|
13
13
|
- organization: organization and member reads
|
|
14
14
|
|
|
15
15
|
Python models use snake_case fields and preserve unknown enum values through
|
|
16
|
-
tolerant string enums.
|
|
17
|
-
|
|
16
|
+
tolerant string enums. Existing v1 clients can continue using `/v1`; v2
|
|
17
|
+
clients must use the v2 artifact and endpoint.
|
|
@@ -26,12 +26,12 @@ from .resources import (
|
|
|
26
26
|
)
|
|
27
27
|
from .transport import AsyncTransport, RetryConfig, SyncTransport
|
|
28
28
|
|
|
29
|
-
DEFAULT_BASE_URL = "https://api.reconifyhq.com/
|
|
29
|
+
DEFAULT_BASE_URL = "https://api.reconifyhq.com/v2"
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def _normalize_base_url(base_url: str | None) -> str:
|
|
33
33
|
value = (base_url or os.getenv("RECONIFY_API_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
34
|
-
return value if value.endswith("/
|
|
34
|
+
return value if value.endswith("/v2") else f"{value}/v2"
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def _api_key(api_key: str | None) -> str:
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Resource clients grouped by the current public API surface."""
|
|
2
|
+
|
|
3
|
+
from .events import AsyncEvents, Events
|
|
4
|
+
from .ingestion import AsyncIngestion, Ingestion
|
|
5
|
+
from .issues import AsyncIssues, Issues
|
|
6
|
+
from .metadata import AsyncMetadata, Metadata
|
|
7
|
+
from .organization import AsyncOrganization, Organization
|
|
8
|
+
|
|
9
|
+
SYNC_RESOURCE_CLASSES = {
|
|
10
|
+
"metadata": Metadata,
|
|
11
|
+
"events": Events,
|
|
12
|
+
"ingestion": Ingestion,
|
|
13
|
+
"issues": Issues,
|
|
14
|
+
"organization": Organization,
|
|
15
|
+
}
|
|
16
|
+
ASYNC_RESOURCE_CLASSES = {
|
|
17
|
+
"metadata": AsyncMetadata,
|
|
18
|
+
"events": AsyncEvents,
|
|
19
|
+
"ingestion": AsyncIngestion,
|
|
20
|
+
"issues": AsyncIssues,
|
|
21
|
+
"organization": AsyncOrganization,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
OPERATION_SPECS = {
|
|
25
|
+
"api_info_get": ("metadata", "GET", "/"),
|
|
26
|
+
"events_list": ("events", "GET", "/events"),
|
|
27
|
+
"events_ingest": ("ingestion", "POST", "/events"),
|
|
28
|
+
"events_get": ("events", "GET", "/events/{event_id}"),
|
|
29
|
+
"health_get": ("metadata", "GET", "/health"),
|
|
30
|
+
"issues_list": ("issues", "GET", "/issues"),
|
|
31
|
+
"issues_get": ("issues", "GET", "/issues/{issue_id}"),
|
|
32
|
+
"issues_assign": ("issues", "PATCH", "/issues/{issue_id}"),
|
|
33
|
+
"issues_list_events": ("events", "GET", "/issues/{issue_id}/events"),
|
|
34
|
+
"issues_list_notes": ("issues", "GET", "/issues/{issue_id}/notes"),
|
|
35
|
+
"issues_add_note": ("issues", "POST", "/issues/{issue_id}/notes"),
|
|
36
|
+
"organization_get": ("organization", "GET", "/organization"),
|
|
37
|
+
"organization_list_members": ("organization", "GET", "/organization/members"),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
OPERATION_METHODS = {
|
|
41
|
+
"api_info_get": "get_api_info",
|
|
42
|
+
"events_list": "list_events",
|
|
43
|
+
"events_ingest": "ingest_monitoring_events",
|
|
44
|
+
"events_get": "get_event",
|
|
45
|
+
"health_get": "get_health",
|
|
46
|
+
"issues_list": "list_issues",
|
|
47
|
+
"issues_get": "get_issue",
|
|
48
|
+
"issues_assign": "update_issue",
|
|
49
|
+
"issues_list_events": "list_issue_events",
|
|
50
|
+
"issues_list_notes": "list_issue_notes",
|
|
51
|
+
"issues_add_note": "add_issue_note",
|
|
52
|
+
"organization_get": "get_organization",
|
|
53
|
+
"organization_list_members": "list_organization_members",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
"AsyncEvents",
|
|
58
|
+
"AsyncIngestion",
|
|
59
|
+
"AsyncIssues",
|
|
60
|
+
"AsyncMetadata",
|
|
61
|
+
"AsyncOrganization",
|
|
62
|
+
"Events",
|
|
63
|
+
"Ingestion",
|
|
64
|
+
"Issues",
|
|
65
|
+
"Metadata",
|
|
66
|
+
"Organization",
|
|
67
|
+
"ASYNC_RESOURCE_CLASSES",
|
|
68
|
+
"OPERATION_SPECS",
|
|
69
|
+
"OPERATION_METHODS",
|
|
70
|
+
"SYNC_RESOURCE_CLASSES",
|
|
71
|
+
]
|
|
@@ -49,20 +49,20 @@ def test_base_url_and_request_contract() -> None:
|
|
|
49
49
|
|
|
50
50
|
with Reconify(
|
|
51
51
|
"rk_test",
|
|
52
|
-
base_url="http://localhost:3002/
|
|
52
|
+
base_url="http://localhost:3002/v2/",
|
|
53
53
|
request_id="caller-id",
|
|
54
54
|
http_client=httpx.Client(transport=httpx.MockTransport(handler)),
|
|
55
55
|
) as client:
|
|
56
56
|
result = client.events.get_event("event id")
|
|
57
57
|
|
|
58
58
|
assert result.id == "evt_1"
|
|
59
|
-
assert str(requests[0].url) == "http://localhost:3002/
|
|
59
|
+
assert str(requests[0].url) == "http://localhost:3002/v2/events/event%20id"
|
|
60
60
|
assert requests[0].headers["Authorization"] == "Bearer rk_test"
|
|
61
61
|
assert requests[0].headers["X-Request-ID"] == "caller-id"
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def test_base_url_and_key_can_come_from_environment(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
65
|
-
monkeypatch.setenv("RECONIFY_API_URL", "http://api.test/
|
|
65
|
+
monkeypatch.setenv("RECONIFY_API_URL", "http://api.test/v2/")
|
|
66
66
|
monkeypatch.setenv("RECONIFY_API_KEY", "rk_environment")
|
|
67
67
|
requests: list[httpx.Request] = []
|
|
68
68
|
|
|
@@ -73,7 +73,7 @@ def test_base_url_and_key_can_come_from_environment(monkeypatch: pytest.MonkeyPa
|
|
|
73
73
|
with Reconify(http_client=httpx.Client(transport=httpx.MockTransport(handler))) as client:
|
|
74
74
|
client.metadata.get_health()
|
|
75
75
|
|
|
76
|
-
assert str(requests[0].url) == "http://api.test/
|
|
76
|
+
assert str(requests[0].url) == "http://api.test/v2/health"
|
|
77
77
|
assert requests[0].headers["Authorization"] == "Bearer rk_environment"
|
|
78
78
|
|
|
79
79
|
|
|
@@ -104,7 +104,7 @@ def test_ingestion_serializes_typed_body() -> None:
|
|
|
104
104
|
client.ingestion.ingest_monitoring_events(body)
|
|
105
105
|
|
|
106
106
|
assert json.loads(requests[0].content)["events"][0]["entity_id"] == "wallet-1"
|
|
107
|
-
assert requests[0].url.path == "/
|
|
107
|
+
assert requests[0].url.path == "/v2/events"
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def test_note_idempotency_header_and_issue_assignment() -> None:
|
|
@@ -6,7 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
-
from reconify.resources import OPERATION_SPECS, SYNC_RESOURCE_CLASSES
|
|
9
|
+
from reconify.resources import OPERATION_METHODS, OPERATION_SPECS, SYNC_RESOURCE_CLASSES
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def _openapi_path() -> Path | None:
|
|
@@ -23,7 +23,7 @@ def _operations() -> list[tuple[str, str, str]]:
|
|
|
23
23
|
pytest.skip("OpenAPI source is not available; run scripts/fetch_contract.py")
|
|
24
24
|
document = json.loads(path.read_text())
|
|
25
25
|
return [
|
|
26
|
-
(operation["operationId"], method.upper(), route.removeprefix("/
|
|
26
|
+
(operation["operationId"], method.upper(), route.removeprefix("/v2") or "/")
|
|
27
27
|
for route, methods in document["paths"].items()
|
|
28
28
|
for method, operation in methods.items()
|
|
29
29
|
if method.lower() in {"get", "post", "put", "patch", "delete"}
|
|
@@ -44,9 +44,9 @@ def test_every_openapi_operation_has_a_public_method() -> None:
|
|
|
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:
|
|
47
|
-
method_name = operation_id
|
|
48
|
-
assert
|
|
49
|
-
group, registered_verb, registered_route = OPERATION_SPECS[
|
|
47
|
+
method_name = OPERATION_METHODS[operation_id]
|
|
48
|
+
assert operation_id in OPERATION_SPECS, f"Missing SDK contract for {operation_id}"
|
|
49
|
+
group, registered_verb, registered_route = OPERATION_SPECS[operation_id]
|
|
50
50
|
assert registered_route == route
|
|
51
51
|
assert registered_verb == verb
|
|
52
52
|
assert hasattr(SYNC_RESOURCE_CLASSES[group], method_name), (
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"""Resource clients grouped by the current public API surface."""
|
|
2
|
-
|
|
3
|
-
from .events import AsyncEvents, Events
|
|
4
|
-
from .ingestion import AsyncIngestion, Ingestion
|
|
5
|
-
from .issues import AsyncIssues, Issues
|
|
6
|
-
from .metadata import AsyncMetadata, Metadata
|
|
7
|
-
from .organization import AsyncOrganization, Organization
|
|
8
|
-
|
|
9
|
-
SYNC_RESOURCE_CLASSES = {
|
|
10
|
-
"metadata": Metadata,
|
|
11
|
-
"events": Events,
|
|
12
|
-
"ingestion": Ingestion,
|
|
13
|
-
"issues": Issues,
|
|
14
|
-
"organization": Organization,
|
|
15
|
-
}
|
|
16
|
-
ASYNC_RESOURCE_CLASSES = {
|
|
17
|
-
"metadata": AsyncMetadata,
|
|
18
|
-
"events": AsyncEvents,
|
|
19
|
-
"ingestion": AsyncIngestion,
|
|
20
|
-
"issues": AsyncIssues,
|
|
21
|
-
"organization": AsyncOrganization,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
OPERATION_SPECS = {
|
|
25
|
-
"get_api_info": ("metadata", "GET", "/"),
|
|
26
|
-
"list_events": ("events", "GET", "/events"),
|
|
27
|
-
"ingest_monitoring_events": ("ingestion", "POST", "/events"),
|
|
28
|
-
"get_event": ("events", "GET", "/events/{event_id}"),
|
|
29
|
-
"get_health": ("metadata", "GET", "/health"),
|
|
30
|
-
"list_issues": ("issues", "GET", "/issues"),
|
|
31
|
-
"get_issue": ("issues", "GET", "/issues/{issue_id}"),
|
|
32
|
-
"update_issue": ("issues", "PATCH", "/issues/{issue_id}"),
|
|
33
|
-
"list_issue_events": ("events", "GET", "/issues/{issue_id}/events"),
|
|
34
|
-
"list_issue_notes": ("issues", "GET", "/issues/{issue_id}/notes"),
|
|
35
|
-
"add_issue_note": ("issues", "POST", "/issues/{issue_id}/notes"),
|
|
36
|
-
"get_organization": ("organization", "GET", "/organization"),
|
|
37
|
-
"list_organization_members": ("organization", "GET", "/organization/members"),
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
__all__ = [
|
|
41
|
-
"AsyncEvents",
|
|
42
|
-
"AsyncIngestion",
|
|
43
|
-
"AsyncIssues",
|
|
44
|
-
"AsyncMetadata",
|
|
45
|
-
"AsyncOrganization",
|
|
46
|
-
"Events",
|
|
47
|
-
"Ingestion",
|
|
48
|
-
"Issues",
|
|
49
|
-
"Metadata",
|
|
50
|
-
"Organization",
|
|
51
|
-
"ASYNC_RESOURCE_CLASSES",
|
|
52
|
-
"OPERATION_SPECS",
|
|
53
|
-
"SYNC_RESOURCE_CLASSES",
|
|
54
|
-
]
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|