Consume decorated Keystone methods
We started decorating Keystone with schema validation similarly to other services. Since Keystone uses Flask and thus we do not use base processing split the processing into separate method and call it. Change-Id: I4e70b85bb16a40cb3dff0d5ddc6004aabe0d1c62
This commit is contained in:
parent
fdf8bda6be
commit
d038d9d8d1
@ -647,88 +647,20 @@ class OpenStackServerSourceBase:
|
||||
if action_name:
|
||||
operation_name = action_name
|
||||
|
||||
# Unwrap operation decorators to access all properties
|
||||
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", {}),
|
||||
)
|
||||
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__
|
||||
(
|
||||
query_params_versions,
|
||||
body_schemas,
|
||||
response_body_schema,
|
||||
expected_errors,
|
||||
) = self._process_decorators(
|
||||
func,
|
||||
path_resource_names,
|
||||
openapi_spec,
|
||||
mode,
|
||||
start_version,
|
||||
end_version,
|
||||
action_name,
|
||||
)
|
||||
|
||||
if hasattr(func, "_wsme_definition"):
|
||||
fdef = getattr(func, "_wsme_definition")
|
||||
@ -786,6 +718,8 @@ class OpenStackServerSourceBase:
|
||||
operation_name,
|
||||
)
|
||||
|
||||
if ser_schema and not response_body_schema:
|
||||
response_body_schema = ser_schema
|
||||
responses_spec = operation_spec.responses
|
||||
for error in expected_errors:
|
||||
responses_spec.setdefault(str(error), dict(description="Error"))
|
||||
@ -830,7 +764,7 @@ class OpenStackServerSourceBase:
|
||||
if not action_name
|
||||
else f"Response of the {operation_spec.operationId}:{action_name} action"
|
||||
), # noqa
|
||||
schema_def=ser_schema,
|
||||
schema_def=response_body_schema,
|
||||
action_name=action_name,
|
||||
)
|
||||
|
||||
@ -910,7 +844,8 @@ class OpenStackServerSourceBase:
|
||||
**copy.deepcopy(spec["items"])
|
||||
)
|
||||
else:
|
||||
raise RuntimeError("Error")
|
||||
param_attrs["schema"] = TypeSchema(**copy.deepcopy(spec))
|
||||
param_attrs["description"] = spec.get("description")
|
||||
if min_ver:
|
||||
os_ext = param_attrs.setdefault("x-openstack", {})
|
||||
os_ext["min-ver"] = min_ver
|
||||
@ -1218,6 +1153,113 @@ class OpenStackServerSourceBase:
|
||||
response_code = "200"
|
||||
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):
|
||||
"""Convert WSME type description to JsonSchema"""
|
||||
|
@ -10,6 +10,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
import copy
|
||||
import inspect
|
||||
from multiprocessing import Process
|
||||
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 user
|
||||
from codegenerator.openapi.utils import merge_api_ref_doc
|
||||
from codegenerator.openapi.utils import rst_to_md
|
||||
|
||||
|
||||
class KeystoneGenerator(OpenStackServerSourceBase):
|
||||
@ -151,10 +153,6 @@ class KeystoneGenerator(OpenStackServerSourceBase):
|
||||
for route in self.router.iter_rules():
|
||||
if route.rule.startswith("/static"):
|
||||
continue
|
||||
# if not route.rule.startswith("/v3/domains"):
|
||||
# continue
|
||||
if "/credentials/OS-EC2" in route.rule:
|
||||
continue
|
||||
|
||||
self._process_route(route, openapi_spec)
|
||||
|
||||
@ -358,26 +356,62 @@ class KeystoneGenerator(OpenStackServerSourceBase):
|
||||
path,
|
||||
method,
|
||||
)
|
||||
doc = inspect.getdoc(func)
|
||||
if doc and not operation_spec.description:
|
||||
doc = rst_to_md(doc)
|
||||
operation_spec.description = LiteralScalarString(doc)
|
||||
|
||||
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,
|
||||
method,
|
||||
start_version,
|
||||
end_version,
|
||||
None,
|
||||
)
|
||||
|
||||
if query_params_versions:
|
||||
so = sorted(
|
||||
query_params_versions,
|
||||
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"]:
|
||||
# This is clearly a modification operation but we know nothing about request
|
||||
schema_name = (
|
||||
"".join([x.title() for x in path_resource_names])
|
||||
+ method.title()
|
||||
+ "Request"
|
||||
)
|
||||
|
||||
(schema_ref, mime_type) = self._get_schema_ref(
|
||||
self.process_body_parameters(
|
||||
openapi_spec,
|
||||
schema_name,
|
||||
description=f"Request of the {operation_spec.operationId} operation",
|
||||
operation_spec,
|
||||
path_resource_names,
|
||||
body_schemas,
|
||||
None,
|
||||
method,
|
||||
)
|
||||
|
||||
if schema_ref:
|
||||
content = operation_spec.requestBody = {"content": {}}
|
||||
content["content"][mime_type] = {
|
||||
"schema": {"$ref": schema_ref}
|
||||
}
|
||||
|
||||
responses_spec = operation_spec.responses
|
||||
# Errors
|
||||
for error in ["403", "404"]:
|
||||
@ -420,6 +454,7 @@ class KeystoneGenerator(OpenStackServerSourceBase):
|
||||
openapi_spec,
|
||||
schema_name,
|
||||
description=f"Response of the {operation_spec.operationId} operation",
|
||||
schema_def=ser_schema,
|
||||
)
|
||||
|
||||
if schema_ref:
|
||||
@ -486,7 +521,11 @@ class KeystoneGenerator(OpenStackServerSourceBase):
|
||||
|
||||
# Default
|
||||
(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)
|
||||
|
@ -170,7 +170,7 @@ def _get_schema_ref(
|
||||
TypeSchema(**APPLICATION_CREDENTIAL_CREATE_SCHEMA),
|
||||
)
|
||||
ref = f"#/components/schemas/{name}"
|
||||
elif name in "UsersApplication_CredentialsPostResponse":
|
||||
elif name == "UsersApplication_CredentialsPostResponse":
|
||||
openapi_spec.components.schemas.setdefault(
|
||||
name,
|
||||
TypeSchema(**APPLICATION_CREDENTIAL_CREATE_RESPONSE_SCHEMA),
|
||||
|
Loading…
x
Reference in New Issue
Block a user