Merge "Consume decorated Keystone methods"
This commit is contained in:
commit
fde587de5c
@ -647,88 +647,20 @@ class OpenStackServerSourceBase:
|
|||||||
if action_name:
|
if action_name:
|
||||||
operation_name = action_name
|
operation_name = action_name
|
||||||
|
|
||||||
# Unwrap operation decorators to access all properties
|
(
|
||||||
f = func
|
query_params_versions,
|
||||||
while hasattr(f, "__wrapped__"):
|
body_schemas,
|
||||||
closure = inspect.getclosurevars(f)
|
response_body_schema,
|
||||||
closure_locals = closure.nonlocals
|
expected_errors,
|
||||||
min_ver = closure_locals.get("min_version", start_version)
|
) = self._process_decorators(
|
||||||
if min_ver and not isinstance(min_ver, str):
|
func,
|
||||||
min_ver = min_ver.get_string()
|
path_resource_names,
|
||||||
max_ver = closure_locals.get("max_version", end_version)
|
openapi_spec,
|
||||||
if max_ver and not isinstance(max_ver, str):
|
mode,
|
||||||
max_ver = max_ver.get_string()
|
start_version,
|
||||||
|
end_version,
|
||||||
if "errors" in closure_locals:
|
action_name,
|
||||||
expected_errors = closure_locals["errors"]
|
|
||||||
if isinstance(expected_errors, list):
|
|
||||||
expected_errors = [
|
|
||||||
str(x)
|
|
||||||
for x in filter(
|
|
||||||
lambda x: isinstance(x, int), expected_errors
|
|
||||||
)
|
)
|
||||||
]
|
|
||||||
elif isinstance(expected_errors, int):
|
|
||||||
expected_errors = [str(expected_errors)]
|
|
||||||
if "request_body_schema" in closure_locals or hasattr(
|
|
||||||
f, "_request_body_schema"
|
|
||||||
):
|
|
||||||
# Body type is known through method decorator
|
|
||||||
obj = closure_locals.get(
|
|
||||||
"request_body_schema",
|
|
||||||
getattr(f, "_request_body_schema", {}),
|
|
||||||
)
|
|
||||||
if obj.get("type") in ["object", "array"]:
|
|
||||||
# We only allow object and array bodies
|
|
||||||
# To prevent type name collision keep module name part of the name
|
|
||||||
typ_name = (
|
|
||||||
"".join([x.title() for x in path_resource_names])
|
|
||||||
+ func.__name__.title()
|
|
||||||
+ (f"_{min_ver.replace('.', '')}" if min_ver else "")
|
|
||||||
)
|
|
||||||
comp_schema = openapi_spec.components.schemas.setdefault(
|
|
||||||
typ_name,
|
|
||||||
self._sanitize_schema(
|
|
||||||
copy.deepcopy(obj),
|
|
||||||
start_version=start_version,
|
|
||||||
end_version=end_version,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
if min_ver:
|
|
||||||
if not comp_schema.openstack:
|
|
||||||
comp_schema.openstack = {}
|
|
||||||
comp_schema.openstack["min-ver"] = min_ver
|
|
||||||
if max_ver:
|
|
||||||
if not comp_schema.openstack:
|
|
||||||
comp_schema.openstack = {}
|
|
||||||
comp_schema.openstack["max-ver"] = max_ver
|
|
||||||
if mode == "action":
|
|
||||||
if not comp_schema.openstack:
|
|
||||||
comp_schema.openstack = {}
|
|
||||||
comp_schema.openstack["action-name"] = action_name
|
|
||||||
|
|
||||||
ref_name = f"#/components/schemas/{typ_name}"
|
|
||||||
body_schemas.append(ref_name)
|
|
||||||
if "response_body_schema" in closure_locals or hasattr(
|
|
||||||
f, "_response_body_schema"
|
|
||||||
):
|
|
||||||
# Response type is known through method decorator - PERFECT
|
|
||||||
obj = closure_locals.get(
|
|
||||||
"response_body_schema",
|
|
||||||
getattr(f, "_response_body_schema", {}),
|
|
||||||
)
|
|
||||||
ser_schema = obj
|
|
||||||
if "query_params_schema" in closure_locals or hasattr(
|
|
||||||
f, "_request_query_schema"
|
|
||||||
):
|
|
||||||
obj = closure_locals.get(
|
|
||||||
"query_params_schema",
|
|
||||||
getattr(f, "_request_query_schema", {}),
|
|
||||||
)
|
|
||||||
query_params_versions.append((obj, min_ver, max_ver))
|
|
||||||
|
|
||||||
f = f.__wrapped__
|
|
||||||
|
|
||||||
if hasattr(func, "_wsme_definition"):
|
if hasattr(func, "_wsme_definition"):
|
||||||
fdef = getattr(func, "_wsme_definition")
|
fdef = getattr(func, "_wsme_definition")
|
||||||
@ -786,6 +718,8 @@ class OpenStackServerSourceBase:
|
|||||||
operation_name,
|
operation_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if ser_schema and not response_body_schema:
|
||||||
|
response_body_schema = ser_schema
|
||||||
responses_spec = operation_spec.responses
|
responses_spec = operation_spec.responses
|
||||||
for error in expected_errors:
|
for error in expected_errors:
|
||||||
responses_spec.setdefault(str(error), dict(description="Error"))
|
responses_spec.setdefault(str(error), dict(description="Error"))
|
||||||
@ -830,7 +764,7 @@ class OpenStackServerSourceBase:
|
|||||||
if not action_name
|
if not action_name
|
||||||
else f"Response of the {operation_spec.operationId}:{action_name} action"
|
else f"Response of the {operation_spec.operationId}:{action_name} action"
|
||||||
), # noqa
|
), # noqa
|
||||||
schema_def=ser_schema,
|
schema_def=response_body_schema,
|
||||||
action_name=action_name,
|
action_name=action_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -910,7 +844,8 @@ class OpenStackServerSourceBase:
|
|||||||
**copy.deepcopy(spec["items"])
|
**copy.deepcopy(spec["items"])
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Error")
|
param_attrs["schema"] = TypeSchema(**copy.deepcopy(spec))
|
||||||
|
param_attrs["description"] = spec.get("description")
|
||||||
if min_ver:
|
if min_ver:
|
||||||
os_ext = param_attrs.setdefault("x-openstack", {})
|
os_ext = param_attrs.setdefault("x-openstack", {})
|
||||||
os_ext["min-ver"] = min_ver
|
os_ext["min-ver"] = min_ver
|
||||||
@ -1218,6 +1153,113 @@ class OpenStackServerSourceBase:
|
|||||||
response_code = "200"
|
response_code = "200"
|
||||||
return [response_code]
|
return [response_code]
|
||||||
|
|
||||||
|
def _process_decorators(
|
||||||
|
self,
|
||||||
|
func,
|
||||||
|
path_resource_names: list[str],
|
||||||
|
openapi_spec,
|
||||||
|
mode: str,
|
||||||
|
start_version,
|
||||||
|
end_version,
|
||||||
|
action_name: str | None,
|
||||||
|
):
|
||||||
|
"""Extract schemas from the decorated method."""
|
||||||
|
# Unwrap operation decorators to access all properties
|
||||||
|
expected_errors: list[str] = []
|
||||||
|
body_schemas: list[str] = []
|
||||||
|
query_params_versions: list[tuple] = []
|
||||||
|
response_body_schema: dict | None = None
|
||||||
|
|
||||||
|
f = func
|
||||||
|
while hasattr(f, "__wrapped__"):
|
||||||
|
closure = inspect.getclosurevars(f)
|
||||||
|
closure_locals = closure.nonlocals
|
||||||
|
min_ver = closure_locals.get("min_version", start_version)
|
||||||
|
if min_ver and not isinstance(min_ver, str):
|
||||||
|
min_ver = min_ver.get_string()
|
||||||
|
max_ver = closure_locals.get("max_version", end_version)
|
||||||
|
if max_ver and not isinstance(max_ver, str):
|
||||||
|
max_ver = max_ver.get_string()
|
||||||
|
|
||||||
|
if "errors" in closure_locals:
|
||||||
|
expected_errors = closure_locals["errors"]
|
||||||
|
if isinstance(expected_errors, list):
|
||||||
|
expected_errors = [
|
||||||
|
str(x)
|
||||||
|
for x in filter(
|
||||||
|
lambda x: isinstance(x, int), expected_errors
|
||||||
|
)
|
||||||
|
]
|
||||||
|
elif isinstance(expected_errors, int):
|
||||||
|
expected_errors = [str(expected_errors)]
|
||||||
|
if "request_body_schema" in closure_locals or hasattr(
|
||||||
|
f, "_request_body_schema"
|
||||||
|
):
|
||||||
|
# Body type is known through method decorator
|
||||||
|
obj = closure_locals.get(
|
||||||
|
"request_body_schema",
|
||||||
|
getattr(f, "_request_body_schema", {}),
|
||||||
|
)
|
||||||
|
if obj.get("type") in ["object", "array"]:
|
||||||
|
# We only allow object and array bodies
|
||||||
|
# To prevent type name collision keep module name part of the name
|
||||||
|
typ_name = (
|
||||||
|
"".join([x.title() for x in path_resource_names])
|
||||||
|
+ func.__name__.title()
|
||||||
|
+ (f"_{min_ver.replace('.', '')}" if min_ver else "")
|
||||||
|
)
|
||||||
|
comp_schema = openapi_spec.components.schemas.setdefault(
|
||||||
|
typ_name,
|
||||||
|
self._sanitize_schema(
|
||||||
|
copy.deepcopy(obj),
|
||||||
|
start_version=start_version,
|
||||||
|
end_version=end_version,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if min_ver:
|
||||||
|
if not comp_schema.openstack:
|
||||||
|
comp_schema.openstack = {}
|
||||||
|
comp_schema.openstack["min-ver"] = min_ver
|
||||||
|
if max_ver:
|
||||||
|
if not comp_schema.openstack:
|
||||||
|
comp_schema.openstack = {}
|
||||||
|
comp_schema.openstack["max-ver"] = max_ver
|
||||||
|
if mode == "action":
|
||||||
|
if not comp_schema.openstack:
|
||||||
|
comp_schema.openstack = {}
|
||||||
|
comp_schema.openstack["action-name"] = action_name
|
||||||
|
|
||||||
|
ref_name = f"#/components/schemas/{typ_name}"
|
||||||
|
body_schemas.append(ref_name)
|
||||||
|
|
||||||
|
if "response_body_schema" in closure_locals or hasattr(
|
||||||
|
f, "_response_body_schema"
|
||||||
|
):
|
||||||
|
# Response type is known through method decorator - PERFECT
|
||||||
|
obj = closure_locals.get(
|
||||||
|
"response_body_schema",
|
||||||
|
getattr(f, "_response_body_schema", {}),
|
||||||
|
)
|
||||||
|
response_body_schema = obj
|
||||||
|
if "query_params_schema" in closure_locals or hasattr(
|
||||||
|
f, "_request_query_schema"
|
||||||
|
):
|
||||||
|
obj = closure_locals.get(
|
||||||
|
"query_params_schema",
|
||||||
|
getattr(f, "_request_query_schema", {}),
|
||||||
|
)
|
||||||
|
query_params_versions.append((obj, min_ver, max_ver))
|
||||||
|
|
||||||
|
f = f.__wrapped__
|
||||||
|
|
||||||
|
return (
|
||||||
|
query_params_versions,
|
||||||
|
body_schemas,
|
||||||
|
response_body_schema,
|
||||||
|
expected_errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _convert_wsme_to_jsonschema(body_spec):
|
def _convert_wsme_to_jsonschema(body_spec):
|
||||||
"""Convert WSME type description to JsonSchema"""
|
"""Convert WSME type description to JsonSchema"""
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
import copy
|
||||||
import inspect
|
import inspect
|
||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
import logging
|
import logging
|
||||||
@ -35,6 +36,7 @@ from codegenerator.openapi.keystone_schemas import role
|
|||||||
from codegenerator.openapi.keystone_schemas import service
|
from codegenerator.openapi.keystone_schemas import service
|
||||||
from codegenerator.openapi.keystone_schemas import user
|
from codegenerator.openapi.keystone_schemas import user
|
||||||
from codegenerator.openapi.utils import merge_api_ref_doc
|
from codegenerator.openapi.utils import merge_api_ref_doc
|
||||||
|
from codegenerator.openapi.utils import rst_to_md
|
||||||
|
|
||||||
|
|
||||||
class KeystoneGenerator(OpenStackServerSourceBase):
|
class KeystoneGenerator(OpenStackServerSourceBase):
|
||||||
@ -151,10 +153,6 @@ class KeystoneGenerator(OpenStackServerSourceBase):
|
|||||||
for route in self.router.iter_rules():
|
for route in self.router.iter_rules():
|
||||||
if route.rule.startswith("/static"):
|
if route.rule.startswith("/static"):
|
||||||
continue
|
continue
|
||||||
# if not route.rule.startswith("/v3/domains"):
|
|
||||||
# continue
|
|
||||||
if "/credentials/OS-EC2" in route.rule:
|
|
||||||
continue
|
|
||||||
|
|
||||||
self._process_route(route, openapi_spec)
|
self._process_route(route, openapi_spec)
|
||||||
|
|
||||||
@ -358,25 +356,61 @@ class KeystoneGenerator(OpenStackServerSourceBase):
|
|||||||
path,
|
path,
|
||||||
method,
|
method,
|
||||||
)
|
)
|
||||||
if method in ["PUT", "POST", "PATCH"]:
|
doc = inspect.getdoc(func)
|
||||||
# This is clearly a modification operation but we know nothing about request
|
if doc and not operation_spec.description:
|
||||||
schema_name = (
|
doc = rst_to_md(doc)
|
||||||
"".join([x.title() for x in path_resource_names])
|
operation_spec.description = LiteralScalarString(doc)
|
||||||
+ method.title()
|
|
||||||
+ "Request"
|
|
||||||
)
|
|
||||||
|
|
||||||
(schema_ref, mime_type) = self._get_schema_ref(
|
query_params_versions = []
|
||||||
|
body_schemas = []
|
||||||
|
expected_errors = ["404"]
|
||||||
|
response_code = None
|
||||||
|
start_version = None
|
||||||
|
end_version = None
|
||||||
|
deser_schema: dict = {}
|
||||||
|
ser_schema: dict = {}
|
||||||
|
|
||||||
|
(
|
||||||
|
query_params_versions,
|
||||||
|
body_schemas,
|
||||||
|
ser_schema,
|
||||||
|
expected_errors,
|
||||||
|
) = self._process_decorators(
|
||||||
|
func,
|
||||||
|
path_resource_names,
|
||||||
openapi_spec,
|
openapi_spec,
|
||||||
schema_name,
|
method,
|
||||||
description=f"Request of the {operation_spec.operationId} operation",
|
start_version,
|
||||||
|
end_version,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
if schema_ref:
|
if query_params_versions:
|
||||||
content = operation_spec.requestBody = {"content": {}}
|
so = sorted(
|
||||||
content["content"][mime_type] = {
|
query_params_versions,
|
||||||
"schema": {"$ref": schema_ref}
|
key=lambda d: (
|
||||||
}
|
tuple(map(int, d[1].split("."))) if d[1] else (0, 0)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for data, min_ver, max_ver in so:
|
||||||
|
self.process_query_parameters(
|
||||||
|
openapi_spec,
|
||||||
|
operation_spec,
|
||||||
|
path_resource_names,
|
||||||
|
data,
|
||||||
|
min_ver,
|
||||||
|
max_ver,
|
||||||
|
)
|
||||||
|
|
||||||
|
if method in ["PUT", "POST", "PATCH"]:
|
||||||
|
self.process_body_parameters(
|
||||||
|
openapi_spec,
|
||||||
|
operation_spec,
|
||||||
|
path_resource_names,
|
||||||
|
body_schemas,
|
||||||
|
None,
|
||||||
|
method,
|
||||||
|
)
|
||||||
|
|
||||||
responses_spec = operation_spec.responses
|
responses_spec = operation_spec.responses
|
||||||
# Errors
|
# Errors
|
||||||
@ -420,6 +454,7 @@ class KeystoneGenerator(OpenStackServerSourceBase):
|
|||||||
openapi_spec,
|
openapi_spec,
|
||||||
schema_name,
|
schema_name,
|
||||||
description=f"Response of the {operation_spec.operationId} operation",
|
description=f"Response of the {operation_spec.operationId} operation",
|
||||||
|
schema_def=ser_schema,
|
||||||
)
|
)
|
||||||
|
|
||||||
if schema_ref:
|
if schema_ref:
|
||||||
@ -486,7 +521,11 @@ class KeystoneGenerator(OpenStackServerSourceBase):
|
|||||||
|
|
||||||
# Default
|
# Default
|
||||||
(ref, mime_type) = super()._get_schema_ref(
|
(ref, mime_type) = super()._get_schema_ref(
|
||||||
openapi_spec, name, description, action_name=action_name
|
openapi_spec,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
schema_def=schema_def,
|
||||||
|
action_name=action_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
return (ref, mime_type)
|
return (ref, mime_type)
|
||||||
|
@ -170,7 +170,7 @@ def _get_schema_ref(
|
|||||||
TypeSchema(**APPLICATION_CREDENTIAL_CREATE_SCHEMA),
|
TypeSchema(**APPLICATION_CREDENTIAL_CREATE_SCHEMA),
|
||||||
)
|
)
|
||||||
ref = f"#/components/schemas/{name}"
|
ref = f"#/components/schemas/{name}"
|
||||||
elif name in "UsersApplication_CredentialsPostResponse":
|
elif name == "UsersApplication_CredentialsPostResponse":
|
||||||
openapi_spec.components.schemas.setdefault(
|
openapi_spec.components.schemas.setdefault(
|
||||||
name,
|
name,
|
||||||
TypeSchema(**APPLICATION_CREDENTIAL_CREATE_RESPONSE_SCHEMA),
|
TypeSchema(**APPLICATION_CREDENTIAL_CREATE_RESPONSE_SCHEMA),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user