Merge "Add support for explicit empty schemas"

This commit is contained in:
Zuul
2024-08-16 17:45:29 +00:00
committed by Gerrit Code Review
2 changed files with 89 additions and 59 deletions

View File

@@ -18,7 +18,7 @@ import importlib
import inspect import inspect
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Any, Callable from typing import Any, Callable, Literal
import re import re
from codegenerator.common.schema import ParameterSchema from codegenerator.common.schema import ParameterSchema
@@ -35,6 +35,16 @@ from wsme import types as wtypes
VERSION_RE = re.compile(r"[Vv][0-9\.]*") VERSION_RE = re.compile(r"[Vv][0-9\.]*")
# Workaround Python's lack of an undefined sentinel
# https://python-patterns.guide/python/sentinel-object/
class Unset:
def __bool__(self) -> Literal[False]:
return False
UNSET: Unset = Unset()
def get_referred_type_data(func, name: str): def get_referred_type_data(func, name: str):
"""Get python type object referred by the function """Get python type object referred by the function
@@ -893,9 +903,9 @@ class OpenStackServerSourceBase:
mode, mode,
action_name, action_name,
): ):
op_body = operation_spec.requestBody.setdefault("content", {})
mime_type: str = "application/json" mime_type: str = "application/json"
schema_name = None schema_name = None
schema_ref: str | Unset | None = None
# We should not modify path_resource_names of the caller # We should not modify path_resource_names of the caller
path_resource_names = path_resource_names.copy() path_resource_names = path_resource_names.copy()
# Create container schema with version discriminator # Create container schema with version discriminator
@@ -909,19 +919,24 @@ class OpenStackServerSourceBase:
if len(body_schemas) == 1: if len(body_schemas) == 1:
# There is only one body known at the moment # There is only one body known at the moment
if cont_schema_name in openapi_spec.components.schemas: # None is a special case with explicitly no body supported
# if we have already oneOf - add there if body_schemas[0] is not UNSET:
cont_schema = openapi_spec.components.schemas[cont_schema_name] if cont_schema_name in openapi_spec.components.schemas:
if cont_schema.oneOf and body_schemas[0] not in [ # if we have already oneOf - add there
x["$ref"] for x in cont_schema.oneOf cont_schema = openapi_spec.components.schemas[
]: cont_schema_name
cont_schema.oneOf.append({"$ref": body_schemas[0]}) ]
schema_ref = f"#/components/schemas/{cont_schema_name}" if cont_schema.oneOf and body_schemas[0] not in [
else: x["$ref"] for x in cont_schema.oneOf
# otherwise just use schema as body ]:
schema_ref = body_schemas[0] cont_schema.oneOf.append({"$ref": body_schemas[0]})
schema_ref = f"#/components/schemas/{cont_schema_name}"
else:
# otherwise just use schema as body
schema_ref = body_schemas[0]
elif len(body_schemas) > 1: elif len(body_schemas) > 1:
# We may end up here multiple times if we have versioned operation. In this case merge to what we have already # We may end up here multiple times if we have versioned operation. In this case merge to what we have already
op_body = operation_spec.requestBody.setdefault("content", {})
old_schema = op_body.get(mime_type, {}).get("schema", {}) old_schema = op_body.get(mime_type, {}).get("schema", {})
old_ref = ( old_ref = (
old_schema.ref old_schema.ref
@@ -984,16 +999,20 @@ class OpenStackServerSourceBase:
) )
if mode == "action": if mode == "action":
op_body = operation_spec.requestBody.setdefault("content", {})
js_content = op_body.setdefault(mime_type, {}) js_content = op_body.setdefault(mime_type, {})
body_schema = js_content.setdefault("schema", {}) body_schema = js_content.setdefault("schema", {})
one_of = body_schema.setdefault("oneOf", []) one_of = body_schema.setdefault("oneOf", [])
if schema_ref not in [x.get("$ref") for x in one_of]: if schema_ref and schema_ref not in [
x.get("$ref") for x in one_of
]:
one_of.append({"$ref": schema_ref}) one_of.append({"$ref": schema_ref})
os_ext = body_schema.setdefault("x-openstack", {}) os_ext = body_schema.setdefault("x-openstack", {})
os_ext["discriminator"] = "action" os_ext["discriminator"] = "action"
if cont_schema and action_name: if cont_schema and action_name:
cont_schema.openstack["action-name"] = action_name cont_schema.openstack["action-name"] = action_name
elif schema_ref: elif schema_ref is not None and schema_ref is not UNSET:
op_body = operation_spec.requestBody.setdefault("content", {})
js_content = op_body.setdefault(mime_type, {}) js_content = op_body.setdefault(mime_type, {})
body_schema = js_content.setdefault("schema", {}) body_schema = js_content.setdefault("schema", {})
operation_spec.requestBody["content"][mime_type]["schema"] = ( operation_spec.requestBody["content"][mime_type]["schema"] = (
@@ -1127,12 +1146,13 @@ class OpenStackServerSourceBase:
"type": "object", "type": "object",
"description": LiteralScalarString(description), "description": LiteralScalarString(description),
} }
schema = openapi_spec.components.schemas.setdefault( if schema_def is not UNSET:
name, schema = openapi_spec.components.schemas.setdefault(
TypeSchema( name,
**schema_def, TypeSchema(
), **schema_def,
) ),
)
if action_name: if action_name:
if not schema.openstack: if not schema.openstack:
@@ -1180,9 +1200,9 @@ class OpenStackServerSourceBase:
"""Extract schemas from the decorated method.""" """Extract schemas from the decorated method."""
# Unwrap operation decorators to access all properties # Unwrap operation decorators to access all properties
expected_errors: list[str] = [] expected_errors: list[str] = []
body_schemas: list[str] = [] body_schemas: list[str | Unset] = []
query_params_versions: list[tuple] = [] query_params_versions: list[tuple] = []
response_body_schema: dict | None = None response_body_schema: dict | Unset | None = UNSET
f = func f = func
while hasattr(f, "__wrapped__"): while hasattr(f, "__wrapped__"):
@@ -1214,38 +1234,47 @@ class OpenStackServerSourceBase:
"request_body_schema", "request_body_schema",
getattr(f, "_request_body_schema", {}), getattr(f, "_request_body_schema", {}),
) )
if obj.get("type") in ["object", "array"]: if obj is not None:
# We only allow object and array bodies if obj.get("type") in ["object", "array"]:
# To prevent type name collision keep module name part of the name # We only allow object and array bodies
typ_name = ( # To prevent type name collision keep module name part of the name
"".join([x.title() for x in path_resource_names]) typ_name = (
+ func.__name__.title() "".join([x.title() for x in path_resource_names])
+ (f"_{min_ver.replace('.', '')}" if min_ver else "") + func.__name__.title()
) + (
comp_schema = openapi_spec.components.schemas.setdefault( f"_{min_ver.replace('.', '')}"
typ_name, if min_ver
self._sanitize_schema( else ""
copy.deepcopy(obj), )
start_version=start_version, )
end_version=end_version, 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 min_ver:
if not comp_schema.openstack: if not comp_schema.openstack:
comp_schema.openstack = {} comp_schema.openstack = {}
comp_schema.openstack["min-ver"] = min_ver comp_schema.openstack["min-ver"] = min_ver
if max_ver: if max_ver:
if not comp_schema.openstack: if not comp_schema.openstack:
comp_schema.openstack = {} comp_schema.openstack = {}
comp_schema.openstack["max-ver"] = max_ver comp_schema.openstack["max-ver"] = max_ver
if mode == "action": if mode == "action":
if not comp_schema.openstack: if not comp_schema.openstack:
comp_schema.openstack = {} comp_schema.openstack = {}
comp_schema.openstack["action-name"] = action_name comp_schema.openstack["action-name"] = action_name
ref_name = f"#/components/schemas/{typ_name}" ref_name = f"#/components/schemas/{typ_name}"
body_schemas.append(ref_name) body_schemas.append(ref_name)
else:
body_schemas.append(UNSET)
if "response_body_schema" in closure_locals or hasattr( if "response_body_schema" in closure_locals or hasattr(
f, "_response_body_schema" f, "_response_body_schema"
@@ -1255,7 +1284,10 @@ class OpenStackServerSourceBase:
"response_body_schema", "response_body_schema",
getattr(f, "_response_body_schema", {}), getattr(f, "_response_body_schema", {}),
) )
response_body_schema = obj if obj is not None:
response_body_schema = obj
else:
response_body_schema = UNSET
if "query_params_schema" in closure_locals or hasattr( if "query_params_schema" in closure_locals or hasattr(
f, "_request_query_schema" f, "_request_query_schema"
): ):

View File

@@ -10,7 +10,6 @@
# 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
@@ -22,7 +21,7 @@ from codegenerator.common.schema import ParameterSchema
from codegenerator.common.schema import PathSchema from codegenerator.common.schema import PathSchema
from codegenerator.common.schema import SpecSchema from codegenerator.common.schema import SpecSchema
from codegenerator.common.schema import TypeSchema from codegenerator.common.schema import TypeSchema
from codegenerator.openapi.base import OpenStackServerSourceBase from codegenerator.openapi.base import OpenStackServerSourceBase, UNSET
from codegenerator.openapi.keystone_schemas import application_credential from codegenerator.openapi.keystone_schemas import application_credential
from codegenerator.openapi.keystone_schemas import auth from codegenerator.openapi.keystone_schemas import auth
from codegenerator.openapi.keystone_schemas import common from codegenerator.openapi.keystone_schemas import common
@@ -368,7 +367,7 @@ class KeystoneGenerator(OpenStackServerSourceBase):
start_version = None start_version = None
end_version = None end_version = None
deser_schema: dict = {} deser_schema: dict = {}
ser_schema: dict = {} ser_schema: dict | None = {}
( (
query_params_versions, query_params_versions,
@@ -512,7 +511,7 @@ class KeystoneGenerator(OpenStackServerSourceBase):
# Invoke modularized schema _get_schema_ref # Invoke modularized schema _get_schema_ref
for resource_mod in self.RESOURCE_MODULES: for resource_mod in self.RESOURCE_MODULES:
hook = getattr(resource_mod, "_get_schema_ref", None) hook = getattr(resource_mod, "_get_schema_ref", None)
if hook: if hook and schema_def is not UNSET:
(ref, mime_type, matched) = hook( (ref, mime_type, matched) = hook(
openapi_spec, name, description, schema_def, action_name openapi_spec, name, description, schema_def, action_name
) )
@@ -527,5 +526,4 @@ class KeystoneGenerator(OpenStackServerSourceBase):
schema_def=schema_def, schema_def=schema_def,
action_name=action_name, action_name=action_name,
) )
return (ref, mime_type) return (ref, mime_type)