vantage-python 0.3.1__tar.gz → 0.3.3__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.3}/PKG-INFO +1 -1
- {vantage_python-0.3.1 → vantage_python-0.3.3}/autogen.py +259 -26
- {vantage_python-0.3.1 → vantage_python-0.3.3}/pyproject.toml +1 -1
- {vantage_python-0.3.1 → vantage_python-0.3.3}/src/vantage/_async/client.py +85 -159
- {vantage_python-0.3.1 → vantage_python-0.3.3}/src/vantage/_sync/client.py +85 -159
- {vantage_python-0.3.1 → vantage_python-0.3.3}/src/vantage/_types.py +18 -1
- {vantage_python-0.3.1 → vantage_python-0.3.3}/.github/workflows/pypi-publish.yml +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.3}/.github/workflows/test.yml +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.3}/.gitignore +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.3}/LICENSE +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.3}/Makefile +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.3}/README.md +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.3}/src/vantage/__init__.py +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.3}/src/vantage/_async/__init__.py +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.3}/src/vantage/_base.py +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.3}/src/vantage/_sync/__init__.py +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.3}/src/vantage/py.typed +0 -0
- {vantage_python-0.3.1 → vantage_python-0.3.3}/tests/test_e2e.py +0 -0
|
@@ -20,6 +20,23 @@ 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
|
+
|
|
34
|
+
# Endpoints that return bool based on HTTP status: 404 -> False, 2xx -> True, else raise.
|
|
35
|
+
# Each entry is (METHOD, openapi_path_template).
|
|
36
|
+
BOOLEAN_STATUS_ROUTES: list[tuple[str, str]] = [
|
|
37
|
+
("GET", "/virtual_tag_configs/async/{request_id}"),
|
|
38
|
+
]
|
|
39
|
+
|
|
23
40
|
|
|
24
41
|
@dataclass
|
|
25
42
|
class Parameter:
|
|
@@ -48,6 +65,9 @@ class Endpoint:
|
|
|
48
65
|
request_body_type: str | None = None
|
|
49
66
|
response_type: str | None = None
|
|
50
67
|
is_multipart: bool = False
|
|
68
|
+
response_handler: str | None = None # internal client method to call, if not the default
|
|
69
|
+
response_handler_return_type: str | None = None
|
|
70
|
+
boolean_status: bool = False # 404->False, 2xx->True, else raise VantageAPIError
|
|
51
71
|
|
|
52
72
|
|
|
53
73
|
@dataclass
|
|
@@ -183,10 +203,16 @@ def preprocess_inline_models(schemas: dict[str, Any]) -> None:
|
|
|
183
203
|
existing_names.add(model_name)
|
|
184
204
|
|
|
185
205
|
|
|
186
|
-
def openapi_type_to_python(
|
|
206
|
+
def openapi_type_to_python(
|
|
207
|
+
schema: dict[str, Any],
|
|
208
|
+
schemas: dict[str, Any],
|
|
209
|
+
name_map: dict[str, str] | None = None,
|
|
210
|
+
) -> str:
|
|
187
211
|
"""Convert OpenAPI type to Python type hint."""
|
|
188
212
|
if "$ref" in schema:
|
|
189
213
|
ref_name = schema["$ref"].split("/")[-1]
|
|
214
|
+
if name_map and ref_name in name_map:
|
|
215
|
+
return name_map[ref_name]
|
|
190
216
|
return to_pascal_case(ref_name)
|
|
191
217
|
|
|
192
218
|
schema_type = schema.get("type", "any")
|
|
@@ -203,12 +229,12 @@ def openapi_type_to_python(schema: dict[str, Any], schemas: dict[str, Any]) -> s
|
|
|
203
229
|
return "bool"
|
|
204
230
|
elif schema_type == "array":
|
|
205
231
|
items = schema.get("items", {})
|
|
206
|
-
item_type = openapi_type_to_python(items, schemas)
|
|
232
|
+
item_type = openapi_type_to_python(items, schemas, name_map)
|
|
207
233
|
return f"List[{item_type}]"
|
|
208
234
|
elif schema_type == "object":
|
|
209
235
|
additional = schema.get("additionalProperties")
|
|
210
236
|
if additional:
|
|
211
|
-
value_type = openapi_type_to_python(additional, schemas)
|
|
237
|
+
value_type = openapi_type_to_python(additional, schemas, name_map)
|
|
212
238
|
return f"Dict[str, {value_type}]"
|
|
213
239
|
# Check if inline properties match an existing named schema
|
|
214
240
|
inline_props = schema.get("properties")
|
|
@@ -217,6 +243,8 @@ def openapi_type_to_python(schema: dict[str, Any], schemas: dict[str, Any]) -> s
|
|
|
217
243
|
for schema_name, schema_def in schemas.items():
|
|
218
244
|
defined_keys = sorted(schema_def.get("properties", {}).keys())
|
|
219
245
|
if defined_keys and inline_keys == defined_keys:
|
|
246
|
+
if name_map and schema_name in name_map:
|
|
247
|
+
return name_map[schema_name]
|
|
220
248
|
return to_pascal_case(schema_name)
|
|
221
249
|
return "Dict[str, Any]"
|
|
222
250
|
else:
|
|
@@ -224,7 +252,9 @@ def openapi_type_to_python(schema: dict[str, Any], schemas: dict[str, Any]) -> s
|
|
|
224
252
|
|
|
225
253
|
|
|
226
254
|
def extract_request_body_type(
|
|
227
|
-
request_body: dict[str, Any] | None,
|
|
255
|
+
request_body: dict[str, Any] | None,
|
|
256
|
+
schemas: dict[str, Any],
|
|
257
|
+
name_map: dict[str, str] | None = None,
|
|
228
258
|
) -> tuple[str | None, bool]:
|
|
229
259
|
"""Extract request body type and whether it's multipart."""
|
|
230
260
|
if not request_body:
|
|
@@ -239,13 +269,15 @@ def extract_request_body_type(
|
|
|
239
269
|
# Check for JSON
|
|
240
270
|
if "application/json" in content:
|
|
241
271
|
schema = content["application/json"].get("schema", {})
|
|
242
|
-
return openapi_type_to_python(schema, schemas), False
|
|
272
|
+
return openapi_type_to_python(schema, schemas, name_map), False
|
|
243
273
|
|
|
244
274
|
return None, False
|
|
245
275
|
|
|
246
276
|
|
|
247
277
|
def extract_response_type(
|
|
248
|
-
responses: dict[str, Any],
|
|
278
|
+
responses: dict[str, Any],
|
|
279
|
+
schemas: dict[str, Any],
|
|
280
|
+
name_map: dict[str, str] | None = None,
|
|
249
281
|
) -> str | None:
|
|
250
282
|
"""Extract successful response type."""
|
|
251
283
|
for code in ["200", "201", "202", "203"]:
|
|
@@ -255,15 +287,59 @@ def extract_response_type(
|
|
|
255
287
|
content = response.get("content", {})
|
|
256
288
|
if "application/json" in content:
|
|
257
289
|
schema = content["application/json"].get("schema", {})
|
|
258
|
-
return openapi_type_to_python(schema, schemas)
|
|
290
|
+
return openapi_type_to_python(schema, schemas, name_map)
|
|
259
291
|
return None
|
|
260
292
|
|
|
261
293
|
|
|
294
|
+
def find_request_body_schemas(schema: dict[str, Any]) -> set[str]:
|
|
295
|
+
"""Return the set of schema names referenced as request bodies in any endpoint."""
|
|
296
|
+
result = set()
|
|
297
|
+
paths = schema.get("paths", {})
|
|
298
|
+
for path_item in paths.values():
|
|
299
|
+
for method, spec in path_item.items():
|
|
300
|
+
if method in ("parameters", "servers", "summary", "description"):
|
|
301
|
+
continue
|
|
302
|
+
request_body = spec.get("requestBody", {})
|
|
303
|
+
content = request_body.get("content", {})
|
|
304
|
+
for media_type in content.values():
|
|
305
|
+
ref_schema = media_type.get("schema", {})
|
|
306
|
+
if "$ref" in ref_schema:
|
|
307
|
+
name = ref_schema["$ref"].split("/")[-1]
|
|
308
|
+
result.add(name)
|
|
309
|
+
return result
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def build_class_name_map(schemas: dict[str, Any], request_body_schemas: set[str]) -> dict[str, str]:
|
|
313
|
+
"""Build a mapping from raw schema names to Python class names, resolving conflicts.
|
|
314
|
+
|
|
315
|
+
If two schema names map to the same PascalCase name, the one used as a
|
|
316
|
+
request body is suffixed with 'Request'.
|
|
317
|
+
"""
|
|
318
|
+
initial = {name: to_pascal_case(name) for name in schemas}
|
|
319
|
+
|
|
320
|
+
by_class_name: dict[str, list[str]] = {}
|
|
321
|
+
for raw_name, class_name in initial.items():
|
|
322
|
+
by_class_name.setdefault(class_name, []).append(raw_name)
|
|
323
|
+
|
|
324
|
+
result: dict[str, str] = {}
|
|
325
|
+
for class_name, raw_names in by_class_name.items():
|
|
326
|
+
if len(raw_names) == 1:
|
|
327
|
+
result[raw_names[0]] = class_name
|
|
328
|
+
else:
|
|
329
|
+
for raw_name in raw_names:
|
|
330
|
+
if raw_name in request_body_schemas:
|
|
331
|
+
result[raw_name] = class_name + "Request"
|
|
332
|
+
else:
|
|
333
|
+
result[raw_name] = class_name
|
|
334
|
+
return result
|
|
335
|
+
|
|
336
|
+
|
|
262
337
|
def parse_endpoints(schema: dict[str, Any]) -> list[Endpoint]:
|
|
263
338
|
"""Parse all endpoints from OpenAPI schema."""
|
|
264
339
|
endpoints = []
|
|
265
340
|
paths = schema.get("paths", {})
|
|
266
341
|
schemas = schema.get("components", {}).get("schemas", {})
|
|
342
|
+
name_map = build_class_name_map(schemas, find_request_body_schemas(schema))
|
|
267
343
|
|
|
268
344
|
for path, methods in paths.items():
|
|
269
345
|
for method, spec in methods.items():
|
|
@@ -275,7 +351,7 @@ def parse_endpoints(schema: dict[str, Any]) -> list[Endpoint]:
|
|
|
275
351
|
parameters = []
|
|
276
352
|
for param in spec.get("parameters", []):
|
|
277
353
|
param_schema = param.get("schema", {})
|
|
278
|
-
param_type = openapi_type_to_python(param_schema, schemas)
|
|
354
|
+
param_type = openapi_type_to_python(param_schema, schemas, name_map)
|
|
279
355
|
parameters.append(
|
|
280
356
|
Parameter(
|
|
281
357
|
name=param["name"],
|
|
@@ -288,9 +364,27 @@ def parse_endpoints(schema: dict[str, Any]) -> list[Endpoint]:
|
|
|
288
364
|
)
|
|
289
365
|
|
|
290
366
|
request_body = spec.get("requestBody")
|
|
291
|
-
body_type, is_multipart = extract_request_body_type(request_body, schemas)
|
|
292
|
-
|
|
293
|
-
response_type = extract_response_type(spec.get("responses", {}), schemas)
|
|
367
|
+
body_type, is_multipart = extract_request_body_type(request_body, schemas, name_map)
|
|
368
|
+
|
|
369
|
+
response_type = extract_response_type(spec.get("responses", {}), schemas, name_map)
|
|
370
|
+
|
|
371
|
+
description = spec.get("description")
|
|
372
|
+
|
|
373
|
+
response_handler = None
|
|
374
|
+
response_handler_return_type = None
|
|
375
|
+
for resp_desc in spec.get("responses", {}).values():
|
|
376
|
+
text = resp_desc.get("description", "")
|
|
377
|
+
for phrase, handler, return_type in RESPONSE_HANDLERS:
|
|
378
|
+
if phrase in text:
|
|
379
|
+
response_handler = handler
|
|
380
|
+
response_handler_return_type = return_type
|
|
381
|
+
break
|
|
382
|
+
if response_handler:
|
|
383
|
+
break
|
|
384
|
+
|
|
385
|
+
boolean_status = (method.upper(), path) in {
|
|
386
|
+
(m.upper(), p) for m, p in BOOLEAN_STATUS_ROUTES
|
|
387
|
+
}
|
|
294
388
|
|
|
295
389
|
endpoints.append(
|
|
296
390
|
Endpoint(
|
|
@@ -298,7 +392,7 @@ def parse_endpoints(schema: dict[str, Any]) -> list[Endpoint]:
|
|
|
298
392
|
method=method.upper(),
|
|
299
393
|
operation_id=operation_id,
|
|
300
394
|
summary=spec.get("summary"),
|
|
301
|
-
description=
|
|
395
|
+
description=description,
|
|
302
396
|
deprecated=spec.get("deprecated", False),
|
|
303
397
|
parameters=parameters,
|
|
304
398
|
request_body_required=request_body.get("required", False)
|
|
@@ -307,6 +401,9 @@ def parse_endpoints(schema: dict[str, Any]) -> list[Endpoint]:
|
|
|
307
401
|
request_body_type=body_type,
|
|
308
402
|
response_type=response_type,
|
|
309
403
|
is_multipart=is_multipart,
|
|
404
|
+
response_handler=response_handler,
|
|
405
|
+
response_handler_return_type=response_handler_return_type,
|
|
406
|
+
boolean_status=boolean_status,
|
|
310
407
|
)
|
|
311
408
|
)
|
|
312
409
|
|
|
@@ -440,6 +537,7 @@ def _append_response_mapping(lines: list[str], return_type: str, data_var: str)
|
|
|
440
537
|
def generate_pydantic_models(schema: dict[str, Any]) -> str:
|
|
441
538
|
"""Generate Pydantic models from OpenAPI schemas."""
|
|
442
539
|
schemas = schema.get("components", {}).get("schemas", {})
|
|
540
|
+
name_map = build_class_name_map(schemas, find_request_body_schemas(schema))
|
|
443
541
|
lines = [
|
|
444
542
|
'"""Auto-generated Pydantic models from OpenAPI schema."""',
|
|
445
543
|
"",
|
|
@@ -453,7 +551,7 @@ def generate_pydantic_models(schema: dict[str, Any]) -> str:
|
|
|
453
551
|
]
|
|
454
552
|
|
|
455
553
|
for name, spec in schemas.items():
|
|
456
|
-
class_name = to_pascal_case(name)
|
|
554
|
+
class_name = name_map.get(name, to_pascal_case(name))
|
|
457
555
|
description = spec.get("description", "")
|
|
458
556
|
|
|
459
557
|
lines.append(f"class {class_name}(BaseModel):")
|
|
@@ -479,7 +577,7 @@ def generate_pydantic_models(schema: dict[str, Any]) -> str:
|
|
|
479
577
|
python_name = python_name + "_"
|
|
480
578
|
needs_alias = True
|
|
481
579
|
|
|
482
|
-
prop_type = openapi_type_to_python(prop_spec, schemas)
|
|
580
|
+
prop_type = openapi_type_to_python(prop_spec, schemas, name_map)
|
|
483
581
|
|
|
484
582
|
# Handle nullable
|
|
485
583
|
if prop_spec.get("x-nullable") or prop_spec.get("nullable"):
|
|
@@ -523,6 +621,33 @@ def generate_pydantic_models(schema: dict[str, Any]) -> str:
|
|
|
523
621
|
return "\n".join(lines)
|
|
524
622
|
|
|
525
623
|
|
|
624
|
+
def _collect_handler_routes(resources: dict[str, Resource]) -> dict[str, list[tuple[str, str]]]:
|
|
625
|
+
"""Scan all endpoints and group (method, path) pairs by their response_handler."""
|
|
626
|
+
handler_routes: dict[str, list[tuple[str, str]]] = {}
|
|
627
|
+
for resource in resources.values():
|
|
628
|
+
for endpoint in resource.endpoints:
|
|
629
|
+
if endpoint.response_handler:
|
|
630
|
+
handler_routes.setdefault(endpoint.response_handler, []).append(
|
|
631
|
+
(endpoint.method, endpoint.path)
|
|
632
|
+
)
|
|
633
|
+
return handler_routes
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def _collect_boolean_status_prefixes(resources: dict[str, Resource]) -> list[tuple[str, str]]:
|
|
637
|
+
"""Collect (method, path_prefix) pairs for boolean-status endpoints.
|
|
638
|
+
|
|
639
|
+
The prefix is derived by taking everything before the first path parameter
|
|
640
|
+
so it can be matched with str.startswith() at runtime.
|
|
641
|
+
"""
|
|
642
|
+
result = []
|
|
643
|
+
for resource in resources.values():
|
|
644
|
+
for endpoint in resource.endpoints:
|
|
645
|
+
if endpoint.boolean_status:
|
|
646
|
+
prefix = endpoint.path.split("{")[0]
|
|
647
|
+
result.append((endpoint.method, prefix))
|
|
648
|
+
return result
|
|
649
|
+
|
|
650
|
+
|
|
526
651
|
def generate_sync_client(resources: dict[str, Resource]) -> str:
|
|
527
652
|
"""Generate synchronous client code."""
|
|
528
653
|
lines = [
|
|
@@ -611,6 +736,29 @@ def generate_sync_client(resources: dict[str, Resource]) -> str:
|
|
|
611
736
|
" json=body,",
|
|
612
737
|
" )",
|
|
613
738
|
"",
|
|
739
|
+
]
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
# Inject boolean-status path checks (before the generic error check)
|
|
743
|
+
boolean_prefixes = _collect_boolean_status_prefixes(resources)
|
|
744
|
+
for method, prefix in boolean_prefixes:
|
|
745
|
+
lines.extend([
|
|
746
|
+
f' if method.upper() == "{method}" and path.startswith("{prefix}"):',
|
|
747
|
+
" if response.status_code == 404:",
|
|
748
|
+
" return False",
|
|
749
|
+
" elif response.is_success:",
|
|
750
|
+
" return True",
|
|
751
|
+
" else:",
|
|
752
|
+
" raise VantageAPIError(",
|
|
753
|
+
" status=response.status_code,",
|
|
754
|
+
" status_text=response.reason_phrase,",
|
|
755
|
+
" body=response.text,",
|
|
756
|
+
" )",
|
|
757
|
+
"",
|
|
758
|
+
])
|
|
759
|
+
|
|
760
|
+
lines.extend(
|
|
761
|
+
[
|
|
614
762
|
" if not response.is_success:",
|
|
615
763
|
" raise VantageAPIError(",
|
|
616
764
|
" status=response.status_code,",
|
|
@@ -618,6 +766,19 @@ def generate_sync_client(resources: dict[str, Resource]) -> str:
|
|
|
618
766
|
" body=response.text,",
|
|
619
767
|
" )",
|
|
620
768
|
"",
|
|
769
|
+
]
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
# Inject generated routing: one if-block per handler, checking (method, path)
|
|
773
|
+
handler_routes = _collect_handler_routes(resources)
|
|
774
|
+
for handler, routes in sorted(handler_routes.items()):
|
|
775
|
+
route_set = "{" + ", ".join(f'("{m}", "{p}")' for m, p in sorted(routes)) + "}"
|
|
776
|
+
lines.append(f" if (method, path) in {route_set}:")
|
|
777
|
+
lines.append(f" return self.{handler}(response)")
|
|
778
|
+
lines.append("")
|
|
779
|
+
|
|
780
|
+
lines.extend(
|
|
781
|
+
[
|
|
621
782
|
" try:",
|
|
622
783
|
" data = response.json()",
|
|
623
784
|
" except Exception:",
|
|
@@ -625,6 +786,10 @@ def generate_sync_client(resources: dict[str, Resource]) -> str:
|
|
|
625
786
|
"",
|
|
626
787
|
" return data",
|
|
627
788
|
"",
|
|
789
|
+
" def _request_for_location(self, response: Any) -> str:",
|
|
790
|
+
' """Extract the Location header from a response."""',
|
|
791
|
+
' return response.headers["Location"]',
|
|
792
|
+
"",
|
|
628
793
|
"",
|
|
629
794
|
]
|
|
630
795
|
)
|
|
@@ -699,7 +864,12 @@ def generate_sync_method(endpoint: Endpoint, method_name: str) -> list[str]:
|
|
|
699
864
|
|
|
700
865
|
# Method signature
|
|
701
866
|
param_str = ", ".join(["self"] + params) if params else "self"
|
|
702
|
-
|
|
867
|
+
if endpoint.boolean_status:
|
|
868
|
+
return_type = "bool"
|
|
869
|
+
elif endpoint.response_handler:
|
|
870
|
+
return_type = endpoint.response_handler_return_type or "Any"
|
|
871
|
+
else:
|
|
872
|
+
return_type = endpoint.response_type or "None"
|
|
703
873
|
lines.append(f" def {method_name}({param_str}) -> {return_type}:")
|
|
704
874
|
|
|
705
875
|
# Docstring
|
|
@@ -740,11 +910,20 @@ def generate_sync_method(endpoint: Endpoint, method_name: str) -> list[str]:
|
|
|
740
910
|
lines.append(" body_data = None")
|
|
741
911
|
|
|
742
912
|
# Make request and coerce response payload into typed models where possible
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
913
|
+
if endpoint.boolean_status or endpoint.response_handler:
|
|
914
|
+
lines.append(
|
|
915
|
+
f' return self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
916
|
+
)
|
|
917
|
+
elif endpoint.response_type is None:
|
|
918
|
+
lines.append(
|
|
919
|
+
f' self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
920
|
+
)
|
|
921
|
+
else:
|
|
922
|
+
lines.append(
|
|
923
|
+
f' data = self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
924
|
+
)
|
|
925
|
+
_append_response_mapping(lines, return_type, "data")
|
|
926
|
+
lines.append(" return data")
|
|
748
927
|
|
|
749
928
|
return lines
|
|
750
929
|
|
|
@@ -837,6 +1016,29 @@ def generate_async_client(resources: dict[str, Resource]) -> str:
|
|
|
837
1016
|
" json=body,",
|
|
838
1017
|
" )",
|
|
839
1018
|
"",
|
|
1019
|
+
]
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
# Inject boolean-status path checks (before the generic error check)
|
|
1023
|
+
boolean_prefixes = _collect_boolean_status_prefixes(resources)
|
|
1024
|
+
for method, prefix in boolean_prefixes:
|
|
1025
|
+
lines.extend([
|
|
1026
|
+
f' if method.upper() == "{method}" and path.startswith("{prefix}"):',
|
|
1027
|
+
" if response.status_code == 404:",
|
|
1028
|
+
" return False",
|
|
1029
|
+
" elif response.is_success:",
|
|
1030
|
+
" return True",
|
|
1031
|
+
" else:",
|
|
1032
|
+
" raise VantageAPIError(",
|
|
1033
|
+
" status=response.status_code,",
|
|
1034
|
+
" status_text=response.reason_phrase,",
|
|
1035
|
+
" body=response.text,",
|
|
1036
|
+
" )",
|
|
1037
|
+
"",
|
|
1038
|
+
])
|
|
1039
|
+
|
|
1040
|
+
lines.extend(
|
|
1041
|
+
[
|
|
840
1042
|
" if not response.is_success:",
|
|
841
1043
|
" raise VantageAPIError(",
|
|
842
1044
|
" status=response.status_code,",
|
|
@@ -844,6 +1046,19 @@ def generate_async_client(resources: dict[str, Resource]) -> str:
|
|
|
844
1046
|
" body=response.text,",
|
|
845
1047
|
" )",
|
|
846
1048
|
"",
|
|
1049
|
+
]
|
|
1050
|
+
)
|
|
1051
|
+
|
|
1052
|
+
# Inject generated routing: one if-block per handler, checking (method, path)
|
|
1053
|
+
handler_routes = _collect_handler_routes(resources)
|
|
1054
|
+
for handler, routes in sorted(handler_routes.items()):
|
|
1055
|
+
route_set = "{" + ", ".join(f'("{m}", "{p}")' for m, p in sorted(routes)) + "}"
|
|
1056
|
+
lines.append(f" if (method, path) in {route_set}:")
|
|
1057
|
+
lines.append(f" return self.{handler}(response)")
|
|
1058
|
+
lines.append("")
|
|
1059
|
+
|
|
1060
|
+
lines.extend(
|
|
1061
|
+
[
|
|
847
1062
|
" try:",
|
|
848
1063
|
" data = response.json()",
|
|
849
1064
|
" except Exception:",
|
|
@@ -851,6 +1066,10 @@ def generate_async_client(resources: dict[str, Resource]) -> str:
|
|
|
851
1066
|
"",
|
|
852
1067
|
" return data",
|
|
853
1068
|
"",
|
|
1069
|
+
" def _request_for_location(self, response: Any) -> str:",
|
|
1070
|
+
' """Extract the Location header from a response."""',
|
|
1071
|
+
' return response.headers["Location"]',
|
|
1072
|
+
"",
|
|
854
1073
|
"",
|
|
855
1074
|
]
|
|
856
1075
|
)
|
|
@@ -925,7 +1144,12 @@ def generate_async_method(endpoint: Endpoint, method_name: str) -> list[str]:
|
|
|
925
1144
|
|
|
926
1145
|
# Method signature
|
|
927
1146
|
param_str = ", ".join(["self"] + params) if params else "self"
|
|
928
|
-
|
|
1147
|
+
if endpoint.boolean_status:
|
|
1148
|
+
return_type = "bool"
|
|
1149
|
+
elif endpoint.response_handler:
|
|
1150
|
+
return_type = endpoint.response_handler_return_type or "Any"
|
|
1151
|
+
else:
|
|
1152
|
+
return_type = endpoint.response_type or "None"
|
|
929
1153
|
lines.append(f" async def {method_name}({param_str}) -> {return_type}:")
|
|
930
1154
|
|
|
931
1155
|
# Docstring
|
|
@@ -966,11 +1190,20 @@ def generate_async_method(endpoint: Endpoint, method_name: str) -> list[str]:
|
|
|
966
1190
|
lines.append(" body_data = None")
|
|
967
1191
|
|
|
968
1192
|
# Make request and coerce response payload into typed models where possible
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1193
|
+
if endpoint.boolean_status or endpoint.response_handler:
|
|
1194
|
+
lines.append(
|
|
1195
|
+
f' return await self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
1196
|
+
)
|
|
1197
|
+
elif endpoint.response_type is None:
|
|
1198
|
+
lines.append(
|
|
1199
|
+
f' await self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
1200
|
+
)
|
|
1201
|
+
else:
|
|
1202
|
+
lines.append(
|
|
1203
|
+
f' data = await self._client.request("{endpoint.method}", path, params=params, body=body_data)'
|
|
1204
|
+
)
|
|
1205
|
+
_append_response_mapping(lines, return_type, "data")
|
|
1206
|
+
lines.append(" return data")
|
|
974
1207
|
|
|
975
1208
|
return lines
|
|
976
1209
|
|