ab-openapi-python-generator 2.1.4__py3-none-any.whl → 2.2.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.
- ab_openapi_python_generator/generate_data.py +60 -60
- ab_openapi_python_generator/language_converters/python/{service_generator.py → client_generator.py} +83 -167
- ab_openapi_python_generator/language_converters/python/exception_generator.py +23 -0
- ab_openapi_python_generator/language_converters/python/generator.py +9 -11
- ab_openapi_python_generator/language_converters/python/jinja_config.py +3 -4
- ab_openapi_python_generator/language_converters/python/model_generator.py +33 -91
- ab_openapi_python_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2 +80 -0
- ab_openapi_python_generator/language_converters/python/templates/http_exception.jinja2 +8 -0
- ab_openapi_python_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2 +80 -0
- ab_openapi_python_generator/models.py +5 -5
- ab_openapi_python_generator/version_detector.py +1 -4
- {ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.2.0.dist-info}/METADATA +1 -1
- {ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.2.0.dist-info}/RECORD +16 -19
- ab_openapi_python_generator/language_converters/python/api_config_generator.py +0 -35
- ab_openapi_python_generator/language_converters/python/templates/aiohttp.jinja2 +0 -49
- ab_openapi_python_generator/language_converters/python/templates/apiconfig.jinja2 +0 -38
- ab_openapi_python_generator/language_converters/python/templates/apiconfig_pydantic_2.jinja2 +0 -42
- ab_openapi_python_generator/language_converters/python/templates/httpx.jinja2 +0 -126
- ab_openapi_python_generator/language_converters/python/templates/requests.jinja2 +0 -50
- ab_openapi_python_generator/language_converters/python/templates/service.jinja2 +0 -12
- {ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.2.0.dist-info}/WHEEL +0 -0
- {ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.2.0.dist-info}/entry_points.txt +0 -0
- {ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,12 +7,11 @@ from . import common
|
|
|
7
7
|
ENUM_TEMPLATE = "enum.jinja2"
|
|
8
8
|
MODELS_TEMPLATE = "models.jinja2"
|
|
9
9
|
MODELS_TEMPLATE_PYDANTIC_V2 = "models_pydantic_2.jinja2"
|
|
10
|
-
SERVICE_TEMPLATE = "service.jinja2"
|
|
11
|
-
HTTPX_TEMPLATE = "httpx.jinja2"
|
|
12
|
-
API_CONFIG_TEMPLATE = "apiconfig.jinja2"
|
|
13
|
-
API_CONFIG_TEMPLATE_PYDANTIC_V2 = "apiconfig_pydantic_2.jinja2"
|
|
14
10
|
ALIAS_UNION_TEMPLATE = "alias_union.jinja2"
|
|
15
11
|
DISCRIMINATOR_ENUM_TEMPLATE = "discriminator_enum.jinja2"
|
|
12
|
+
HTTP_EXCEPTION_TEMPLATE = "http_exception.jinja2"
|
|
13
|
+
SYNC_CLIENT_HTTPX_TEMPLATE_PYDANTIC_V2 = "sync_client_httpx_pydantic_2.jinja2"
|
|
14
|
+
ASYNC_CLIENT_HTTPX_TEMPLATE_PYDANTIC_V2 = "async_client_httpx_pydantic_2.jinja2"
|
|
16
15
|
TEMPLATE_PATH = Path(__file__).parent / "templates"
|
|
17
16
|
|
|
18
17
|
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
4
|
import re
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from typing import Dict, List, Optional, Set, Tuple, Union
|
|
6
7
|
|
|
7
8
|
import click
|
|
@@ -27,15 +28,13 @@ from openapi_pydantic.v3.v3_1 import (
|
|
|
27
28
|
from ab_openapi_python_generator.common import PydanticVersion
|
|
28
29
|
from ab_openapi_python_generator.language_converters.python import common
|
|
29
30
|
from ab_openapi_python_generator.language_converters.python.jinja_config import (
|
|
31
|
+
ALIAS_UNION_TEMPLATE,
|
|
32
|
+
DISCRIMINATOR_ENUM_TEMPLATE,
|
|
30
33
|
ENUM_TEMPLATE,
|
|
31
34
|
MODELS_TEMPLATE,
|
|
32
35
|
MODELS_TEMPLATE_PYDANTIC_V2,
|
|
33
|
-
ALIAS_UNION_TEMPLATE,
|
|
34
|
-
DISCRIMINATOR_ENUM_TEMPLATE,
|
|
35
36
|
create_jinja_env,
|
|
36
37
|
)
|
|
37
|
-
from dataclasses import dataclass
|
|
38
|
-
|
|
39
38
|
from ab_openapi_python_generator.models import Model, Property, TypeConversion
|
|
40
39
|
|
|
41
40
|
# Type aliases for compatibility
|
|
@@ -305,10 +304,7 @@ def _build_discriminator_bindings(components: Components) -> Dict[str, Discrimin
|
|
|
305
304
|
if discriminator_key is None:
|
|
306
305
|
continue
|
|
307
306
|
|
|
308
|
-
enum_name = (
|
|
309
|
-
f"{_pascal_schema_name(schema_name)}"
|
|
310
|
-
f"{_pascal_discriminator(discriminator_key)}"
|
|
311
|
-
)
|
|
307
|
+
enum_name = f"{_pascal_schema_name(schema_name)}{_pascal_discriminator(discriminator_key)}"
|
|
312
308
|
|
|
313
309
|
mapping = getattr(disc, "mapping", None) or {}
|
|
314
310
|
# invert mapping to get $ref -> value
|
|
@@ -366,11 +362,7 @@ def type_converter( # noqa: C901
|
|
|
366
362
|
return TypeConversion(
|
|
367
363
|
original_type=schema.ref,
|
|
368
364
|
converted_type=converted_type,
|
|
369
|
-
import_types=(
|
|
370
|
-
[f"from .{import_type} import {import_type}"]
|
|
371
|
-
if import_type != model_name
|
|
372
|
-
else None
|
|
373
|
-
),
|
|
365
|
+
import_types=([f"from .{import_type} import {import_type}"] if import_type != model_name else None),
|
|
374
366
|
)
|
|
375
367
|
|
|
376
368
|
if required:
|
|
@@ -383,7 +375,9 @@ def type_converter( # noqa: C901
|
|
|
383
375
|
original_type = (
|
|
384
376
|
schema.type.value
|
|
385
377
|
if hasattr(schema.type, "value") and schema.type is not None
|
|
386
|
-
else str(schema.type)
|
|
378
|
+
else str(schema.type)
|
|
379
|
+
if schema.type is not None
|
|
380
|
+
else "object"
|
|
387
381
|
)
|
|
388
382
|
import_types: Optional[List[str]] = None
|
|
389
383
|
|
|
@@ -412,22 +406,16 @@ def type_converter( # noqa: C901
|
|
|
412
406
|
)
|
|
413
407
|
)
|
|
414
408
|
|
|
415
|
-
original_type = (
|
|
416
|
-
"tuple<" + ",".join([i.original_type for i in conversions]) + ">"
|
|
417
|
-
)
|
|
409
|
+
original_type = "tuple<" + ",".join([i.original_type for i in conversions]) + ">"
|
|
418
410
|
if len(conversions) == 1:
|
|
419
411
|
converted_type = conversions[0].converted_type
|
|
420
412
|
else:
|
|
421
|
-
converted_type = (
|
|
422
|
-
"Tuple[" + ",".join([i.converted_type for i in conversions]) + "]"
|
|
423
|
-
)
|
|
413
|
+
converted_type = "Tuple[" + ",".join([i.converted_type for i in conversions]) + "]"
|
|
424
414
|
|
|
425
415
|
converted_type = pre_type + converted_type + post_type
|
|
426
416
|
# Collect first import from referenced sub-schemas only (skip empty lists)
|
|
427
417
|
import_types = [
|
|
428
|
-
i.import_types[0]
|
|
429
|
-
for i in conversions
|
|
430
|
-
if i.import_types is not None and len(i.import_types) > 0
|
|
418
|
+
i.import_types[0] for i in conversions if i.import_types is not None and len(i.import_types) > 0
|
|
431
419
|
] or None
|
|
432
420
|
|
|
433
421
|
elif schema.oneOf is not None or schema.anyOf is not None:
|
|
@@ -447,23 +435,15 @@ def type_converter( # noqa: C901
|
|
|
447
435
|
import_types=import_types,
|
|
448
436
|
)
|
|
449
437
|
)
|
|
450
|
-
original_type = (
|
|
451
|
-
"union<" + ",".join([i.original_type for i in conversions]) + ">"
|
|
452
|
-
)
|
|
438
|
+
original_type = "union<" + ",".join([i.original_type for i in conversions]) + ">"
|
|
453
439
|
|
|
454
440
|
if len(conversions) == 1:
|
|
455
441
|
converted_type = conversions[0].converted_type
|
|
456
442
|
else:
|
|
457
|
-
converted_type = (
|
|
458
|
-
"Union[" + ",".join([i.converted_type for i in conversions]) + "]"
|
|
459
|
-
)
|
|
443
|
+
converted_type = "Union[" + ",".join([i.converted_type for i in conversions]) + "]"
|
|
460
444
|
|
|
461
445
|
converted_type = pre_type + converted_type + post_type
|
|
462
|
-
import_types = list(
|
|
463
|
-
itertools.chain(
|
|
464
|
-
*[i.import_types for i in conversions if i.import_types is not None]
|
|
465
|
-
)
|
|
466
|
-
)
|
|
446
|
+
import_types = list(itertools.chain(*[i.import_types for i in conversions if i.import_types is not None]))
|
|
467
447
|
# We only want to auto convert to datetime if orjson is used throghout the code, otherwise we can not
|
|
468
448
|
# serialize it to JSON.
|
|
469
449
|
elif (schema.type == "string" or str(schema.type) == "DataType.STRING") and (
|
|
@@ -483,9 +463,7 @@ def type_converter( # noqa: C901
|
|
|
483
463
|
else:
|
|
484
464
|
converted_type = pre_type + "UUID" + post_type
|
|
485
465
|
import_types = ["from uuid import UUID"]
|
|
486
|
-
elif (
|
|
487
|
-
schema.type == "string" or str(schema.type) == "DataType.STRING"
|
|
488
|
-
) and schema.schema_format == "date-time":
|
|
466
|
+
elif (schema.type == "string" or str(schema.type) == "DataType.STRING") and schema.schema_format == "date-time":
|
|
489
467
|
converted_type = pre_type + "datetime" + post_type
|
|
490
468
|
import_types = ["from datetime import datetime"]
|
|
491
469
|
elif schema.type == "integer" or str(schema.type) == "DataType.INTEGER":
|
|
@@ -496,9 +474,7 @@ def type_converter( # noqa: C901
|
|
|
496
474
|
converted_type = pre_type + "bool" + post_type
|
|
497
475
|
elif schema.type == "array" or str(schema.type) == "DataType.ARRAY":
|
|
498
476
|
retVal = pre_type + "List["
|
|
499
|
-
if isinstance(schema.items, Reference30) or isinstance(
|
|
500
|
-
schema.items, Reference31
|
|
501
|
-
):
|
|
477
|
+
if isinstance(schema.items, Reference30) or isinstance(schema.items, Reference31):
|
|
502
478
|
converted_reference = _generate_property_from_reference(
|
|
503
479
|
model_name or "", "", schema.items, schema, required
|
|
504
480
|
)
|
|
@@ -535,10 +511,7 @@ def type_converter( # noqa: C901
|
|
|
535
511
|
and schema.schema_format.startswith("uuid")
|
|
536
512
|
and common.get_use_orjson()
|
|
537
513
|
):
|
|
538
|
-
if (
|
|
539
|
-
len(schema.schema_format) > 4
|
|
540
|
-
and schema.schema_format[4].isnumeric()
|
|
541
|
-
):
|
|
514
|
+
if len(schema.schema_format) > 4 and schema.schema_format[4].isnumeric():
|
|
542
515
|
uuid_type = schema.schema_format.upper()
|
|
543
516
|
converted_type = pre_type + uuid_type + post_type
|
|
544
517
|
import_types = ["from pydantic import " + uuid_type]
|
|
@@ -576,10 +549,7 @@ def type_converter( # noqa: C901
|
|
|
576
549
|
and schema.schema_format.startswith("uuid")
|
|
577
550
|
and common.get_use_orjson()
|
|
578
551
|
):
|
|
579
|
-
if (
|
|
580
|
-
len(schema.schema_format) > 4
|
|
581
|
-
and schema.schema_format[4].isnumeric()
|
|
582
|
-
):
|
|
552
|
+
if len(schema.schema_format) > 4 and schema.schema_format[4].isnumeric():
|
|
583
553
|
uuid_type = schema.schema_format.upper()
|
|
584
554
|
converted_type = pre_type + uuid_type + post_type
|
|
585
555
|
import_types = ["from pydantic import " + uuid_type]
|
|
@@ -630,11 +600,7 @@ def _generate_property_from_schema(
|
|
|
630
600
|
:param parent_schema: Component this belongs to
|
|
631
601
|
:return: Property
|
|
632
602
|
"""
|
|
633
|
-
required =
|
|
634
|
-
parent_schema is not None
|
|
635
|
-
and parent_schema.required is not None
|
|
636
|
-
and name in parent_schema.required
|
|
637
|
-
)
|
|
603
|
+
required = parent_schema is not None and parent_schema.required is not None and name in parent_schema.required
|
|
638
604
|
|
|
639
605
|
import_type = None
|
|
640
606
|
if required:
|
|
@@ -666,26 +632,20 @@ def _generate_property_from_reference(
|
|
|
666
632
|
:return: Property and model to be imported by the file
|
|
667
633
|
"""
|
|
668
634
|
required = (
|
|
669
|
-
parent_schema is not None
|
|
670
|
-
and parent_schema.required is not None
|
|
671
|
-
and name in parent_schema.required
|
|
635
|
+
parent_schema is not None and parent_schema.required is not None and name in parent_schema.required
|
|
672
636
|
) or force_required
|
|
673
637
|
import_model = common.normalize_symbol(reference.ref.split("/")[-1])
|
|
674
638
|
|
|
675
639
|
if import_model == model_name:
|
|
676
640
|
type_conv = TypeConversion(
|
|
677
641
|
original_type=reference.ref,
|
|
678
|
-
converted_type=(
|
|
679
|
-
import_model if required else 'Optional["' + import_model + '"]'
|
|
680
|
-
),
|
|
642
|
+
converted_type=(import_model if required else 'Optional["' + import_model + '"]'),
|
|
681
643
|
import_types=None,
|
|
682
644
|
)
|
|
683
645
|
else:
|
|
684
646
|
type_conv = TypeConversion(
|
|
685
647
|
original_type=reference.ref,
|
|
686
|
-
converted_type=(
|
|
687
|
-
import_model if required else "Optional[" + import_model + "]"
|
|
688
|
-
),
|
|
648
|
+
converted_type=(import_model if required else "Optional[" + import_model + "]"),
|
|
689
649
|
import_types=[f"from .{import_model} import {import_model}"],
|
|
690
650
|
)
|
|
691
651
|
return Property(
|
|
@@ -697,9 +657,7 @@ def _generate_property_from_reference(
|
|
|
697
657
|
)
|
|
698
658
|
|
|
699
659
|
|
|
700
|
-
def generate_models(
|
|
701
|
-
components: Components, pydantic_version: PydanticVersion = PydanticVersion.V2
|
|
702
|
-
) -> List[Model]:
|
|
660
|
+
def generate_models(components: Components, pydantic_version: PydanticVersion = PydanticVersion.V2) -> List[Model]:
|
|
703
661
|
"""
|
|
704
662
|
Receives components from an OpenAPI 3.0+ specification and generates the models from it.
|
|
705
663
|
Additionally:
|
|
@@ -735,14 +693,10 @@ def generate_models(
|
|
|
735
693
|
# --------------------------
|
|
736
694
|
if schema_or_reference.enum is not None:
|
|
737
695
|
value_dict = schema_or_reference.model_dump()
|
|
738
|
-
value_dict["enum"] = [
|
|
739
|
-
(common.normalize_symbol(str(i)).upper(), i) for i in value_dict["enum"]
|
|
740
|
-
]
|
|
696
|
+
value_dict["enum"] = [(common.normalize_symbol(str(i)).upper(), i) for i in value_dict["enum"]]
|
|
741
697
|
m = Model(
|
|
742
698
|
file_name=name,
|
|
743
|
-
content=jinja_env.get_template(ENUM_TEMPLATE).render(
|
|
744
|
-
name=name, **value_dict
|
|
745
|
-
),
|
|
699
|
+
content=jinja_env.get_template(ENUM_TEMPLATE).render(name=name, **value_dict),
|
|
746
700
|
openapi_object=schema_or_reference,
|
|
747
701
|
properties=[],
|
|
748
702
|
)
|
|
@@ -758,30 +712,24 @@ def generate_models(
|
|
|
758
712
|
# Normal models
|
|
759
713
|
# --------------------------
|
|
760
714
|
properties: List[Property] = []
|
|
761
|
-
property_iterator = (
|
|
762
|
-
schema_or_reference.properties.items()
|
|
763
|
-
if schema_or_reference.properties is not None
|
|
764
|
-
else {}
|
|
765
|
-
)
|
|
715
|
+
property_iterator = schema_or_reference.properties.items() if schema_or_reference.properties is not None else {}
|
|
766
716
|
|
|
767
717
|
for prop_name, prop_schema in property_iterator:
|
|
768
718
|
# Reference property
|
|
769
719
|
if isinstance(prop_schema, Reference30) or isinstance(prop_schema, Reference31):
|
|
770
|
-
conv_property = _generate_property_from_reference(
|
|
771
|
-
name, prop_name, prop_schema, schema_or_reference
|
|
772
|
-
)
|
|
720
|
+
conv_property = _generate_property_from_reference(name, prop_name, prop_schema, schema_or_reference)
|
|
773
721
|
properties.append(conv_property)
|
|
774
722
|
continue
|
|
775
723
|
|
|
776
724
|
# Schema property
|
|
777
|
-
conv_property = _generate_property_from_schema(
|
|
778
|
-
name, prop_name, prop_schema, schema_or_reference
|
|
779
|
-
)
|
|
725
|
+
conv_property = _generate_property_from_schema(name, prop_name, prop_schema, schema_or_reference)
|
|
780
726
|
|
|
781
727
|
# If this model is a discriminated union member, and this property
|
|
782
728
|
# is the discriminator key, make it a Literal[...] with a default
|
|
783
729
|
binding = discriminator_bindings.get(name)
|
|
784
|
-
if binding and common.normalize_symbol(conv_property.name) == common.normalize_symbol(
|
|
730
|
+
if binding and common.normalize_symbol(conv_property.name) == common.normalize_symbol(
|
|
731
|
+
binding.discriminator_key
|
|
732
|
+
):
|
|
785
733
|
conv_property.required = True
|
|
786
734
|
conv_property.default = f"{binding.enum_name}.{binding.enum_member}"
|
|
787
735
|
|
|
@@ -845,11 +793,7 @@ def generate_models(
|
|
|
845
793
|
|
|
846
794
|
properties.append(conv_property)
|
|
847
795
|
|
|
848
|
-
template_name =
|
|
849
|
-
MODELS_TEMPLATE_PYDANTIC_V2
|
|
850
|
-
if pydantic_version == PydanticVersion.V2
|
|
851
|
-
else MODELS_TEMPLATE
|
|
852
|
-
)
|
|
796
|
+
template_name = MODELS_TEMPLATE_PYDANTIC_V2 if pydantic_version == PydanticVersion.V2 else MODELS_TEMPLATE
|
|
853
797
|
|
|
854
798
|
generated_content = jinja_env.get_template(template_name).render(
|
|
855
799
|
schema_name=name, schema=schema_or_reference, properties=properties
|
|
@@ -872,9 +816,7 @@ def generate_models(
|
|
|
872
816
|
# Ensure enum modules for discriminators are included
|
|
873
817
|
enum_models: List[Model] = []
|
|
874
818
|
for enum_name, members in enum_members_by_name.items():
|
|
875
|
-
enum_content = jinja_env.get_template(DISCRIMINATOR_ENUM_TEMPLATE).render(
|
|
876
|
-
enum_name=enum_name, members=members
|
|
877
|
-
)
|
|
819
|
+
enum_content = jinja_env.get_template(DISCRIMINATOR_ENUM_TEMPLATE).render(enum_name=enum_name, members=members)
|
|
878
820
|
try:
|
|
879
821
|
compile(enum_content, "<string>", "exec")
|
|
880
822
|
except SyntaxError as e: # pragma: no cover
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{% if env_token_name is not none %}import os{% endif %}
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional, Union
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from ..models import *
|
|
9
|
+
{% set _base_path = servers[0].url if servers|length > 0 else "/" %}
|
|
10
|
+
from ..exceptions import HTTPException
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AsyncClient(BaseModel):
|
|
14
|
+
model_config = {"validate_assignment": True}
|
|
15
|
+
|
|
16
|
+
base_path: str = "{{ _base_path }}"
|
|
17
|
+
verify: Union[bool, str] = True
|
|
18
|
+
{% if env_token_name is none %}
|
|
19
|
+
access_token: Optional[str] = None
|
|
20
|
+
{% endif %}
|
|
21
|
+
|
|
22
|
+
def get_access_token(self) -> Optional[str]:
|
|
23
|
+
{% if env_token_name is not none %}
|
|
24
|
+
try:
|
|
25
|
+
return os.environ["{{ env_token_name }}"]
|
|
26
|
+
except KeyError:
|
|
27
|
+
return None
|
|
28
|
+
{% else %}
|
|
29
|
+
return self.access_token
|
|
30
|
+
{% endif %}
|
|
31
|
+
|
|
32
|
+
def set_access_token(self, value: str) -> None:
|
|
33
|
+
{% if env_token_name is not none %}
|
|
34
|
+
raise Exception(
|
|
35
|
+
"This client was generated with an environment variable for the access token. "
|
|
36
|
+
"Please set '{{ env_token_name }}'."
|
|
37
|
+
)
|
|
38
|
+
{% else %}
|
|
39
|
+
self.access_token = value
|
|
40
|
+
{% endif %}
|
|
41
|
+
|
|
42
|
+
{% for op in operations %}
|
|
43
|
+
async def {{ op.operation_id }}(self{% if op.params %}, {{ op.params }}{% endif %}) -> Any:
|
|
44
|
+
base_path = self.base_path
|
|
45
|
+
path = f"{{ op.path_name }}"
|
|
46
|
+
|
|
47
|
+
headers = {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
"Accept": "application/json",
|
|
50
|
+
"Authorization": f"Bearer { self.get_access_token() }",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
query_params: Dict[str, Any] = {
|
|
54
|
+
{% for qp in op.query_params %}
|
|
55
|
+
{{ qp }},
|
|
56
|
+
{% endfor %}
|
|
57
|
+
}
|
|
58
|
+
query_params = {k: v for (k, v) in query_params.items() if v is not None}
|
|
59
|
+
|
|
60
|
+
async with httpx.AsyncClient(base_url=base_path, verify=self.verify) as client:
|
|
61
|
+
response = await client.request(
|
|
62
|
+
"{{ op.method }}",
|
|
63
|
+
httpx.URL(path),
|
|
64
|
+
headers=headers,
|
|
65
|
+
params=query_params,
|
|
66
|
+
{% if op.body_param %}
|
|
67
|
+
json={{ op.body_param }},
|
|
68
|
+
{% endif %}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if response.status_code != {{ op.return_type.status_code }}:
|
|
72
|
+
raise HTTPException(
|
|
73
|
+
response.status_code,
|
|
74
|
+
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
body = None if {{ op.return_type.status_code }} == 204 else response.json()
|
|
78
|
+
return body
|
|
79
|
+
|
|
80
|
+
{% endfor %}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
class HTTPException(Exception):
|
|
2
|
+
def __init__(self, status_code: int, message: str):
|
|
3
|
+
self.status_code = status_code
|
|
4
|
+
self.message = message
|
|
5
|
+
super().__init__(f"{status_code} {message}")
|
|
6
|
+
|
|
7
|
+
def __str__(self) -> str:
|
|
8
|
+
return f"{self.status_code} {self.message}"
|
ab_openapi_python_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{% if env_token_name is not none %}import os{% endif %}
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional, Union
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from ..models import *
|
|
9
|
+
{% set _base_path = servers[0].url if servers|length > 0 else "/" %}
|
|
10
|
+
from ..exceptions import HTTPException
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SyncClient(BaseModel):
|
|
14
|
+
model_config = {"validate_assignment": True}
|
|
15
|
+
|
|
16
|
+
base_path: str = "{{ _base_path }}"
|
|
17
|
+
verify: Union[bool, str] = True
|
|
18
|
+
{% if env_token_name is none %}
|
|
19
|
+
access_token: Optional[str] = None
|
|
20
|
+
{% endif %}
|
|
21
|
+
|
|
22
|
+
def get_access_token(self) -> Optional[str]:
|
|
23
|
+
{% if env_token_name is not none %}
|
|
24
|
+
try:
|
|
25
|
+
return os.environ["{{ env_token_name }}"]
|
|
26
|
+
except KeyError:
|
|
27
|
+
return None
|
|
28
|
+
{% else %}
|
|
29
|
+
return self.access_token
|
|
30
|
+
{% endif %}
|
|
31
|
+
|
|
32
|
+
def set_access_token(self, value: str) -> None:
|
|
33
|
+
{% if env_token_name is not none %}
|
|
34
|
+
raise Exception(
|
|
35
|
+
"This client was generated with an environment variable for the access token. "
|
|
36
|
+
"Please set '{{ env_token_name }}'."
|
|
37
|
+
)
|
|
38
|
+
{% else %}
|
|
39
|
+
self.access_token = value
|
|
40
|
+
{% endif %}
|
|
41
|
+
|
|
42
|
+
{% for op in operations %}
|
|
43
|
+
def {{ op.operation_id }}(self{% if op.params %}, {{ op.params }}{% endif %}) -> Any:
|
|
44
|
+
base_path = self.base_path
|
|
45
|
+
path = f"{{ op.path_name }}"
|
|
46
|
+
|
|
47
|
+
headers = {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
"Accept": "application/json",
|
|
50
|
+
"Authorization": f"Bearer { self.get_access_token() }",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
query_params: Dict[str, Any] = {
|
|
54
|
+
{% for qp in op.query_params %}
|
|
55
|
+
{{ qp }},
|
|
56
|
+
{% endfor %}
|
|
57
|
+
}
|
|
58
|
+
query_params = {k: v for (k, v) in query_params.items() if v is not None}
|
|
59
|
+
|
|
60
|
+
with httpx.Client(base_url=base_path, verify=self.verify) as client:
|
|
61
|
+
response = client.request(
|
|
62
|
+
"{{ op.method }}",
|
|
63
|
+
httpx.URL(path),
|
|
64
|
+
headers=headers,
|
|
65
|
+
params=query_params,
|
|
66
|
+
{% if op.body_param %}
|
|
67
|
+
json={{ op.body_param }},
|
|
68
|
+
{% endif %}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if response.status_code != {{ op.return_type.status_code }}:
|
|
72
|
+
raise HTTPException(
|
|
73
|
+
response.status_code,
|
|
74
|
+
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
body = None if {{ op.return_type.status_code }} == 204 else response.json()
|
|
78
|
+
return body
|
|
79
|
+
|
|
80
|
+
{% endfor %}
|
|
@@ -18,7 +18,7 @@ from openapi_pydantic.v3.v3_1 import (
|
|
|
18
18
|
from openapi_pydantic.v3.v3_1 import (
|
|
19
19
|
Schema as Schema31,
|
|
20
20
|
)
|
|
21
|
-
from pydantic import BaseModel
|
|
21
|
+
from pydantic import BaseModel, Field
|
|
22
22
|
|
|
23
23
|
# Type unions for compatibility with both OpenAPI 3.0 and 3.1
|
|
24
24
|
Operation = Union[Operation30, Operation31]
|
|
@@ -76,7 +76,7 @@ class Property(BaseModel):
|
|
|
76
76
|
class Model(BaseModel):
|
|
77
77
|
file_name: str
|
|
78
78
|
content: str
|
|
79
|
-
openapi_object: Schema
|
|
79
|
+
openapi_object: Optional[Schema] = None
|
|
80
80
|
properties: List[Property] = []
|
|
81
81
|
|
|
82
82
|
|
|
@@ -96,6 +96,6 @@ class APIConfig(BaseModel):
|
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
class ConversionResult(BaseModel):
|
|
99
|
-
models: List[Model]
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
models: List[Model] = Field(default_factory=list)
|
|
100
|
+
clients: List[Model] = Field(default_factory=list)
|
|
101
|
+
exceptions: List[Model] = Field(default_factory=list)
|
|
@@ -48,10 +48,7 @@ def detect_openapi_version(spec_data: Dict[str, Any]) -> OpenAPIVersion:
|
|
|
48
48
|
elif openapi_version.startswith("3.1"):
|
|
49
49
|
return "3.1"
|
|
50
50
|
else:
|
|
51
|
-
raise ValueError(
|
|
52
|
-
f"Unsupported OpenAPI version: {openapi_version}. "
|
|
53
|
-
f"Only OpenAPI 3.0.x and 3.1.x are supported."
|
|
54
|
-
)
|
|
51
|
+
raise ValueError(f"Unsupported OpenAPI version: {openapi_version}. Only OpenAPI 3.0.x and 3.1.x are supported.")
|
|
55
52
|
|
|
56
53
|
|
|
57
54
|
def is_openapi_30(spec_data: Dict[str, Any]) -> bool:
|
{ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.2.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ab-openapi-python-generator
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Openapi Python Generator
|
|
5
5
|
Project-URL: Homepage, https://github.com/auth-broker/openapi-python-generator
|
|
6
6
|
Project-URL: Repository, https://github.com/auth-broker/openapi-python-generator
|
{ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.2.0.dist-info}/RECORD
RENAMED
|
@@ -1,34 +1,31 @@
|
|
|
1
1
|
ab_openapi_python_generator/__init__.py,sha256=ph2EznAu4nkHFruaG542m4TQ_xAvDlf36uKxtAkVP1s,460
|
|
2
2
|
ab_openapi_python_generator/__main__.py,sha256=Vz0FsBvxQtyozKkZZFK_ag8XgJcx-5obRm44ogjD0Wk,2500
|
|
3
3
|
ab_openapi_python_generator/common.py,sha256=rh6AxoWyL-jb5WbeLhiohvFUBUnevn4N7ixHPKTIiDM,1221
|
|
4
|
-
ab_openapi_python_generator/generate_data.py,sha256=
|
|
5
|
-
ab_openapi_python_generator/models.py,sha256=
|
|
4
|
+
ab_openapi_python_generator/generate_data.py,sha256=bxu0eETzJOiucX8CP2fLWer-iYZ9MbJ0JLvCaQfbDK4,7947
|
|
5
|
+
ab_openapi_python_generator/models.py,sha256=b0AyJalyZSvGyi5f5xj5ymd_s9iR09kH1UurrJDxxck,2312
|
|
6
6
|
ab_openapi_python_generator/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
ab_openapi_python_generator/version_detector.py,sha256=
|
|
7
|
+
ab_openapi_python_generator/version_detector.py,sha256=PUDTpgDMHljUzxhLhMrCzHMdS3mgYQibWDiI4-vLRB4,2104
|
|
8
8
|
ab_openapi_python_generator/language_converters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
ab_openapi_python_generator/language_converters/python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
ab_openapi_python_generator/language_converters/python/
|
|
10
|
+
ab_openapi_python_generator/language_converters/python/client_generator.py,sha256=LZj13DuaNZ_PD0-JafKnXQb4GUU427_N40YQuR_eLpU,18009
|
|
11
11
|
ab_openapi_python_generator/language_converters/python/common.py,sha256=gZHNhVC4OwGBlmr3h9Ffz3iILO1ykva9CrqhBfeYQXU,1616
|
|
12
|
-
ab_openapi_python_generator/language_converters/python/
|
|
13
|
-
ab_openapi_python_generator/language_converters/python/
|
|
14
|
-
ab_openapi_python_generator/language_converters/python/
|
|
15
|
-
ab_openapi_python_generator/language_converters/python/
|
|
16
|
-
ab_openapi_python_generator/language_converters/python/templates/aiohttp.jinja2,sha256=Ym3-KX8JH9-V75GqkfcXVgh7rnqlkOQU5DLaNd1Wma0,2135
|
|
12
|
+
ab_openapi_python_generator/language_converters/python/exception_generator.py,sha256=a4iX53W8qhQ99Tkdk2sHPCq3HvtZp5JctTbObgsRX7I,653
|
|
13
|
+
ab_openapi_python_generator/language_converters/python/generator.py,sha256=g-WTVLidcpDLqYW00VufmK0_jfRAM1ZVOSAcHyLAhOU,1664
|
|
14
|
+
ab_openapi_python_generator/language_converters/python/jinja_config.py,sha256=yis4AERylbqVgb2WhDWr5tqiJ02OhzkEIaDekyTt4OE,1181
|
|
15
|
+
ab_openapi_python_generator/language_converters/python/model_generator.py,sha256=LDO3X1tqdVOL6U9TXGbvyQCvrLoVXh-47K5PVXJO1HE,34542
|
|
17
16
|
ab_openapi_python_generator/language_converters/python/templates/alias_union.jinja2,sha256=tndnrDky4srNRyB_HnUMZFW7HtoJvEfwIFteYIu0ELc,468
|
|
18
|
-
ab_openapi_python_generator/language_converters/python/templates/
|
|
19
|
-
ab_openapi_python_generator/language_converters/python/templates/apiconfig_pydantic_2.jinja2,sha256=_MzcHYEq9aoqSUEhZ-5SLbO80zgBeJnx5d7q3Q_usSo,1319
|
|
17
|
+
ab_openapi_python_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2,sha256=UqLgyv4_Mk7LbvB9_wP4afmhWRRE-42w3mq1Sdbr8JY,2414
|
|
20
18
|
ab_openapi_python_generator/language_converters/python/templates/discriminator_enum.jinja2,sha256=GJZ_ih1eURnJRPZq5P_3aKhYB5iJHymj2fpuNSdM90Y,163
|
|
21
19
|
ab_openapi_python_generator/language_converters/python/templates/enum.jinja2,sha256=7dewyMLifUTe9xjCTE62deuncPiNkp7eGkIaY-IWsLo,263
|
|
22
|
-
ab_openapi_python_generator/language_converters/python/templates/
|
|
20
|
+
ab_openapi_python_generator/language_converters/python/templates/http_exception.jinja2,sha256=BHy48PD74aU74NGWC98iciacmGotHuWw9CaOqnCL8Kc,294
|
|
23
21
|
ab_openapi_python_generator/language_converters/python/templates/models.jinja2,sha256=s5PllbxiEENsZ5_fKSh1gKMJWhcLfBdH5744nKnAZsQ,797
|
|
24
22
|
ab_openapi_python_generator/language_converters/python/templates/models_pydantic_2.jinja2,sha256=o6-OlV5upoZOAVx6aQbsaigPMTCi4Uuec-oP1FNDJ60,904
|
|
25
|
-
ab_openapi_python_generator/language_converters/python/templates/
|
|
26
|
-
ab_openapi_python_generator/language_converters/python/templates/service.jinja2,sha256=tt0Iu_JLaAlMPU73KTM7yntQjnkdvve53ShjgAGcmqY,213
|
|
23
|
+
ab_openapi_python_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2,sha256=wGDXmQs1WTkMdHv-hE-UrHoWgY4hfA7vf7LujODeDRw,2390
|
|
27
24
|
ab_openapi_python_generator/parsers/__init__.py,sha256=UITDIV7rp3HAA1M8kQc_9cNG6e1tS2vDa4mTKua6aMY,300
|
|
28
25
|
ab_openapi_python_generator/parsers/openapi_30.py,sha256=2Hq5Vl3V4NUHKol-OO2nmt8-Fh6Lqk-hJUys8s-4UNI,1924
|
|
29
26
|
ab_openapi_python_generator/parsers/openapi_31.py,sha256=jyKYAKDRkqKUPSeP6xZuM6ZCE0JjuowAe_pbm01iqWE,1924
|
|
30
|
-
ab_openapi_python_generator-2.
|
|
31
|
-
ab_openapi_python_generator-2.
|
|
32
|
-
ab_openapi_python_generator-2.
|
|
33
|
-
ab_openapi_python_generator-2.
|
|
34
|
-
ab_openapi_python_generator-2.
|
|
27
|
+
ab_openapi_python_generator-2.2.0.dist-info/METADATA,sha256=qi60lgC1kgF1sdurWjGVach3oUXMlqmi0nic1JASRhQ,5186
|
|
28
|
+
ab_openapi_python_generator-2.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
29
|
+
ab_openapi_python_generator-2.2.0.dist-info/entry_points.txt,sha256=zezbCs2qP8lSUbcjL6cN5XHoHxf9JTe15CZYH8Yuzo0,90
|
|
30
|
+
ab_openapi_python_generator-2.2.0.dist-info/licenses/LICENSE,sha256=0myanGwJ2vUOZN12aN95o0My6XEysnnVlbKikYw3pHg,1070
|
|
31
|
+
ab_openapi_python_generator-2.2.0.dist-info/RECORD,,
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
from openapi_pydantic.v3 import OpenAPI
|
|
4
|
-
|
|
5
|
-
from ab_openapi_python_generator.common import PydanticVersion
|
|
6
|
-
from ab_openapi_python_generator.language_converters.python.jinja_config import (
|
|
7
|
-
API_CONFIG_TEMPLATE,
|
|
8
|
-
API_CONFIG_TEMPLATE_PYDANTIC_V2,
|
|
9
|
-
create_jinja_env,
|
|
10
|
-
)
|
|
11
|
-
from ab_openapi_python_generator.models import APIConfig
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def generate_api_config(
|
|
15
|
-
data: OpenAPI,
|
|
16
|
-
env_token_name: Optional[str] = None,
|
|
17
|
-
pydantic_version: PydanticVersion = PydanticVersion.V2,
|
|
18
|
-
) -> APIConfig:
|
|
19
|
-
"""
|
|
20
|
-
Generate the API model.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
template_name = (
|
|
24
|
-
API_CONFIG_TEMPLATE_PYDANTIC_V2
|
|
25
|
-
if pydantic_version == PydanticVersion.V2
|
|
26
|
-
else API_CONFIG_TEMPLATE
|
|
27
|
-
)
|
|
28
|
-
jinja_env = create_jinja_env()
|
|
29
|
-
return APIConfig(
|
|
30
|
-
file_name="api_config",
|
|
31
|
-
content=jinja_env.get_template(template_name).render(
|
|
32
|
-
env_token_name=env_token_name, **data.model_dump()
|
|
33
|
-
),
|
|
34
|
-
base_url=data.servers[0].url if len(data.servers) > 0 else "NO SERVER",
|
|
35
|
-
)
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
async def {{ operation_id }}(api_config_override : Optional[APIConfig] = None{% if params.strip() %}, *, {{ params.rstrip(', ') }}{% endif %}) -> {% if return_type.type is none or return_type.type.converted_type is none %}None{% else %}{{ return_type.type.converted_type}}{% endif %}:
|
|
2
|
-
api_config = api_config_override if api_config_override else APIConfig()
|
|
3
|
-
|
|
4
|
-
base_path = api_config.base_path
|
|
5
|
-
path = f'{{ path_name }}'
|
|
6
|
-
headers = {
|
|
7
|
-
'Content-Type': 'application/json',
|
|
8
|
-
'Accept': 'application/json',
|
|
9
|
-
'Authorization': f'Bearer { api_config.get_access_token() }',
|
|
10
|
-
{{ header_params | join(',\n') | safe }}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
query_params : Dict[str,Any] = {
|
|
14
|
-
{% if query_params|length > 0 %}
|
|
15
|
-
{{ query_params | join(',\n') | safe }}
|
|
16
|
-
{% endif %}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
query_params = {key:value for (key,value) in query_params.items() if value is not None}
|
|
20
|
-
|
|
21
|
-
async with aiohttp.ClientSession(headers=headers) as session:
|
|
22
|
-
async with session.request(
|
|
23
|
-
'{{ method }}',
|
|
24
|
-
base_path + path,
|
|
25
|
-
params=query_params,
|
|
26
|
-
{% if body_param %}
|
|
27
|
-
{% if use_orjson %}
|
|
28
|
-
data=orjson.dumps({{ body_param }})
|
|
29
|
-
{% else %}
|
|
30
|
-
json = {{ body_param }}
|
|
31
|
-
{% endif %}
|
|
32
|
-
{% endif %}
|
|
33
|
-
) as initial_response:
|
|
34
|
-
if initial_response.status != {{ return_type.status_code }}:
|
|
35
|
-
raise HTTPException(initial_response.status, f'{{ operation_id }} failed with status code: {initial_response.status}')
|
|
36
|
-
# Only parse JSON when a body is expected (avoid errors on 204 No Content)
|
|
37
|
-
body = None if {{ return_type.status_code }} == 204 else await initial_response.json()
|
|
38
|
-
|
|
39
|
-
{% if return_type.type is none or return_type.type.converted_type is none %}
|
|
40
|
-
return None
|
|
41
|
-
{% elif return_type.complex_type %}
|
|
42
|
-
{%- if return_type.list_type is none %}
|
|
43
|
-
return {{ return_type.type.converted_type }}(**body) if body is not None else {{ return_type.type.converted_type }}()
|
|
44
|
-
{%- else %}
|
|
45
|
-
return [{{ return_type.list_type }}(**item) for item in body]
|
|
46
|
-
{%- endif %}
|
|
47
|
-
{% else %}
|
|
48
|
-
return body
|
|
49
|
-
{% endif %}
|