robotframework-openapitools 0.4.0__py3-none-any.whl → 1.0.0b2__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 (60) hide show
  1. OpenApiDriver/__init__.py +44 -41
  2. OpenApiDriver/openapi_executors.py +40 -39
  3. OpenApiDriver/openapi_reader.py +115 -116
  4. OpenApiDriver/openapidriver.libspec +71 -61
  5. OpenApiDriver/openapidriver.py +25 -19
  6. OpenApiLibCore/__init__.py +13 -11
  7. OpenApiLibCore/annotations.py +3 -0
  8. OpenApiLibCore/data_generation/__init__.py +12 -0
  9. OpenApiLibCore/data_generation/body_data_generation.py +269 -0
  10. OpenApiLibCore/data_generation/data_generation_core.py +240 -0
  11. OpenApiLibCore/data_invalidation.py +281 -0
  12. OpenApiLibCore/dto_base.py +29 -35
  13. OpenApiLibCore/dto_utils.py +97 -85
  14. OpenApiLibCore/oas_cache.py +14 -13
  15. OpenApiLibCore/openapi_libcore.libspec +346 -193
  16. OpenApiLibCore/openapi_libcore.py +389 -1702
  17. OpenApiLibCore/parameter_utils.py +91 -0
  18. OpenApiLibCore/path_functions.py +215 -0
  19. OpenApiLibCore/path_invalidation.py +44 -0
  20. OpenApiLibCore/protocols.py +30 -0
  21. OpenApiLibCore/request_data.py +281 -0
  22. OpenApiLibCore/resource_relations.py +54 -0
  23. OpenApiLibCore/validation.py +497 -0
  24. OpenApiLibCore/value_utils.py +528 -481
  25. openapi_libgen/__init__.py +46 -0
  26. openapi_libgen/command_line.py +87 -0
  27. openapi_libgen/parsing_utils.py +26 -0
  28. openapi_libgen/spec_parser.py +221 -0
  29. openapi_libgen/templates/__init__.jinja +3 -0
  30. openapi_libgen/templates/library.jinja +30 -0
  31. robotframework_openapitools-1.0.0b2.dist-info/METADATA +237 -0
  32. robotframework_openapitools-1.0.0b2.dist-info/RECORD +37 -0
  33. {robotframework_openapitools-0.4.0.dist-info → robotframework_openapitools-1.0.0b2.dist-info}/WHEEL +1 -1
  34. robotframework_openapitools-1.0.0b2.dist-info/entry_points.txt +3 -0
  35. roboswag/__init__.py +0 -9
  36. roboswag/__main__.py +0 -3
  37. roboswag/auth.py +0 -44
  38. roboswag/cli.py +0 -80
  39. roboswag/core.py +0 -85
  40. roboswag/generate/__init__.py +0 -1
  41. roboswag/generate/generate.py +0 -121
  42. roboswag/generate/models/__init__.py +0 -0
  43. roboswag/generate/models/api.py +0 -219
  44. roboswag/generate/models/definition.py +0 -28
  45. roboswag/generate/models/endpoint.py +0 -68
  46. roboswag/generate/models/parameter.py +0 -25
  47. roboswag/generate/models/response.py +0 -8
  48. roboswag/generate/models/tag.py +0 -16
  49. roboswag/generate/models/utils.py +0 -60
  50. roboswag/generate/templates/api_init.jinja +0 -15
  51. roboswag/generate/templates/models.jinja +0 -7
  52. roboswag/generate/templates/paths.jinja +0 -68
  53. roboswag/logger.py +0 -33
  54. roboswag/validate/__init__.py +0 -6
  55. roboswag/validate/core.py +0 -3
  56. roboswag/validate/schema.py +0 -21
  57. roboswag/validate/text_response.py +0 -14
  58. robotframework_openapitools-0.4.0.dist-info/METADATA +0 -42
  59. robotframework_openapitools-0.4.0.dist-info/RECORD +0 -41
  60. {robotframework_openapitools-0.4.0.dist-info → robotframework_openapitools-1.0.0b2.dist-info}/LICENSE +0 -0
@@ -0,0 +1,37 @@
1
+ OpenApiDriver/__init__.py,sha256=YjHr-j8g9KwRriNCw_ABvCEbUEp118J70xeJ_t7qcas,1453
2
+ OpenApiDriver/openapi_executors.py,sha256=NsHhcP6CM3DsQgxqOcKhpAAOM0zjhMmCsrEygIxJleY,12534
3
+ OpenApiDriver/openapi_reader.py,sha256=BPD_T8PUjfB-NBajogErI2ATmzlUGFJT8Nx5WpM5N5Y,4478
4
+ OpenApiDriver/openapidriver.libspec,sha256=ObsSKxKimuui6KcJ6PifLJtfOitHbg7bl_TGW-cPpB8,28220
5
+ OpenApiDriver/openapidriver.py,sha256=vypR00YZS3ygGfFxdUedLdCm_f_bWDLMpD1vb49-O0A,15321
6
+ OpenApiDriver/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ OpenApiLibCore/__init__.py,sha256=R6n4FTEz0IGD_LehKkOFSQrlt9Bk4Ugl3OicsZ_h84I,1775
8
+ OpenApiLibCore/annotations.py,sha256=zfWwItXzECBezyQKoom9BLfrRFlU1u4jOF7NEgEAuQQ,135
9
+ OpenApiLibCore/data_generation/__init__.py,sha256=KYBoMeFsa4fzb6E-BsQBykCGwt54NvXthXiy9VNwQBc,303
10
+ OpenApiLibCore/data_generation/body_data_generation.py,sha256=c887DfNMghHFyu3omUDxA0SkaQtMaNiWYNM3YREbbEQ,8903
11
+ OpenApiLibCore/data_generation/data_generation_core.py,sha256=N6hgyc2WHnPfIRwqdUwmT9XSGM2P-l88K2kblk0VCeA,8413
12
+ OpenApiLibCore/data_invalidation.py,sha256=HSzbeJnzxrlS2NlPfXnCRXvGzGzXvBwiaJ3WXxrZOSw,10503
13
+ OpenApiLibCore/dto_base.py,sha256=m41QZyL6ChIcC3DcBCpDFkok5wpCZ6K9gjmADyMGOWc,12132
14
+ OpenApiLibCore/dto_utils.py,sha256=5dvMYM2Nt9gXq6GGTGJjU5Z75x9p9ky-1V01aLTZgX8,3177
15
+ OpenApiLibCore/oas_cache.py,sha256=Z2v0mn6JwKchbUKojYVt9sXi8Mc4LoENtN_ekTUtzfI,387
16
+ OpenApiLibCore/openapi_libcore.libspec,sha256=45xzp_QdGtaH4F_RjDDlL6mEKH12f6NndY0nrZi4yaA,53175
17
+ OpenApiLibCore/openapi_libcore.py,sha256=30FtvsNHkfWZhz55bJq8bnWiHKvX6dEbGyLhbdNAtqY,36257
18
+ OpenApiLibCore/parameter_utils.py,sha256=_P2GRc_teIixTgPsRgCxY2Bj6nsZq41GoSOhVgfXE0Y,3251
19
+ OpenApiLibCore/path_functions.py,sha256=757f655yLg5HzvXxlOIQ76i48gK91Ct7KM-D9OAEA3A,8055
20
+ OpenApiLibCore/path_invalidation.py,sha256=2iYjkskMV-7lWu_id_LbVIiumffMWZicqoIKeewqpFA,1539
21
+ OpenApiLibCore/protocols.py,sha256=N9HxH9SqNuzWTyOGQuyEGfyorHZyYkehlBsyPse5jtQ,763
22
+ OpenApiLibCore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ OpenApiLibCore/request_data.py,sha256=jm6HnTG4xvQfSeYPMIMzowlSi7cuU3XqDGTQ6FdKnUY,10644
24
+ OpenApiLibCore/resource_relations.py,sha256=Qp5TBNSArLaDfAqcjC5xiHwlB1h4hgAFIfuJnBg5Yxw,1710
25
+ OpenApiLibCore/validation.py,sha256=uVPgg3k5X1ww4u4-glqC2szSL1qsvqgnCUoqUMjpnek,20067
26
+ OpenApiLibCore/value_utils.py,sha256=M4k5m0YW1B20-6KD0f9-5xMERWKG8ncu2-5ahC2_VzU,19208
27
+ openapi_libgen/__init__.py,sha256=9ZfgPSI8AUAcJ59hBRZE1VImhMVRyt6G_w1nYIPORgQ,1543
28
+ openapi_libgen/command_line.py,sha256=GppzNAAMomd0Dl5c5HGK8chDQw-kaIztD0hqQ2iUEXE,2783
29
+ openapi_libgen/parsing_utils.py,sha256=d_N-v619SR6iyolz65IGN12H5wMUA8dzPuG1l7rv5gg,821
30
+ openapi_libgen/spec_parser.py,sha256=9WCuCCDpMMr9jhA8fgzjBhhoF_DEWFc0lBwFcGBDqs4,8509
31
+ openapi_libgen/templates/__init__.jinja,sha256=92OFwNzeO7lEiUBv04GfjKXA1088V7GbeneASBShbmc,102
32
+ openapi_libgen/templates/library.jinja,sha256=tIwJuGerOUJmMt928P_38MMfcnSHZqcPDPEJ56R6wT0,952
33
+ robotframework_openapitools-1.0.0b2.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
34
+ robotframework_openapitools-1.0.0b2.dist-info/METADATA,sha256=Nu-LfVU0r_z8VCFX5UeM3r9eFScb9eiFmyalTBEiF10,14491
35
+ robotframework_openapitools-1.0.0b2.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
36
+ robotframework_openapitools-1.0.0b2.dist-info/entry_points.txt,sha256=_yd5PITEJf85hIEsrRkkWxXePf_O9YOOUjON3TYLy0c,69
37
+ robotframework_openapitools-1.0.0b2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: poetry-core 2.1.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ generate-library=openapi_libgen.command_line:main
3
+
roboswag/__init__.py DELETED
@@ -1,9 +0,0 @@
1
- from importlib.metadata import version
2
-
3
- from roboswag.cli import cli
4
- from roboswag.core import APIModel
5
-
6
- try:
7
- __version__ = version("robotframework-openapitools")
8
- except Exception: # pragma: no cover
9
- pass
roboswag/__main__.py DELETED
@@ -1,3 +0,0 @@
1
- from roboswag import cli
2
-
3
- cli()
roboswag/auth.py DELETED
@@ -1,44 +0,0 @@
1
- from requests.auth import HTTPBasicAuth
2
- from robot.libraries.BuiltIn import BuiltIn
3
-
4
-
5
- class MissingParameter(ValueError):
6
- def __init__(self, name):
7
- super().__init__(
8
- f"Missing {name}. Set ${{{name}}} global variable or pass it as {name} named variable."
9
- )
10
-
11
-
12
- def get_from_kwargs_or_robot(
13
- kwargs, name, missing_ok=True
14
- ): # TODO pass flag to enforce auth with missing_ok
15
- value = kwargs.get(name)
16
- if value is None:
17
- value = BuiltIn().get_variable_value(f"${{{name}}}")
18
- if not missing_ok and value is None:
19
- raise MissingParameter(name) from None
20
- return value
21
-
22
-
23
- class TokenHandler:
24
- # FIXME
25
- def __init__(self, **kwargs):
26
- self.access_token = get_from_kwargs_or_robot(kwargs, "access_token")
27
-
28
- def __call__(self, *args, **kwargs):
29
- user = get_from_kwargs_or_robot(kwargs, "user")
30
- password = get_from_kwargs_or_robot(kwargs, "password")
31
- super().__init__(user, password)
32
-
33
-
34
- class BasicAuth(HTTPBasicAuth):
35
- def __init__(self, **kwargs):
36
- user = get_from_kwargs_or_robot(kwargs, "user")
37
- password = get_from_kwargs_or_robot(kwargs, "password")
38
- super().__init__(user, password)
39
-
40
-
41
- AUTH_BACKENDS = {
42
- "disable": "disable",
43
- "basicauth": "BasicAuth",
44
- }
roboswag/cli.py DELETED
@@ -1,80 +0,0 @@
1
- from pathlib import Path
2
- from typing import Optional
3
-
4
- import rich_click as click
5
-
6
- from roboswag import __version__ as version
7
- from roboswag.auth import AUTH_BACKENDS
8
- from roboswag.generate import generate_libraries
9
-
10
- CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
11
-
12
-
13
- class AuthBackend(click.ParamType):
14
- name = "auth"
15
-
16
- def convert(self, value, param, ctx):
17
- backends = {
18
- backend.lower(): auth_class for backend, auth_class in AUTH_BACKENDS.items()
19
- }
20
- normalized = value.casefold()
21
- if normalized in backends:
22
- return backends[normalized]
23
- backend_names = "\n ".join(backend for backend in AUTH_BACKENDS.keys())
24
- self.fail(
25
- f"Invalid authentication backend: {value}. Authentication can be only one of:\n {backend_names}",
26
- param,
27
- ctx,
28
- )
29
-
30
-
31
- @click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
32
- @click.version_option(version=version, prog_name="roboswag")
33
- def cli():
34
- """
35
- Roboswag is a tool that generates Python libraries out of your Swagger (OpenAPI specification file).
36
- """
37
- pass
38
-
39
-
40
- @cli.command()
41
- @click.option(
42
- "-s",
43
- "--spec",
44
- type=click.Path(
45
- exists=True,
46
- file_okay=True,
47
- dir_okay=False,
48
- readable=True,
49
- allow_dash=False,
50
- path_type=str,
51
- ),
52
- required=True,
53
- metavar="SWAGGER",
54
- help="OpenAPI specification file",
55
- )
56
- @click.option(
57
- "-o",
58
- "--output-dir",
59
- type=click.Path(
60
- file_okay=False,
61
- dir_okay=True,
62
- writable=True,
63
- allow_dash=False,
64
- path_type=Path,
65
- ),
66
- show_default="current working directory",
67
- metavar="OUTPUT_DIR",
68
- help="Output directory path",
69
- )
70
- @click.option(
71
- "-a",
72
- "--auth",
73
- type=AuthBackend(),
74
- show_default="Authentication found in the specification",
75
- metavar="AUTH_CLASS",
76
- help="Overwrite default authentication class",
77
- )
78
- def generate(spec: str, output_dir: Optional[Path] = None, auth: Optional[Path] = None):
79
- """Generate Python libraries."""
80
- generate_libraries(spec, output_dir, auth)
roboswag/core.py DELETED
@@ -1,85 +0,0 @@
1
- import requests
2
- import urllib3
3
-
4
- from roboswag.auth import TokenHandler
5
- from roboswag.logger import Logger
6
- from roboswag.validate import Validate
7
-
8
- urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
9
-
10
-
11
- class APIModel:
12
- def __init__(
13
- self,
14
- base_url,
15
- verify=False,
16
- headers=None,
17
- content_type="application/json",
18
- proxies=None,
19
- allow_redirects=True,
20
- authentication=None,
21
- ):
22
- # Set headers from init too (or reuse auth)
23
- self.base_url = base_url
24
- self.session = requests.Session()
25
- self.session.verify = verify
26
- self.content_type = content_type
27
- if content_type is not None:
28
- self.session.headers = {"Content-Type": content_type}
29
- self.allow_redirects = allow_redirects
30
- if proxies is not None: # TODO urllib have autodetect proxy - allow to use it
31
- self.session.proxies.update(proxies)
32
- self.authentication = authentication
33
- self.logger = Logger()
34
- self.validate = Validate(self.logger)
35
- if headers is not None:
36
- self.session.headers.update(headers)
37
-
38
- def send_request(
39
- self, method, url, status=None, headers=None, body=None, query=None, **kwargs
40
- ):
41
- headers = self.trim_empty(headers)
42
- query = self.trim_empty(query)
43
- auth = (
44
- self.authentication(**kwargs) if self.authentication is not None else None
45
- )
46
- content_type = kwargs.get("content-type", self.content_type)
47
- if content_type is not None:
48
- headers["Content-Type"] = content_type
49
-
50
- resp = self.session.request(
51
- method,
52
- url=self.base_url + url,
53
- headers=headers,
54
- json=body,
55
- params=query,
56
- auth=auth,
57
- allow_redirects=self.allow_redirects,
58
- )
59
- # TODO quiet mode
60
- self.logger.log_request(resp)
61
- self.logger.log_response(resp)
62
- if status is not None:
63
- assert (
64
- resp.status_code == status
65
- ), f"Expected return status: {status} but received: {resp.status_code}"
66
- return resp
67
-
68
- def post(self, *args, **kwargs):
69
- # TODO handle files upload
70
- return self.send_request("POST", *args, **kwargs)
71
-
72
- def get(self, *args, **kwargs):
73
- return self.send_request("GET", *args, **kwargs)
74
-
75
- def put(self, *args, **kwargs):
76
- return self.send_request("PUT", *args, **kwargs)
77
-
78
- def delete(self, *args, **kwargs):
79
- return self.send_request("DELETE", *args, **kwargs)
80
-
81
- @staticmethod
82
- def trim_empty(dictionary):
83
- if dictionary is None:
84
- return {}
85
- return {key: value for key, value in dictionary.items() if value is not None}
@@ -1 +0,0 @@
1
- from roboswag.generate.generate import generate_libraries
@@ -1,121 +0,0 @@
1
- import json
2
- from pathlib import Path
3
- from typing import Any, Optional
4
-
5
- import black
6
- from jinja2 import Template
7
-
8
- from roboswag.generate.models.api import (
9
- get_definitions_from_swagger,
10
- parse_swagger_specification,
11
- )
12
-
13
-
14
- class LibraryGenerator:
15
- def __init__(self, source, output: Optional[Path], authentication):
16
- self.source = source
17
- self.parent_dir = Path(__file__).parent
18
- api_model, swagger = parse_swagger_specification(self.source)
19
- self.api_model = api_model
20
- self.swagger = swagger
21
- self.output_dir = self.resolve_output_dir(output)
22
- self.default_auth = authentication
23
- self.unformatted_files = []
24
-
25
- def resolve_output_dir(self, output: Optional[Path]):
26
- output_dir = self.api_model.name
27
- if output is None:
28
- return Path(output_dir)
29
- return output / output_dir
30
-
31
- def generate(self):
32
- self.output_dir.mkdir(exist_ok=True, parents=True)
33
- self.generate_init()
34
- self.generate_endpoints()
35
- self.generate_models()
36
- self.generate_schemas()
37
- self.format_files()
38
-
39
- def format_files(self):
40
- for path in self.unformatted_files:
41
- blackify_file(path)
42
- self.unformatted_files = []
43
-
44
- def generate_init(self):
45
- swagger_version = self.swagger.get("openapi") or self.swagger.get("swagger")
46
- api_init_template = self.parent_dir / "templates" / "api_init.jinja"
47
- with open(api_init_template) as f:
48
- template = Template(f.read()).render(
49
- swagger_version=swagger_version, infos=self.swagger["info"]
50
- )
51
- init_file = self.output_dir / "__init__.py"
52
- with open(init_file, "w") as f:
53
- f.write(template)
54
- print(f"Generated '{init_file}' file")
55
- self.unformatted_files.append(init_file)
56
-
57
- def get_api_auth(self):
58
- if self.default_auth is None:
59
- return self.api_model.authentication
60
- if self.default_auth == "disable":
61
- return None
62
- return self.default_auth
63
-
64
- def generate_endpoints(self):
65
- endpoints_dir = self.output_dir / "endpoints"
66
- Path(endpoints_dir).mkdir(exist_ok=True)
67
- print("Generating endpoints...")
68
- for tag in self.api_model.tags.values():
69
- paths_template = self.parent_dir / "templates" / "paths.jinja"
70
- with open(paths_template) as f:
71
- template = Template(f.read()).render(
72
- class_name=tag.name,
73
- authentication=self.get_api_auth(),
74
- endpoints=tag.endpoints,
75
- description=tag.description,
76
- )
77
- endpoint_file = endpoints_dir / f"{tag.name}.py"
78
- with open(endpoint_file, "w") as f:
79
- f.write(template)
80
- print(f"Generated '{endpoint_file}' file")
81
- self.unformatted_files.append(endpoint_file)
82
-
83
- def generate_models(self):
84
- models_dir = self.output_dir / "models"
85
- Path(models_dir).mkdir(exist_ok=True)
86
- print("Generating models...")
87
- for definition in self.api_model.definitions.values():
88
- models_template = self.parent_dir / "templates" / "models.jinja"
89
- with open(models_template) as f:
90
- template = Template(f.read()).render(
91
- class_name=definition.name, properties=definition.properties
92
- )
93
- model_file = models_dir / f"{definition.name}.py"
94
- with open(model_file, "w") as f:
95
- f.write(template)
96
- print(f"Generated '{model_file}' file")
97
- self.unformatted_files.append(model_file)
98
-
99
- def generate_schemas(self):
100
- schemas_dir = self.output_dir / "schemas"
101
- Path(schemas_dir).mkdir(exist_ok=True)
102
- print("Generating schemas...")
103
- schemas = get_definitions_from_swagger(self.swagger)
104
- for schema_name, schema in schemas.items():
105
- schema_file = schemas_dir / f"{schema_name}.json"
106
- with open(schema_file, "w") as f:
107
- f.write(json.dumps(schema, indent=4))
108
- f.write("\n")
109
- print(f"Generated '{schema_file}' file")
110
- self.unformatted_files.append(schema_file)
111
-
112
-
113
- def blackify_file(source):
114
- black.format_file_in_place(
115
- source, fast=True, mode=black.FileMode(), write_back=black.WriteBack.YES
116
- )
117
-
118
-
119
- def generate_libraries(source: str, output_dir: Optional[Path], authentication: Any):
120
- generator = LibraryGenerator(source, output_dir, authentication)
121
- generator.generate()
File without changes
@@ -1,219 +0,0 @@
1
- import re
2
- from collections import defaultdict
3
- from typing import Dict, Optional
4
-
5
- from prance import ResolvingParser
6
- from prance.convert import convert_spec
7
-
8
- from roboswag.generate.models.definition import (
9
- Definition,
10
- Property,
11
- get_definitions_from_swagger,
12
- )
13
- from roboswag.generate.models.endpoint import Endpoint
14
- from roboswag.generate.models.parameter import Parameter
15
- from roboswag.generate.models.response import Response
16
- from roboswag.generate.models.tag import Tag
17
- from roboswag.generate.models.utils import get_python_type, pythonify_name
18
-
19
-
20
- def get_schema(param):
21
- return param.get("schema") or get_schema_openapi_v3(param)
22
-
23
-
24
- def get_schema_openapi_v3(param):
25
- return param.get("content", {}).get("application/json", {}).get("schema", {})
26
-
27
-
28
- def get_body(params, method_body):
29
- """
30
- Get optional body of the request.
31
- For OpenAPI v2 body is part of the parameters (with in: body).
32
- For OpenAPi v3 body is in separate property requestBody.
33
- # TODO: We only support application/json type of the body
34
- """
35
- if "body" in params:
36
- # TODO Can it be more than one? If not, should we use the same container as for headers & query?
37
- return params["body"][0]
38
- if "requestBody" in method_body:
39
- schema = get_schema_openapi_v3(method_body["requestBody"])
40
- if not schema:
41
- return None
42
- return Parameter(
43
- "body",
44
- default="None",
45
- param_type=None,
46
- description=None,
47
- required=None,
48
- schema=schema, # TODO
49
- )
50
-
51
-
52
- class APIModel:
53
- def __init__(self) -> None:
54
- self.name: str = ""
55
- self.tags: Dict[str, Tag] = {}
56
- self.definitions: Dict[str, Definition] = {}
57
- self.authentication: Optional[str] = None
58
-
59
- def parse_swagger(self, swagger):
60
- self.parse_info(swagger)
61
- self.parse_paths(swagger)
62
- self.parse_tags(swagger)
63
- self.parse_schemas(swagger)
64
- self.parse_authentication(swagger)
65
-
66
- def parse_info(self, swagger):
67
- name = swagger["info"]["title"].replace(" ", "")
68
- self.name = name
69
-
70
- def get_unique_endpoint_name(self, path, method, method_body):
71
- if "operationId" in method_body:
72
- return pythonify_name(method_body["operationId"])
73
- # split on camelCase and / path
74
- parts = re.split("((?<=[a-z])(?=[A-Z])|/)", path)
75
- name = method
76
- for part in parts:
77
- part = re.sub("[/{}]", "", part)
78
- part = part.replace("-", "_")
79
- if not part:
80
- continue
81
- name += f"_{part.lower()}"
82
- return name
83
-
84
- @staticmethod
85
- def class_name_from_tag(method_body, **kwargs):
86
- return method_body["tags"][0].strip(" -_").title()
87
-
88
- @staticmethod
89
- def class_name_from_path(path, **kwargs):
90
- for part in path.split("/"):
91
- part = re.sub("[/{}]", "", part)
92
- if not part:
93
- continue
94
- return part.title()
95
- return "Root"
96
-
97
- def set_source_of_class_name(self, swagger):
98
- """
99
- If all methods have their tags - use tag name. Otherwise use first part of the path.
100
- """
101
- # TODO configurable class name
102
- for path_body in swagger["paths"].values():
103
- for method in path_body.values():
104
- if "tags" not in method:
105
- return self.class_name_from_path
106
- return self.class_name_from_tag
107
-
108
- def parse_paths(self, swagger):
109
- get_class_name = self.set_source_of_class_name(swagger)
110
- for path, path_body in swagger["paths"].items():
111
- method: str
112
- method_body: Dict
113
- for method, method_body in path_body.items():
114
- class_name = get_class_name(path=path, method_body=method_body)
115
- unique_name = self.get_unique_endpoint_name(path, method, method_body)
116
- summary = method_body.get("summary", "")
117
- description = method_body.get("description", "")
118
- params = defaultdict(list)
119
- for param in method_body.get("parameters", []):
120
- params[param["in"]].append(
121
- Parameter(
122
- param["name"],
123
- default=(
124
- "None" if param["in"] != "path" else None
125
- ), # TODO Retrieve default value
126
- param_type=(
127
- get_python_type(param["type"], param.get("format"))
128
- if param.get("type")
129
- else None
130
- ),
131
- description=param.get("description"),
132
- required=param.get("required"),
133
- schema=get_schema(param),
134
- )
135
- )
136
- responses = dict()
137
- for status_code, resp in method_body.get("responses", []).items():
138
- responses[status_code] = Response(
139
- description=resp.get("description"),
140
- headers=resp.get("headers"),
141
- schema=get_schema(resp),
142
- )
143
- body = get_body(params, method_body)
144
- endpoint = Endpoint(
145
- unique_name,
146
- method,
147
- path,
148
- summary=summary,
149
- description=description,
150
- path_params=params["path"],
151
- headers=params["header"],
152
- query=params["query"],
153
- body=body,
154
- responses=responses,
155
- )
156
- self.add_endpoint_to_tag(class_name, endpoint)
157
-
158
- def parse_tags(self, swagger):
159
- if not swagger.get("tags"):
160
- return
161
- for tag in swagger["tags"]:
162
- tag_name = Tag.normalize_tag_name(tag["name"])
163
- if tag_name in self.tags:
164
- self.tags[tag_name].description = tag["description"]
165
-
166
- def add_endpoint_to_tag(self, tag: str, endpoint: Endpoint) -> None:
167
- if tag not in self.tags:
168
- self.tags[tag] = Tag(tag)
169
- self.tags[tag].endpoints.append(endpoint)
170
-
171
- def parse_schemas(self, swagger):
172
- schemas = get_definitions_from_swagger(swagger)
173
- for def_name, def_body in schemas.items():
174
- def_type = def_body.get("type", "")
175
- def_req = def_body.get("required")
176
- properties = []
177
- for prop_name, prop_body in def_body.get("properties", {}).items():
178
- prop_type = prop_body.get("type", "")
179
- prop_format = prop_body.get("format", "")
180
- properties.append(
181
- Property(
182
- prop_name, prop_type=get_python_type(prop_type, prop_format)
183
- )
184
- )
185
-
186
- definition = Definition(
187
- def_name, def_type=def_type, properties=properties, required=def_req
188
- )
189
- self.definitions[def_name] = definition
190
-
191
- def parse_authentication(self, swagger):
192
- # TODO we should check what security schemes applies to which endpoints
193
- # it could be that entire path does not need auth, while other needs it etc
194
- security_schemes = swagger.get("components", {}).get("securitySchemes", {})
195
- if not security_schemes:
196
- return
197
- for name, scheme in security_schemes.items():
198
- if scheme.get("type", "") == "http" and scheme.get("scheme", "") == "basic":
199
- self.authentication = "BasicAuth"
200
- return
201
-
202
-
203
- def parse_swagger_specification(source, convert_to_3=False):
204
- def recursion_limit_handler(limit, refstring, recursions):
205
- return {}
206
-
207
- parser = ResolvingParser(
208
- source,
209
- backend="openapi-spec-validator",
210
- recursion_limit=1,
211
- recursion_limit_handler=recursion_limit_handler,
212
- )
213
- swagger = parser.specification
214
- # convert to OpenAPI 3.x if swagger is in version 2.x
215
- if swagger.get("swagger") and convert_to_3:
216
- swagger = convert_spec(swagger).specification
217
- api_model = APIModel()
218
- api_model.parse_swagger(swagger)
219
- return api_model, swagger
@@ -1,28 +0,0 @@
1
- from typing import List
2
-
3
- from roboswag.generate.models.utils import (
4
- get_python_type,
5
- pythonify_name,
6
- replace_reserved_name,
7
- )
8
-
9
-
10
- class Property:
11
- def __init__(self, name: str, prop_type=None):
12
- self.name: str = replace_reserved_name(pythonify_name(name))
13
- self.type = prop_type
14
-
15
-
16
- class Definition:
17
- def __init__(self, name: str, def_type, properties: List[Property], required=None):
18
- self.name: str = name
19
- self.type = get_python_type(def_type)
20
- self.required = required
21
- self.properties: List[Property] = properties
22
-
23
-
24
- def get_definitions_from_swagger(swagger):
25
- """
26
- Get resolved schema definition from Swagger file. Supports both OpenAPI v2 and 3.
27
- """
28
- return swagger.get("definitions", swagger.get("components", {}).get("schemas", {}))