diff --git a/codegenerator/openapi/base.py b/codegenerator/openapi/base.py index bb7b249..0ed7de2 100644 --- a/codegenerator/openapi/base.py +++ b/codegenerator/openapi/base.py @@ -16,7 +16,6 @@ import datetime import enum import importlib import inspect -import itertools import jsonref import logging from pathlib import Path @@ -48,6 +47,25 @@ class Unset: return False +class QueryParamsSchema: + schema: dict[Any, Any] | None = None + min_version: str | None = None + max_version: str | None = None + + def __init__( + self, + schema: dict[Any, Any] | None, + min_version: str | None, + max_version: str | None, + ): + self.schema = schema + self.min_version = min_version + self.max_version = max_version + + def __hash(self): + return hash((self.min_version, self.max_version)) + + UNSET: Unset = Unset() @@ -464,6 +482,7 @@ class OpenStackServerSourceBase: ) elif action and hasattr(contr, action): # Normal REST operation without version bounds + (start_version, end_version) = (None, None) func = getattr(contr, action) # Get the path/op spec only when we have @@ -477,6 +496,11 @@ class OpenStackServerSourceBase: operation_spec.tags.extend(operation_tags) operation_spec.tags = list(set(operation_spec.tags)) + closure_vars = inspect.getclosurevars(func) + if closure_vars and closure_vars.nonlocals: + start_version = closure_vars.nonlocals.get("min_version") + end_version = closure_vars.nonlocals.get("max_version") + self.process_operation( func, openapi_spec, @@ -486,6 +510,8 @@ class OpenStackServerSourceBase: operation_name=action, method=method, path=path, + start_version=start_version, + end_version=end_version, ) elif action != "action" and action in controller_actions: # Normal REST operation without version bounds and present in @@ -718,8 +744,8 @@ class OpenStackServerSourceBase: ) action_name = None - query_params_versions = [] - body_schemas: list[str | None] | Unset = UNSET + query_params_versions: set[QueryParamsSchema] = set() + body_schemas: set[str | None] | Unset = UNSET expected_errors = ["404"] response_code = None # Version bound on an operation are set only when it is not an @@ -813,9 +839,9 @@ class OpenStackServerSourceBase: schema_name, TypeSchema(**body_schema) ) if body_schemas is UNSET: - body_schemas = [] - if isinstance(body_schemas, list): - body_schemas.append(f"#/components/schemas/{schema_name}") + body_schemas = set() + if isinstance(body_schemas, set): + body_schemas.add(f"#/components/schemas/{schema_name}") rsp_spec = getattr(fdef, "return_type", None) if rsp_spec: ser_schema = _convert_wsme_to_jsonschema(rsp_spec) @@ -839,18 +865,20 @@ class OpenStackServerSourceBase: so = sorted( query_params_versions, key=lambda d: ( - tuple(map(int, d[1].split("."))) if d[1] else (0, 0) + tuple(map(int, d.min_version.split("."))) + if d.min_version + else (0, 0) ), ) - for data, min_ver, max_ver in so: - if data: + for item in so: + if item.schema: self.process_query_parameters( openapi_spec, operation_spec, path_resource_names, - data, - min_ver, - max_ver, + item.schema, + item.min_version, + item.max_version, ) # if body_schemas or mode == "action": if method in ["PUT", "POST", "PATCH"]: @@ -1070,7 +1098,7 @@ class OpenStackServerSourceBase: schema_ref = f"#/components/schemas/{cont_schema_name}" else: # otherwise just use schema as body - schema_ref = body_schemas[0] + schema_ref = body_schemas.pop() elif body_schemas is not UNSET and 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 op_body = operation_spec.requestBody.setdefault("content", {}) @@ -1198,11 +1226,19 @@ class OpenStackServerSourceBase: ]: if not schema.openstack: schema.openstack = {} - schema.openstack["min-ver"] = start_version.get_string() + schema.openstack["min-ver"] = ( + start_version.get_string() + if hasattr(start_version, "get_string") + else start_version + ) if end_version and self._api_ver_major(end_version) not in ["0", 0]: if not schema.openstack: schema.openstack = {} - schema.openstack["max-ver"] = end_version.get_string() + schema.openstack["max-ver"] = ( + end_version.get_string() + if hasattr(end_version, "get_string") + else end_version + ) return schema def _get_param_ref( @@ -1341,32 +1377,48 @@ class OpenStackServerSourceBase: end_version, action_name: str | None = None, operation_name: str | None = None, - ): + ) -> tuple[ + set[QueryParamsSchema], + set[str | None] | Unset, + dict | Unset | None, + list[str], + ]: """Extract schemas from the decorated method.""" # Unwrap operation decorators to access all properties expected_errors: list[str] = [] - body_schemas: list[str | None] | Unset = UNSET - query_params_versions: list[tuple] = [] + body_schemas: set[str | None] | Unset = UNSET + query_params_versions: set[QueryParamsSchema] = set() response_body_schema: dict | Unset | None = UNSET f = func while hasattr(f, "__wrapped__"): closure = inspect.getclosurevars(f) closure_locals = closure.nonlocals - min_ver = closure_locals.get("min_version", start_version) + min_ver = ( + closure_locals.get("min_version", start_version) + or start_version + ) + if min_ver and not isinstance(min_ver, str): min_ver = ( min_ver.get_string() if hasattr(min_ver, "get_string") else str(min_ver) ) - max_ver = closure_locals.get("max_version", end_version) + if min_ver and not start_version: + start_version = min_ver + + max_ver = ( + closure_locals.get("max_version", end_version) or end_version + ) if max_ver and not isinstance(max_ver, str): max_ver = ( max_ver.get_string() if hasattr(max_ver, "get_string") else str(max_ver) ) + if max_ver and not end_version: + end_version = max_ver if "errors" in closure_locals: expected_errors = closure_locals["errors"] @@ -1389,11 +1441,12 @@ class OpenStackServerSourceBase: ) # body schemas are not UNSET anymore if body_schemas is UNSET: - body_schemas = [] + body_schemas = set() if obj is not None: 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 + # 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() @@ -1428,12 +1481,12 @@ class OpenStackServerSourceBase: comp_schema.openstack["action-name"] = action_name ref_name = f"#/components/schemas/{typ_name}" - if isinstance(body_schemas, list): - body_schemas.append(ref_name) + if isinstance(body_schemas, set): + body_schemas.add(ref_name) else: # register no-body - if isinstance(body_schemas, list): - body_schemas.append(None) + if isinstance(body_schemas, set): + body_schemas.add(None) if "response_body_schema" in closure_locals or hasattr( f, "_response_body_schema" @@ -1451,17 +1504,21 @@ class OpenStackServerSourceBase: "query_params_schema", getattr(f, "_request_query_schema", {}), ) - query_params_versions.append((obj, min_ver, max_ver)) + query_params_versions.add( + QueryParamsSchema(obj, min_ver, max_ver) + ) elif "request_query_schema" in closure_locals: # Nova altered the decorator obj = closure_locals.get( "request_query_schema", getattr(f, "request_query_schema", {}), ) - query_params_versions.append((obj, min_ver, max_ver)) + query_params_versions.add( + QueryParamsSchema(obj, min_ver, max_ver) + ) if "validators" in closure_locals: validators = closure_locals.get("validators") - body_schemas = [] + body_schemas = set() if isinstance(validators, dict): for k, v in validators.items(): sig = inspect.signature(v) @@ -1498,8 +1555,9 @@ class OpenStackServerSourceBase: ) ref_name = f"#/components/schemas/{typ_name}" - if isinstance(body_schemas, list): - body_schemas.append(ref_name) + + if isinstance(body_schemas, set): + body_schemas.add(ref_name) f = f.__wrapped__ diff --git a/codegenerator/openapi/cinder.py b/codegenerator/openapi/cinder.py index bad289e..08351b8 100644 --- a/codegenerator/openapi/cinder.py +++ b/codegenerator/openapi/cinder.py @@ -66,13 +66,22 @@ class CinderV3Generator(OpenStackServerSourceBase): ] def _api_ver_major(self, ver): - return ver._ver_major + if hasattr(ver, "_ver_major"): + return ver._ver_major + elif isinstance(ver, str) and "." in ver: + return ver.split(".")[0] def _api_ver_minor(self, ver): - return ver._ver_minor + if hasattr(ver, "_ver_minor"): + return ver._ver_minor + elif isinstance(ver, str) and "." in ver: + return ver.split(".")[1] def _api_ver(self, ver): - return (ver._ver_major, ver._ver_minor) + if hasattr(ver, "_ver_major") and hasattr(ver, "_ver_minor"): + return (ver._ver_major, ver._ver_minor) + elif isinstance(ver, str): + return tuple(int(x) for x in ver.split(".")) def generate(self, target_dir, args): proc = Process(target=self._generate, args=[target_dir, args]) diff --git a/codegenerator/openapi/keystone.py b/codegenerator/openapi/keystone.py index 866c619..63243f4 100644 --- a/codegenerator/openapi/keystone.py +++ b/codegenerator/openapi/keystone.py @@ -22,6 +22,8 @@ from codegenerator.common.schema import PathSchema from codegenerator.common.schema import SpecSchema from codegenerator.common.schema import TypeSchema from codegenerator.openapi.base import OpenStackServerSourceBase +from codegenerator.openapi.base import QueryParamsSchema +from codegenerator.openapi.base import Unset from codegenerator.openapi.keystone_schemas import application_credential from codegenerator.openapi.keystone_schemas import auth from codegenerator.openapi.keystone_schemas import common @@ -378,13 +380,13 @@ class KeystoneGenerator(OpenStackServerSourceBase): doc = rst_to_md(doc) operation_spec.description = LiteralScalarString(doc) - query_params_versions = [] - body_schemas = [] + query_params_versions: set[QueryParamsSchema] = set() + body_schemas: set[str | None] | Unset | None = set() expected_errors = ["404"] response_code = None start_version = None end_version = None - ser_schema: dict | None = {} + ser_schema: dict | None | Unset = {} (query_params_versions, body_schemas, ser_schema, expected_errors) = ( self._process_decorators( @@ -403,18 +405,20 @@ class KeystoneGenerator(OpenStackServerSourceBase): so = sorted( query_params_versions, key=lambda d: ( - tuple(map(int, d[1].split("."))) if d[1] else (0, 0) + tuple(map(int, d.min_version.split("."))) + if d.min_version + else (0, 0) ), ) - for data, min_ver, max_ver in so: - if data: + for item in so: + if item.schema: self.process_query_parameters( openapi_spec, operation_spec, path_resource_names, - data, - min_ver, - max_ver, + item.schema, + item.min_version, + item.max_version, ) if method in ["PUT", "POST", "PATCH"]: diff --git a/codegenerator/openapi/nova.py b/codegenerator/openapi/nova.py index 85b71c7..3e3e3a4 100644 --- a/codegenerator/openapi/nova.py +++ b/codegenerator/openapi/nova.py @@ -38,13 +38,22 @@ class NovaGenerator(OpenStackServerSourceBase): } def _api_ver_major(self, ver): - return ver.ver_major + if hasattr(ver, "ver_major"): + return ver.ver_major + elif isinstance(ver, str) and "." in ver: + return ver.split(".")[0] def _api_ver_minor(self, ver): - return ver.ver_minor + if hasattr(ver, "ver_minor"): + return ver.ver_minor + elif isinstance(ver, str) and "." in ver: + return ver.split(".")[1] def _api_ver(self, ver): - return (ver.ver_major, ver.ver_minor) + if hasattr(ver, "ver_major") and hasattr(ver, "ver_minor"): + return (ver.ver_major, ver.ver_minor) + elif isinstance(ver, str): + return tuple(int(x) for x in ver.split(".")) def _generate(self, target_dir, args): from nova.api.openstack import api_version_request diff --git a/codegenerator/openapi/octavia.py b/codegenerator/openapi/octavia.py index b959447..6f188b7 100644 --- a/codegenerator/openapi/octavia.py +++ b/codegenerator/openapi/octavia.py @@ -20,10 +20,9 @@ import fixtures from codegenerator.common.schema import SpecSchema from codegenerator.common.schema import TypeSchema -from codegenerator.openapi.base import ( - OpenStackServerSourceBase, - _convert_wsme_to_jsonschema, -) +from codegenerator.openapi.base import _convert_wsme_to_jsonschema +from codegenerator.openapi.base import OpenStackServerSourceBase +from codegenerator.openapi.base import QueryParamsSchema from codegenerator.openapi.utils import merge_api_ref_doc from ruamel.yaml.scalarstring import LiteralScalarString @@ -1177,7 +1176,7 @@ class OctaviaGenerator(OpenStackServerSourceBase): } if qp: - query_params_versions.append((qp, None, None)) + query_params_versions.add(QueryParamsSchema(qp, None, None)) return ( query_params_versions, diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml index 5f1fb92..59509cc 100644 --- a/zuul.d/project.yaml +++ b/zuul.d/project.yaml @@ -12,6 +12,7 @@ - codegenerator-openapi-container-infrastructure-management-tips-with-api-ref - codegenerator-openapi-dns-tips-with-api-ref - codegenerator-openapi-identity-tips-with-api-ref + - codegenerator-openapi-identity-tips - codegenerator-openapi-image-tips-with-api-ref - codegenerator-openapi-key-manager-tips - codegenerator-openapi-load-balancing-tips-with-api-ref