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.
Files changed (23) hide show
  1. ab_openapi_python_generator/generate_data.py +60 -60
  2. ab_openapi_python_generator/language_converters/python/{service_generator.py → client_generator.py} +83 -167
  3. ab_openapi_python_generator/language_converters/python/exception_generator.py +23 -0
  4. ab_openapi_python_generator/language_converters/python/generator.py +9 -11
  5. ab_openapi_python_generator/language_converters/python/jinja_config.py +3 -4
  6. ab_openapi_python_generator/language_converters/python/model_generator.py +33 -91
  7. ab_openapi_python_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2 +80 -0
  8. ab_openapi_python_generator/language_converters/python/templates/http_exception.jinja2 +8 -0
  9. ab_openapi_python_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2 +80 -0
  10. ab_openapi_python_generator/models.py +5 -5
  11. ab_openapi_python_generator/version_detector.py +1 -4
  12. {ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.2.0.dist-info}/METADATA +1 -1
  13. {ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.2.0.dist-info}/RECORD +16 -19
  14. ab_openapi_python_generator/language_converters/python/api_config_generator.py +0 -35
  15. ab_openapi_python_generator/language_converters/python/templates/aiohttp.jinja2 +0 -49
  16. ab_openapi_python_generator/language_converters/python/templates/apiconfig.jinja2 +0 -38
  17. ab_openapi_python_generator/language_converters/python/templates/apiconfig_pydantic_2.jinja2 +0 -42
  18. ab_openapi_python_generator/language_converters/python/templates/httpx.jinja2 +0 -126
  19. ab_openapi_python_generator/language_converters/python/templates/requests.jinja2 +0 -50
  20. ab_openapi_python_generator/language_converters/python/templates/service.jinja2 +0 -12
  21. {ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.2.0.dist-info}/WHEEL +0 -0
  22. {ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.2.0.dist-info}/entry_points.txt +0 -0
  23. {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) if schema.type is not None else "object"
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(binding.discriminator_key):
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}"
@@ -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
- services: List[Service]
101
- api_config: APIConfig
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ab-openapi-python-generator
3
- Version: 2.1.4
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
@@ -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=Av7nXE6BvsE9eHWF6eOBhPCZWWC9pi-lvksAJGB6MA8,8022
5
- ab_openapi_python_generator/models.py,sha256=FDnO9hKa3w05quxuQRe4bJDvRJte-uS1GwjPp4VSrBo,2199
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=Cn7iG6_EnBdmjGmefJQ1cIBzbT28F0BeCEpoqksBGyQ,2142
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/api_config_generator.py,sha256=SSA-jac7SpP_tpzwc37Vw0VYvSfx0KlsOFeF9Bioe0g,1029
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/generator.py,sha256=VX8glNSu-pHo7GKYCVaP3k6JZKKGluYZq7zRZL0fDVE,1699
13
- ab_openapi_python_generator/language_converters/python/jinja_config.py,sha256=1xhwJAkk2BBaEzlOI-rgPWY6xjvhjrVEUqrl3rLU1uI,1144
14
- ab_openapi_python_generator/language_converters/python/model_generator.py,sha256=jvtMwV-SKjvXUn5sv-xULsBrsQmSj9UrK4zZ4r5n4oI,35347
15
- ab_openapi_python_generator/language_converters/python/service_generator.py,sha256=uVdFyAmVPfCbWwhSW7G-9Z0qEBpqpgKr8af2MbdoQhg,20107
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/apiconfig.jinja2,sha256=bvYMJV6YB2NZLLcqwozISdS86XLH8J6D8o3ZpfgJCjc,1256
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/httpx.jinja2,sha256=zblT5_shg0vvggL0dJRz4GhGZs2bfRoeQb9YSyHLjrs,4586
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/requests.jinja2,sha256=8R3S5M26vQol-rXHuNVYuG4qc_skC9-G8x-6AZlqTXg,2043
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.1.4.dist-info/METADATA,sha256=Ej6Sl2TNph4vKnfEixyxqRy6O1jENT7JxGSX0CXfswU,5186
31
- ab_openapi_python_generator-2.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
32
- ab_openapi_python_generator-2.1.4.dist-info/entry_points.txt,sha256=zezbCs2qP8lSUbcjL6cN5XHoHxf9JTe15CZYH8Yuzo0,90
33
- ab_openapi_python_generator-2.1.4.dist-info/licenses/LICENSE,sha256=0myanGwJ2vUOZN12aN95o0My6XEysnnVlbKikYw3pHg,1070
34
- ab_openapi_python_generator-2.1.4.dist-info/RECORD,,
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 %}