vantage-python 0.3.1__tar.gz → 0.3.2__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.
- {vantage_python-0.3.1 → vantage_python-0.3.2}/PKG-INFO +1 -1
- {vantage_python-0.3.1 → vantage_python-0.3.2}/autogen.py +112 -13
- {vantage_python-0.3.1 → vantage_python-0.3.2}/pyproject.toml +1 -1
- {vantage_python-0.3.1 → vantage_python-0.3.2}/src/vantage/_async/client.py +73 -165
- {vantage_python-0.3.1 → vantage_python-0.3.2}/src/vantage/_sync/client.py +73 -165
- {vantage_python-0.3.1 → vantage_python-0.3.2}/.github/workflows/pypi-publish.yml +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/.github/workflows/test.yml +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/.gitignore +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/LICENSE +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/Makefile +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/README.md +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/src/vantage/__init__.py +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/src/vantage/_async/__init__.py +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/src/vantage/_base.py +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/src/vantage/_sync/__init__.py +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/src/vantage/_types.py +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/src/vantage/py.typed +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.2}/tests/test_e2e.py +0 -0
|
@@ -20,6 +20,17 @@ from typing import Any
|
|
|
20
20
|
OPENAPI_URL = "https://api.vantage.sh/v2/oas_v3.json"
|
|
21
21
|
OUTPUT_DIR = Path(__file__).parent / "src" / "vantage"
|
|
22
22
|
|
|
23
|
+
# Maps a substring found in a response description to the internal client method
|
|
24
|
+
# that should handle it, and the Python return type to emit.
|
|
25
|
+
# Checked against each HTTP status code's description during endpoint parsing.
|
|
26
|
+
RESPONSE_HANDLERS: list[tuple[str, str, str]] = [
|
|
27
|
+
(
|
|
28
|
+
"will be available at the location specified in the Location header",
|
|
29
|
+
"_request_for_location",
|
|
30
|
+
"str",
|
|
31
|
+
),
|
|
32
|
+
]
|
|
33
|
+
|
|
23
34
|
|
|
24
35
|
@dataclass
|
|
25
36
|
class Parameter:
|
|
@@ -48,6 +59,8 @@ class Endpoint:
|
|
|
48
59
|
request_body_type: str | None = None
|
|
49
60
|
response_type: str | None = None
|
|
50
61
|
is_multipart: bool = False
|
|
62
|
+
response_handler: str | None = None # internal client method to call, if not the default
|
|
63
|
+
response_handler_return_type: str | None = None
|
|
51
64
|
|
|
52
65
|
|
|
53
66
|
@dataclass
|
|
@@ -292,13 +305,27 @@ def parse_endpoints(schema: dict[str, Any]) -> list[Endpoint]:
|
|
|
292
305
|
|
|
293
306
|
response_type = extract_response_type(spec.get("responses", {}), schemas)
|
|
294
307
|
|
|
308
|
+
description = spec.get("description")
|
|
309
|
+
|
|
310
|
+
response_handler = None
|
|
311
|
+
response_handler_return_type = None
|
|
312
|
+
for resp_desc in spec.get("responses", {}).values():
|
|
313
|
+
text = resp_desc.get("description", "")
|
|
314
|
+
for phrase, handler, return_type in RESPONSE_HANDLERS:
|
|
315
|
+
if phrase in text:
|
|
316
|
+
response_handler = handler
|
|
317
|
+
response_handler_return_type = return_type
|
|
318
|
+
break
|
|
319
|
+
if response_handler:
|
|
320
|
+
break
|
|
321
|
+
|
|
295
322
|
endpoints.append(
|
|
296
323
|
Endpoint(
|
|
297
324
|
path=path,
|
|
298
325
|
method=method.upper(),
|
|
299
326
|
operation_id=operation_id,
|
|
300
327
|
summary=spec.get("summary"),
|
|
301
|
-
description=
|
|
328
|
+
description=description,
|
|
302
329
|
deprecated=spec.get("deprecated", False),
|
|
303
330
|
parameters=parameters,
|
|
304
331
|
request_body_required=request_body.get("required", False)
|
|
@@ -307,6 +334,8 @@ def parse_endpoints(schema: dict[str, Any]) -> list[Endpoint]:
|
|
|
307
334
|
request_body_type=body_type,
|
|
308
335
|
response_type=response_type,
|
|
309
336
|
is_multipart=is_multipart,
|
|
337
|
+
response_handler=response_handler,
|
|
338
|
+
response_handler_return_type=response_handler_return_type,
|
|
310
339
|
)
|
|
311
340
|
)
|
|
312
341
|
|
|
@@ -523,6 +552,18 @@ def generate_pydantic_models(schema: dict[str, Any]) -> str:
|
|
|
523
552
|
return "\n".join(lines)
|
|
524
553
|
|
|
525
554
|
|
|
555
|
+
def _collect_handler_routes(resources: dict[str, Resource]) -> dict[str, list[tuple[str, str]]]:
|
|
556
|
+
"""Scan all endpoints and group (method, path) pairs by their response_handler."""
|
|
557
|
+
handler_routes: dict[str, list[tuple[str, str]]] = {}
|
|
558
|
+
for resource in resources.values():
|
|
559
|
+
for endpoint in resource.endpoints:
|
|
560
|
+
if endpoint.response_handler:
|
|
561
|
+
handler_routes.setdefault(endpoint.response_handler, []).append(
|
|
562
|
+
(endpoint.method, endpoint.path)
|
|
563
|
+
)
|
|
564
|
+
return handler_routes
|
|
565
|
+
|
|
566
|
+
|
|
526
567
|
def generate_sync_client(resources: dict[str, Resource]) -> str:
|
|
527
568
|
"""Generate synchronous client code."""
|
|
528
569
|
lines = [
|
|
@@ -618,6 +659,19 @@ def generate_sync_client(resources: dict[str, Resource]) -> str:
|
|
|
618
659
|
" body=response.text,",
|
|
619
660
|
" )",
|
|
620
661
|
"",
|
|
662
|
+
]
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
# Inject generated routing: one if-block per handler, checking (method, path)
|
|
666
|
+
handler_routes = _collect_handler_routes(resources)
|
|
667
|
+
for handler, routes in sorted(handler_routes.items()):
|
|
668
|
+
route_set = "{" + ", ".join(f'("{m}", "{p}")' for m, p in sorted(routes)) + "}"
|
|
669
|
+
lines.append(f" if (method, path) in {route_set}:")
|
|
670
|
+
lines.append(f" return self.{handler}(response)")
|
|
671
|
+
lines.append("")
|
|
672
|
+
|
|
673
|
+
lines.extend(
|
|
674
|
+
[
|
|
621
675
|
" try:",
|
|
622
676
|
" data = response.json()",
|
|
623
677
|
" except Exception:",
|
|
@@ -625,6 +679,10 @@ def generate_sync_client(resources: dict[str, Resource]) -> str:
|
|
|
625
679
|
"",
|
|
626
680
|
" return data",
|
|
627
681
|
"",
|
|
682
|
+
" def _request_for_location(self, response: Any) -> str:",
|
|
683
|
+
' """Extract the Location header from a response."""',
|
|
684
|
+
' return response.headers["Location"]',
|
|
685
|
+
"",
|
|
628
686
|
"",
|
|
629
687
|
]
|
|
630
688
|
)
|
|
@@ -699,7 +757,10 @@ def generate_sync_method(endpoint: Endpoint, method_name: str) -> list[str]:
|
|
|
699
757
|
|
|
700
758
|
# Method signature
|
|
701
759
|
param_str = ", ".join(["self"] + params) if params else "self"
|
|
702
|
-
|
|
760
|
+
if endpoint.response_handler:
|
|
761
|
+
return_type = endpoint.response_handler_return_type or "Any"
|
|
762
|
+
else:
|
|
763
|
+
return_type = endpoint.response_type or "None"
|
|
703
764
|
lines.append(f" def {method_name}({param_str}) -> {return_type}:")
|
|
704
765
|
|
|
705
766
|
# Docstring
|
|
@@ -740,11 +801,20 @@ def generate_sync_method(endpoint: Endpoint, method_name: str) -> list[str]:
|
|
|
740
801
|
lines.append(" body_data = None")
|
|
741
802
|
|
|
742
803
|
# Make request and coerce response payload into typed models where possible
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
804
|
+
if endpoint.response_handler:
|
|
805
|
+
lines.append(
|
|
806
|
+
f' return self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
807
|
+
)
|
|
808
|
+
elif endpoint.response_type is None:
|
|
809
|
+
lines.append(
|
|
810
|
+
f' self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
811
|
+
)
|
|
812
|
+
else:
|
|
813
|
+
lines.append(
|
|
814
|
+
f' data = self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
815
|
+
)
|
|
816
|
+
_append_response_mapping(lines, return_type, "data")
|
|
817
|
+
lines.append(" return data")
|
|
748
818
|
|
|
749
819
|
return lines
|
|
750
820
|
|
|
@@ -844,6 +914,19 @@ def generate_async_client(resources: dict[str, Resource]) -> str:
|
|
|
844
914
|
" body=response.text,",
|
|
845
915
|
" )",
|
|
846
916
|
"",
|
|
917
|
+
]
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
# Inject generated routing: one if-block per handler, checking (method, path)
|
|
921
|
+
handler_routes = _collect_handler_routes(resources)
|
|
922
|
+
for handler, routes in sorted(handler_routes.items()):
|
|
923
|
+
route_set = "{" + ", ".join(f'("{m}", "{p}")' for m, p in sorted(routes)) + "}"
|
|
924
|
+
lines.append(f" if (method, path) in {route_set}:")
|
|
925
|
+
lines.append(f" return self.{handler}(response)")
|
|
926
|
+
lines.append("")
|
|
927
|
+
|
|
928
|
+
lines.extend(
|
|
929
|
+
[
|
|
847
930
|
" try:",
|
|
848
931
|
" data = response.json()",
|
|
849
932
|
" except Exception:",
|
|
@@ -851,6 +934,10 @@ def generate_async_client(resources: dict[str, Resource]) -> str:
|
|
|
851
934
|
"",
|
|
852
935
|
" return data",
|
|
853
936
|
"",
|
|
937
|
+
" def _request_for_location(self, response: Any) -> str:",
|
|
938
|
+
' """Extract the Location header from a response."""',
|
|
939
|
+
' return response.headers["Location"]',
|
|
940
|
+
"",
|
|
854
941
|
"",
|
|
855
942
|
]
|
|
856
943
|
)
|
|
@@ -925,7 +1012,10 @@ def generate_async_method(endpoint: Endpoint, method_name: str) -> list[str]:
|
|
|
925
1012
|
|
|
926
1013
|
# Method signature
|
|
927
1014
|
param_str = ", ".join(["self"] + params) if params else "self"
|
|
928
|
-
|
|
1015
|
+
if endpoint.response_handler:
|
|
1016
|
+
return_type = endpoint.response_handler_return_type or "Any"
|
|
1017
|
+
else:
|
|
1018
|
+
return_type = endpoint.response_type or "None"
|
|
929
1019
|
lines.append(f" async def {method_name}({param_str}) -> {return_type}:")
|
|
930
1020
|
|
|
931
1021
|
# Docstring
|
|
@@ -966,11 +1056,20 @@ def generate_async_method(endpoint: Endpoint, method_name: str) -> list[str]:
|
|
|
966
1056
|
lines.append(" body_data = None")
|
|
967
1057
|
|
|
968
1058
|
# Make request and coerce response payload into typed models where possible
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1059
|
+
if endpoint.response_handler:
|
|
1060
|
+
lines.append(
|
|
1061
|
+
f' return await self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
1062
|
+
)
|
|
1063
|
+
elif endpoint.response_type is None:
|
|
1064
|
+
lines.append(
|
|
1065
|
+
f' await self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
1066
|
+
)
|
|
1067
|
+
else:
|
|
1068
|
+
lines.append(
|
|
1069
|
+
f' data = await self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
1070
|
+
)
|
|
1071
|
+
_append_response_mapping(lines, return_type, "data")
|
|
1072
|
+
lines.append(" return data")
|
|
974
1073
|
|
|
975
1074
|
return lines
|
|
976
1075
|
|