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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vantage-python
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Python SDK for the Vantage API
5
5
  Project-URL: Homepage, https://github.com/vantage-sh/vantage-python
6
6
  Project-URL: Repository, https://github.com/vantage-sh/vantage-python
@@ -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=spec.get("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
- return_type = endpoint.response_type or "Any"
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
- lines.append(
744
- f' data = self._client.request("{endpoint.method}", path, params=params, body=body_data)'
745
- )
746
- _append_response_mapping(lines, return_type, "data")
747
- lines.append(" return data")
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
- return_type = endpoint.response_type or "Any"
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
- lines.append(
970
- f' data = await self._client.request("{endpoint.method}", path, params=params, body=body_data)'
971
- )
972
- _append_response_mapping(lines, return_type, "data")
973
- lines.append(" return data")
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vantage-python"
7
- version = "0.3.1"
7
+ version = "0.3.2"
8
8
  description = "Python SDK for the Vantage API"
9
9
  readme = "README.md"
10
10
  license = "MIT"