robotframework-openapitools 0.4.0__py3-none-any.whl → 1.0.0b1__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.
- OpenApiDriver/__init__.py +44 -41
- OpenApiDriver/openapi_executors.py +40 -39
- OpenApiDriver/openapi_reader.py +115 -116
- OpenApiDriver/openapidriver.libspec +71 -61
- OpenApiDriver/openapidriver.py +25 -19
- OpenApiLibCore/__init__.py +13 -11
- OpenApiLibCore/annotations.py +3 -0
- OpenApiLibCore/data_generation/__init__.py +12 -0
- OpenApiLibCore/data_generation/body_data_generation.py +269 -0
- OpenApiLibCore/data_generation/data_generation_core.py +240 -0
- OpenApiLibCore/data_invalidation.py +281 -0
- OpenApiLibCore/dto_base.py +29 -35
- OpenApiLibCore/dto_utils.py +97 -85
- OpenApiLibCore/oas_cache.py +14 -13
- OpenApiLibCore/openapi_libcore.libspec +350 -193
- OpenApiLibCore/openapi_libcore.py +392 -1698
- OpenApiLibCore/parameter_utils.py +89 -0
- OpenApiLibCore/path_functions.py +215 -0
- OpenApiLibCore/path_invalidation.py +44 -0
- OpenApiLibCore/protocols.py +30 -0
- OpenApiLibCore/request_data.py +275 -0
- OpenApiLibCore/resource_relations.py +54 -0
- OpenApiLibCore/validation.py +497 -0
- OpenApiLibCore/value_utils.py +528 -481
- openapi_libgen/__init__.py +46 -0
- openapi_libgen/command_line.py +87 -0
- openapi_libgen/parsing_utils.py +26 -0
- openapi_libgen/spec_parser.py +212 -0
- openapi_libgen/templates/__init__.jinja +3 -0
- openapi_libgen/templates/library.jinja +30 -0
- robotframework_openapitools-1.0.0b1.dist-info/METADATA +237 -0
- robotframework_openapitools-1.0.0b1.dist-info/RECORD +37 -0
- {robotframework_openapitools-0.4.0.dist-info → robotframework_openapitools-1.0.0b1.dist-info}/WHEEL +1 -1
- robotframework_openapitools-1.0.0b1.dist-info/entry_points.txt +3 -0
- roboswag/__init__.py +0 -9
- roboswag/__main__.py +0 -3
- roboswag/auth.py +0 -44
- roboswag/cli.py +0 -80
- roboswag/core.py +0 -85
- roboswag/generate/__init__.py +0 -1
- roboswag/generate/generate.py +0 -121
- roboswag/generate/models/__init__.py +0 -0
- roboswag/generate/models/api.py +0 -219
- roboswag/generate/models/definition.py +0 -28
- roboswag/generate/models/endpoint.py +0 -68
- roboswag/generate/models/parameter.py +0 -25
- roboswag/generate/models/response.py +0 -8
- roboswag/generate/models/tag.py +0 -16
- roboswag/generate/models/utils.py +0 -60
- roboswag/generate/templates/api_init.jinja +0 -15
- roboswag/generate/templates/models.jinja +0 -7
- roboswag/generate/templates/paths.jinja +0 -68
- roboswag/logger.py +0 -33
- roboswag/validate/__init__.py +0 -6
- roboswag/validate/core.py +0 -3
- roboswag/validate/schema.py +0 -21
- roboswag/validate/text_response.py +0 -14
- robotframework_openapitools-0.4.0.dist-info/METADATA +0 -42
- robotframework_openapitools-0.4.0.dist-info/RECORD +0 -41
- {robotframework_openapitools-0.4.0.dist-info → robotframework_openapitools-1.0.0b1.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=tv-dgEUGIpLve8KbLqGujgr0kTWLg2Txem6V12-qCco,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=fBcQapNBAweSjfMAOO_LMYj2NDzSgQnSpcKnZhusB5Y,53487
|
17
|
+
OpenApiLibCore/openapi_libcore.py,sha256=-1Kk8AeZ0cGe8HtkADFy9y-8E4j_nWwFImLgGTlNNrg,36473
|
18
|
+
OpenApiLibCore/parameter_utils.py,sha256=qEvlG9ddALrytvRVFxfmcLEtpKQBufWTOJDrEqw4ofQ,3244
|
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=K-wXpGbywo79eTRrAxyT5aUZALhbzuL7aGcwtdxvxr0,10391
|
24
|
+
OpenApiLibCore/resource_relations.py,sha256=Qp5TBNSArLaDfAqcjC5xiHwlB1h4hgAFIfuJnBg5Yxw,1710
|
25
|
+
OpenApiLibCore/validation.py,sha256=gSR3-TnfbC59EvPpOSC6_Y2FVj-9NyibKqsYRhbkF3g,20053
|
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=eSDIPlAGw6koeEYNXYyqV7wzpXrJ3Ry_qmu7FMBmaRU,8068
|
31
|
+
openapi_libgen/templates/__init__.jinja,sha256=92OFwNzeO7lEiUBv04GfjKXA1088V7GbeneASBShbmc,102
|
32
|
+
openapi_libgen/templates/library.jinja,sha256=16Sx8L4zmt6N1p-vr7AHUkkEDSi9USCq0owE68xFATk,942
|
33
|
+
robotframework_openapitools-1.0.0b1.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
34
|
+
robotframework_openapitools-1.0.0b1.dist-info/METADATA,sha256=iIBGsaZ5vkXpjb4Ue2H7lQQ8gVgLM1dhTtrtrmJt5d8,14491
|
35
|
+
robotframework_openapitools-1.0.0b1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
36
|
+
robotframework_openapitools-1.0.0b1.dist-info/entry_points.txt,sha256=_yd5PITEJf85hIEsrRkkWxXePf_O9YOOUjON3TYLy0c,69
|
37
|
+
robotframework_openapitools-1.0.0b1.dist-info/RECORD,,
|
roboswag/__init__.py
DELETED
roboswag/__main__.py
DELETED
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}
|
roboswag/generate/__init__.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
from roboswag.generate.generate import generate_libraries
|
roboswag/generate/generate.py
DELETED
@@ -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
|
roboswag/generate/models/api.py
DELETED
@@ -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", {}))
|