text: from inspect import getmro
  from typing import Union, List, Dict, Any
  import re
  from dataclasses import fields
  
  from sanic import response
  from sanic.request import Request
  
  from dataclasses_jsonschema import JsonSchemaMixin, SchemaType
  
  from yrest.tree import Tree
  
  class OpenApi():
    def v3(self):
      result = {"openapi": "3.0.1"}
  
      c_keys = list(self.config.keys())
      if "OA_INFO" in c_keys:
        result["info"] = self.config["OA_INFO"]
  
      if "OA_SERVER_DESCRIPTION" in c_keys:
        result["servers"] = [{"url": self.config["SERVER_NAME"], "description": self.config["OA_SERVER_DESCRIPTION"]}]
  
      result["paths"] = self._paths()
  
      schemas = self._correct_schemas(JsonSchemaMixin.all_json_schemas(schema_type = SchemaType.OPENAPI_3))
      result["components"] = {"x-root": self._root_model.__name__, "schemas": schemas}
      if hasattr(self, "_params"):
        result["components"]["parameters"] = self._params
  
      return result
  
    def _paths(self):
      regex = re.compile("\/new\/\w+$")
      regexsub = re.compile("\/create_\w+$")
  
      result = self._own_path()
      for name, data in self._introspection.items():
        paths = {}
        model = getattr(self._models, name)
        for e_name, endpoint in data.items():
          if e_name != "factories":
            for path, path_data in self._path(model, e_name, endpoint).items():
              if not regex.search(path) and not regexsub.search(path):
                for verb, verb_data in path_data.items():
                  if path not in paths:
                    paths[path] = {}
                  paths[path][verb] = verb_data
  
        if "factories" in data.keys():
          result.update(self._factories(model, data))
  
        if paths:
          result.update(paths)
  
      return result
  
    def _path(self, model: Tree, e_name: str, e_data: Dict[str, Union[str, Dict]]) -> Dict[str, Union[str, Dict]]:
      regex = re.compile("/{\w+_Path}")
      path = {}
      verb = e_data["verb"].lower()
      e_keys = e_data.keys()
      for url in e_data["urls"]:
        p = {}
        p[url] = {}
        p[url][verb] = {}
  
        if regex.match(url):
          p[url][verb]["operationId"] = f"{model.__name__}/{e_name}"
          p[url][verb]["parameters"] = self._parameters(model, url)
        else:
          p[url][verb]["operationId"] = f"Root/{e_name}"
  
        if "can_crash" in e_keys or "produces" in e_keys:
          p[url][verb]["responses"] = {}
  
          if "produces" in e_keys:
            p[url][verb]["responses"][200] = {}
            if "description" in e_data:
              p[url][verb]["responses"][200]["description"] = e_data["description"]
  
            p[url][verb]["responses"][200].update(self._content(e_data["produces"]))
  
          if "can_crash" in e_keys:
            for error in e_data["can_crash"].values():
              p[url][verb]["responses"][error["code"]] = {}
              if "description" in error:
                p[url][verb]["responses"][error["code"]]["description"] = error["description"]
  
              p[url][verb]["responses"][error["code"]].update(self._content(error["returns"]))
  
        if "consumes" in e_keys:
          p[url][verb]["requestBody"] = self._content(e_data["consumes"])
  
        path.update(p)
  
      return path
  
    def _factories(self, model: Tree, data):
      okResultContent = self._content(getattr(self._models, "OkResult"))["content"]
      errorMessageContent = self._content(getattr(self._models, "ErrorMessage"))["content"]
  
      urls = []
      if model == self._root_model:
        urls.append("/")
  
      if model != self._root_model or model.__name__ in data["factories"]:
        urls.append(f"/{{{model.__name__}_Path}}/")
  
      regex = re.compile("/{\w+_Path}")
      paths = {}
      for factory in data["factories"]:
        fact = factory.lower()
        for url in urls:
          path = f"{url}new/{fact}"
          paths[path] = {"post": {}}
          if regex.match(url):
            paths[path]["post"]["operationId"] = f"{model.__name__}/create_{fact}"
            paths[path]["post"]["parameters"] = self._parameters(model, url)
          else:
            paths[path]["post"]["operationId"] =  f"Root/create_{fact}"
          from pprint import pprint
          if f"create_{fact}" in data.keys():
            paths[path]["post"]["requestBody"] = self._content(data[f"create_{fact}"]["consumes"])
          else:
            paths[path]["post"]["requestBody"] = self._content(getattr(self._models, factory))
  
          paths[path]["post"]["responses"] = {
            200: {
              "description": f"Returns the data of the new {fact}",
              "content": okResultContent
            },
            400: {
              "description": f"Returns the errors if the {fact} can't be created with the provided data",
              "content": errorMessageContent
            },
            401: {
              "description": f"Returns Unauthorized if the actor is not allowed to perform the creation of the {fact}",
              "content": errorMessageContent
            },
            409: {
              "description": f"Returns an error if there is already a model with the same url",
              "content": errorMessageContent
            }
          }
  
      return paths
  
    def _own_path(self) -> Dict[str, Union[str, Dict]]:
      result = {
        "/openapi": {
          "get": {
            "responses": {
              "200": {
                "description": "Returns the app's OpenAPI definition",
                "content": {
                  "application/json": {
                    "schema": {"type": "object"}
                  }
                }
              }
            }
          }
        }
      }
      return result
  
    def _parameters(self, model: Tree, url: str):
      if not hasattr(self, "_params"):
        self._params = {}
  
      name = model.__name__
      param_name = f"{name}_Path"
  
      if param_name not in self._params.keys():
        path = {
          "name": param_name,
          "in": "path",
          "description": f"The URL of the {name} with out the first slash",
          "required": True,
          "schema": {"type": "string"}
        }
  
        self._params[param_name] = path
  
      return [self._ref(param_name, "parameters")]
  
    def _content(self, model: Tree, mime: str = "application/json"):
      content = {}
      content[mime] = {"schema": self._ref(model[1].__name__ if isinstance(model, tuple) else model.__name__)}
      return {"content": content}
  
    def _ref(self, model: Tree, context: str = "schemas"):
      return {"$ref": f"#/components/{context}/{model}"}
  
    def _correct_schemas(self, schemas):
      models = {model.__name__: model for model in JsonSchemaMixin.__subclasses__()}
      needed_props = {"maximum", "exclusiveMaximum", "minimum", "exclusiveMinimum", "maxLength", "minLength"}
  
      for schema in schemas.keys():
        if hasattr(models[schema], "__x_schema__"):
          schemas[schema].update({f"x-{key}": value for key, value in models[schema].__x_schema__.items()})
          schemas[schema][f"x-features"] = [feat.__name__ for feat in getmro(models[schema])]
  
        for field in fields(models[schema]):
          if hasattr(field, "metadata") and field.metadata :
            for prop in needed_props & set(field.metadata.keys()):
              schemas[schema]["properties"][field.name][prop] = field.metadata[prop]
  
            if "model" in field.metadata:
              schemas[schema]["properties"][field.name]["x-model"] = field.metadata["model"]
  
      return schemas
  
    def openapi(self, request: Request) -> Dict[str, str]:
      """Returns tha API's OpenAPI definition"""
      return response.json(self.v3())
  