def test_schema_with_ref_prefix(): class Foo(BaseModel): a: str class Bar(BaseModel): b: Foo class Baz(BaseModel): c: Bar model_schema = schema([Bar, Baz], ref_prefix='#/components/schemas/') # OpenAPI style assert model_schema == { 'definitions': { 'Baz': { 'title': 'Baz', 'type': 'object', 'properties': {'c': {'$ref': '#/components/schemas/Bar'}}, 'required': ['c'], }, 'Bar': { 'title': 'Bar', 'type': 'object', 'properties': {'b': {'$ref': '#/components/schemas/Foo'}}, 'required': ['b'], }, 'Foo': { 'title': 'Foo', 'type': 'object', 'properties': {'a': {'title': 'A', 'type': 'string'}}, 'required': ['a'], }, } }
def schema(self) -> dict: return schema( self.models, by_alias=self.by_alias, title=self.title, description=self.description, ref_prefix=self.ref_prefix, )
def construct_open_api_with_schema_class( open_api: OpenAPI, schema_classes: List[Type[PydanticType]] = None, scan_for_pydantic_schema_reference: bool = True, by_alias: bool = True, ) -> OpenAPI: """ Construct a new OpenAPI object, with the use of pydantic classes to produce JSON schemas :param open_api: the base `OpenAPI` object :param schema_classes: pydanitic classes that their schema will be used "#/components/schemas" values :param scan_for_pydantic_schema_reference: flag to indicate if scanning for `PydanticSchemaReference` class is needed for "#/components/schemas" value updates :param by_alias: construct schema by alias (default is True) :return: new OpenAPI object with "#/components/schemas" values updated. If there is no update in "#/components/schemas" values, the original `open_api` will be returned. """ new_open_api: OpenAPI = open_api.copy(deep=True) if scan_for_pydantic_schema_reference: extracted_schema_classes = _handle_pydantic_schema(new_open_api) if schema_classes: schema_classes = list( {*schema_classes, *_handle_pydantic_schema(new_open_api)}) else: schema_classes = extracted_schema_classes if not schema_classes: return open_api schema_classes.sort(key=lambda x: x.__name__) logging.debug(f"schema_classes{schema_classes}") # update new_open_api with new #/components/schemas schema_definitions = schema(schema_classes, by_alias=by_alias, ref_prefix=ref_prefix) if not new_open_api.components: new_open_api.components = Components() if new_open_api.components.schemas: for existing_key in new_open_api.components.schemas: if existing_key in schema_definitions.get("definitions"): logging.warning( f'"{existing_key}" already exists in {ref_prefix}. ' f'The value of "{ref_prefix}{existing_key}" will be overwritten.' ) new_open_api.components.schemas.update({ key: Schema.parse_obj(schema_dict) for key, schema_dict in schema_definitions.get( "definitions").items() }) else: new_open_api.components.schemas = { key: Schema.parse_obj(schema_dict) for key, schema_dict in schema_definitions.get( "definitions").items() } return new_open_api
def test_readonly(read_only_field): clazz, fields = read_only_field s = schema([clazz])["definitions"][clazz.__name__] for prop in s["properties"]: if prop in fields: assert "readOnly" in s["properties"][ prop] and s["properties"][prop]["readOnly"] is True else: assert "readOnly" not in s["properties"][prop]
def get_schemas_inheritance(model_cls): """This method modifies the default OpenAPI from Pydantic. It adds referenced values to subclasses using allOf field as explained in this post: https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism """ # list of top level class names that we should stop at stoppage = set( ['NoExtraBaseModel', 'ModelMetaclass', 'BaseModel', 'object', 'Enum']) model_name_map = get_model_mapper(model_cls, stoppage, full=True, include_enum=False) # get the standard OpenAPI schema for Pydantic for all the new objects ref_prefix = '#/components/schemas/' schemas = \ schema(model_name_map.values(), ref_prefix=ref_prefix)['definitions'] # collect updated objects updated_schemas = {} # iterate through all the data models # find the ones which are subclassed and updated them based on the properties of # baseclasses. for name in schemas.keys(): # find the class from class name try: main_cls = model_name_map[name] except KeyError: # enum objects are not included. if 'enum' in schemas[name]: continue raise KeyError(f'{name} key not found.') top_classes = [] for cls in type.mro(main_cls): if cls.__name__ in stoppage: break top_classes.append(cls) if len(top_classes) < 2: # this class is not a subclass print(f'\n{name} is not a subclass.') continue # set object inheritance updated_schema = set_inheritance(name, top_classes, schemas) updated_schemas[name] = updated_schema # replace updated schemas in original schema for name, value in updated_schemas.items(): schemas[name] = value return schemas
def get_openapi( *, schema_class, title: str = None, version: str = None, openapi_version: str = "3.0.2", description: str = None, ) -> Dict: """Return Queenbee Workflow Schema as an openapi compatible dictionary.""" open_api = dict(BASE_OPEN_API) open_api['openapi'] = openapi_version if title: open_api['info']['title'] = title if version: open_api['info']['version'] = version if description: open_api['info']['description'] = description definitions = schema([schema_class], ref_prefix='#/components/schemas/') # goes to tags tags = [] # goes to x-tagGroups['tags'] tag_names = [] schemas = definitions['definitions'] schema_names = list(schemas.keys()) schema_names.sort() for name in schema_names: model_name = '%s_model' % name.lower() tag_names.append(model_name) tag = { 'name': model_name, 'x-displayName': name, 'description': '<SchemaDefinition schemaRef=\"#/components/schemas/%s\" />\n' % name } tags.append(tag) tag_names.sort() open_api['tags'] = tags open_api['x-tagGroups'][0]['tags'] = tag_names open_api['components']['schemas'] = schemas return open_api
def get_openapi_components(self): components = OpenApiComponents() model_list = list(self.models_name.keys()) schemas = schema(model_list, title='Pydantic_Schemas')['definitions'] components.set_schemas(schemas) for name, security_dict in self.securitySchemas_index.items(): security_schema = OpenApiSecuritySchema() security_schema.type = security_dict['auth_type'] security_schema.flows = { "password": { "scopes": {}, "tokenUrl": self.token_url } } components.add_security_schemas(name, security_schema) return components
def main(): model_schema = schema( [ api_models.Todo, api_models.TodoOut, api_models.Instrument, api_models.InstrumentOut, api_models.InstrumentFilter, api_models.RetrieveSingle, api_models.RetrieveMultiple, api_models.Search, api_models.SignOut, ] )["definitions"] schema_body = { "openapi": "3.0.2", "info": {"title": "Instrument Inventory", "version": os.getenv("VERSION", "1")}, "components": {"schemas": model_schema}, } return success(schema_body)
def test_dictionary_field(additional_metadata_field): clazz, fields = additional_metadata_field s = schema([clazz])["definitions"][clazz.__name__] for field in fields: assert 'additionalProperties' not in s["properties"][field] assert 'default' not in s["properties"][field] assert s["properties"][field]['items'] == { "type": "object", "title": "Key-Value", "description": "A key-value pair", "default": [], "properties": { "key": { "type": "string" }, "value": { "type": "string" } } }
def test_dataclass(): @dataclass class Model: a: bool assert schema([Model]) == { 'definitions': { 'Model': { 'title': 'Model', 'type': 'object', 'properties': {'a': {'title': 'A', 'type': 'boolean'}}, 'required': ['a'], } } } assert model_schema(Model) == { 'title': 'Model', 'type': 'object', 'properties': {'a': {'title': 'A', 'type': 'boolean'}}, 'required': ['a'], }
# output-json import json from pydantic import BaseModel from pydantic.schema import schema class Foo(BaseModel): a: str = None class Model(BaseModel): b: Foo class Bar(BaseModel): c: int top_level_schema = schema([Model, Bar], title='My Schema') print(json.dumps(top_level_schema, indent=2))
def headers_form_schema(schema): headers = [] for name, prop in schema.schema()['properties'].items(): headers.append({"value": name, 'text': prop["title"], 'type': prop['type']}) return headers
def openapi(self): paths = {} info = { "title": self.api_title, "version": self.api_version, } schemas = [ ValidationErrorResponse, HttpErrorResponse, ] # these responses will be included with every request response in the # openapi default_responses = { "400": self._gen_content("ValidationErrorResponse", description="Bad request"), "401": { "description": "Unauthorized", }, "403": { "description": "Forbidden", }, "500": { "description": "Internal server error. Assume request failed. Please try again", }, } default_parameters = [ # { # "in": "header", # "name": "Accept-Language", # "description": "Request content in the specific language.", # "schema": { # "type": "string", # "default": default_language, # "enum": supported_language_codes, # }, # }, { "in": "header", "name": "Accept", "description": "Request use of a particular data serialization.", "schema": { "type": "string", "default": default_serializer, "enum": supported_serializers, }, }, ] content_type_parameter = { "in": "header", "name": "Content-Type", "description": "The data format that the request body is serialized in.", "required": True, "schema": { "type": "string", "enum": list(serializers.keys()) }, } method_parameters = { HttpMethod.PATCH: [ content_type_parameter, ], HttpMethod.GET: [], HttpMethod.DELETE: [], HttpMethod.PUT: [ content_type_parameter, ], HttpMethod.POST: [ content_type_parameter, ], } for rule in self.url_map.iter_rules(): if rule.endpoint in self.schema_metadata: rule_normalised = re.sub(r"<(?:\w+:)?(\w+)>", r"{\1}", rule.rule) if rule_normalised not in paths: paths[rule_normalised] = {} metadata = self.schema_metadata[rule.endpoint] sig = metadata["sig"] for method in rule.methods: if method in method_parameters: request_body = None if "body" in metadata: body = metadata["body"] schemas.append(body) request_body = self._gen_content(body.__name__, required=True) # request_body = { # 'required': True, # 'content': { # 'application/json': { # 'schema': { # '$ref': '#/definitions/' + body.__name__, # }, # }, # }, # } parameters = default_parameters.copy() parameters.extend(method_parameters.get(method)) for field, parameter in sig.parameters.items(): if field == 'body': # body is a keyword used for json body continue param = { "name": field, # 'description': ... } if field in rule.arguments: param["in"] = "path" param["required"] = True else: param["in"] = "query" if parameter.annotation: type_map = { "str": "string", "UUID": "string", "int": "integer", } assert parameter.annotation.__name__ != '_empty', 'declare parameter %s with a type notation in function declaration %s %s' % ( field, method, rule_normalised) param["schema"] = { "type": type_map.get( parameter.annotation.__name__, parameter.annotation.__name__), } if parameter.default: if isinstance(parameter.default, FieldInfo): param[ "description"] = parameter.default.description if parameter.default.default != Ellipsis: param[ "default"] = parameter.default.default elif parameter.default not in (inspect._empty, Ellipsis): param["default"] = parameter.default parameters.append(param) response = None if sig.return_annotation is not inspect._empty: schemas.append(sig.return_annotation) response = self._gen_content( sig.return_annotation.__name__) # response = { # 'description': '', # 'content': { # 'application/json': { # 'schema': { # '$ref': '#/definitions/' + sig.return_annotation.__name__, # }, # }, # }, # } else: response = { "description": "No content", } responses = default_responses.copy() response_code = metadata[ "response_code"] or FlaskFastAPI.default_response_codes[ method] responses[str(response_code)] = response method_schema = { "summary": metadata["summary"], "tags": metadata["tags"], "operationId": rule.endpoint, "responses": responses, } if metadata["requires_auth"]: method_schema["security"] = openapi_security if metadata["doc"]: method_schema["description"] = metadata["doc"] if parameters: method_schema["parameters"] = parameters if request_body: method_schema["requestBody"] = request_body paths[rule_normalised][method.lower()] = method_schema server = request.url[0:request.url.index("/", 8)] # pydantic puts these in a sub-key "definitions", so lets just pull that out schemas = schema(schemas, ref_prefix='#/components/schemas/') openapi = { "openapi": self.openapi_version, "servers": [ { "url": server, }, ], "paths": paths, "info": info, "components": { **component_security, "schemas": schemas["definitions"], }, } return openapi
from __future__ import annotations import typing as t import json from pydantic import BaseModel from pydantic.schema import schema class Person(BaseModel): name: str age: int data: t.Dict[str, str] nested_data: t.Dict[str, t.Dict[str, str]] count: t.Dict[int, int] toplevel_schema = schema([Person]) print(json.dumps(toplevel_schema, indent=2, ensure_ascii=False))
import json import pydantic from typing import List, Optional from pydantic import BaseModel from pydantic.schema import schema # generates just defintions and stores in dictionary class Dataset(BaseModel): dataset_name: str data_file: str class ytCreateModel(BaseModel): data_out: Optional[bool] var_name: Optional[str] Data: List[Dataset] top_level_schema = schema([ytCreateModel], title = "top level test") print(top_level_schema) print() print(json.dumps(top_level_schema, indent=2))
def run(): toplevel = schema([Config], title="App Config") toplevel["$ref"] = "#/definitions/Config" loading.dumpfile(toplevel)
import json from pydantic import BaseModel from pydantic.schema import schema class Foo(BaseModel): a: int class Model(BaseModel): a: Foo top_level_schema = schema( [Model], ref_prefix='#/components/schemas/') # Default location for OpenAPI print(json.dumps(top_level_schema, indent=2)) # { # "definitions": { # "Foo": { # "title": "Foo", # "type": "object", # ...
def get_open_api(app): api_obj = { "info": { "title": "xx开放平台接口文档", "version": "v0.0.1" }, "paths": {} } for url in app.url_map.iter_rules(): print(url) func = app.view_functions[url.endpoint] if not isinstance(func, ViewFuncWrapper): continue item = {} for method in url.methods: print(method) method = method.lower() if method not in ["get", "post", "put", "delete", "patch"]: continue item[method] = {} item[method]["description"] = func.description item[method]["summary"] = func.summary if hasattr(func, "tags") and func.tags: item[method]["tags"] = func.tags # url上的参数 item[method]["parameters"] = [] for arg in url.arguments: arg_obj = { "name": arg, "in": "path", "required": True, "schema": { "type": "string" }, } item[method]["parameters"].append(arg_obj) # get参数 if hasattr(func, "query") and func.query: sc = schema(models=[func.query]) obj_sc = sc.get("definitions", {}) obj_sc = obj_sc[list( obj_sc.keys())[0]] if len(sc.keys()) > 0 else {} prop_list = obj_sc.get("properties", []) required_list = obj_sc.get("required", []) for key, value in prop_list.items(): required = key in required_list prop = { "name": key, "in": "query", "required": required, "description": value.get("description", ""), "schema": value, } item[method]["parameters"].append(prop) if hasattr(func, "body") and func.body: item[method]["requestBody"] = { "content": { "application/json": { "schema": PydanticSchema(schema_class=func.body) } } } item[method]["responses"] = {} if hasattr(func, "responses") and func.responses: for key, value in func.responses.items(): item[method]["responses"][key] = { "description": value.get("description", ""), "content": { "application/json": { "schema": PydanticSchema(schema_class=value.get( "schema", BaseModel)) } }, } if url.rule not in api_obj["paths"]: api_obj["paths"][url.rule] = {} api_obj["paths"][url.rule].update(item) return OpenAPI.parse_obj(api_obj)
def get_openapi(base_object: List[Any], title: str = None, version: str = None, openapi_version: str = "3.0.2", description: str = None, info: dict = None, external_docs: dict = None) -> Dict: """Return UWG Schema as an openapi compatible dictionary.""" open_api = dict(_base_open_api) open_api['openapi'] = openapi_version if info: open_api['info'] = info if title: open_api['info']['title'] = title if not version: raise ValueError( 'Schema version must be specified as argument or from distribution metadata' ) if version: open_api['info']['version'] = version if description: open_api['info']['description'] = description if external_docs: open_api['externalDocs'] = external_docs schemas = schema(base_object, ref_prefix='#/components/schemas/')['definitions'] # goes to tags tags = [] # goes to x-tagGroups['tags'] tag_names = [] schema_names = list(schemas.keys()) schema_names.sort() for name in schema_names: model_name, tag = create_tag(name) tag_names.append(model_name) tags.append(tag) # sort properties order: put required parameters at begining of the list s = schemas[name] if 'properties' in s: properties = s['properties'] elif 'enum' in s: # enum continue else: properties = s['allOf'][1]['properties'] # make all types readOnly try: properties['type']['readOnly'] = True except KeyError: # no type has been set in properties for this object typ = { 'title': 'Type', 'default': f'{name}', 'type': 'string', 'pattern': f'^{name}$', 'readOnly': True, } properties['type'] = typ # add format to numbers and integers # this is helpful for C# generators for prop in properties: try: properties[prop] = set_format(properties[prop]) except KeyError: # referenced object if 'anyOf' in properties[prop]: new_any_of = [] for item in properties[prop]['anyOf']: new_any_of.append(set_format(item)) properties[prop]['anyOf'] = new_any_of else: continue # sort fields to keep required ones on top if 'required' in s: required = s['required'] elif 'allOf' in s: try: required = s['allOf'][1]['required'] except KeyError: # no required field continue else: continue sorted_props = {} optional = {} for prop, value in properties.items(): if prop in required: sorted_props[prop] = value else: optional[prop] = value sorted_props.update(optional) if 'properties' in s: s['properties'] = sorted_props else: s['allOf'][1]['properties'] = sorted_props tag_names.sort() open_api['tags'] = tags open_api['x-tagGroups'][0]['tags'] = tag_names open_api['components']['schemas'] = schemas return open_api
def show_schema() -> None: from config import Config from pydantic.schema import schema toplevel_schema = schema([Config]) print(json.dumps(toplevel_schema, indent=2, ensure_ascii=False))
def get_openapi( base_object: List[Any], title: str = None, version: str = None, openapi_version: str = "3.0.2", description: str = None, info: dict = None, external_docs: dict = None, inheritance: bool = False, add_discriminator: bool = True ) -> Dict: """Get openapi compatible dictionary from a list of Pydantic objects. Args: base_objects: A list of Pydantic model objects to be included in the OpenAPI schema. title: An optional title for OpenAPI title in info field. version: Schema version to set the version in info. openapi_version: Version for OpenAPI schema. Default is 3.0.2. description: A short description for schema info. info: Schema info as a dictionary. You can use this input to provide title, version and description together. external_docs: Link to external docs for schema. inheritance: A boolean to wheather the OpenAPI specification should be modified to use polymorphism. We use Pydantic to generate the initial version and then post-process the output dictionary to generate the new schema. Returns: Dict -- OpenAPI schema as a dictionary. """ open_api = dict(_base_open_api) open_api['openapi'] = openapi_version if info: open_api['info'] = info if title: open_api['info']['title'] = title if not version: raise ValueError( 'Schema version must be specified as argument or from distribution metadata' ) if version: open_api['info']['version'] = version if description: open_api['info']['description'] = description if external_docs: open_api['externalDocs'] = external_docs if not inheritance: schemas = schema(base_object, ref_prefix='#/components/schemas/')['definitions'] else: schemas = get_schemas_inheritance(base_object) schemas, tags, tag_names = clean_schemas( schemas, add_tags=True, add_discriminator=inheritance and add_discriminator, add_type=True ) open_api['tags'] = tags open_api['x-tagGroups'][0]['tags'] = tag_names open_api['components']['schemas'] = schemas return open_api
def test_schema_no_definitions(): model_schema = schema([], title='Schema without definitions') assert model_schema == {'title': 'Schema without definitions'}
from typing import List import json from pydantic import BaseModel from pydantic.schema import schema class Pets(BaseModel): __root__: List[str] print(Pets(__root__=['dog', 'cat'])) print(Pets(__root__=['dog', 'cat']).json()) print(Pets.parse_obj(['dog', 'cat'])) print(Pets.schema()) pets_schema = schema([Pets]) print(json.dumps(pets_schema, indent=2))
def get_openapi( base_object, title: str = None, version: str = None, openapi_version: str = "3.0.2", description: str = None, ) -> Dict: """Return Honeybee Model Schema as an openapi compatible dictionary.""" open_api = dict(_base_open_api) open_api['openapi'] = openapi_version if title: open_api['info']['title'] = title if version: open_api['info']['version'] = version if description: open_api['info']['description'] = description definitions = schema(base_object, ref_prefix='#/components/schemas/') # goes to tags tags = [] # goes to x-tagGroups['tags'] tag_names = [] schemas = definitions['definitions'] schema_names = list(schemas.keys()) schema_names.sort() for name in schema_names: model_name = '%s_model' % name.lower() tag_names.append(model_name) tag = { 'name': model_name, 'x-displayName': name, 'description': '<SchemaDefinition schemaRef=\"#/components/schemas/%s\" />\n' % name } tags.append(tag) # sort properties order: put required parameters at begining of the list s = schemas[name] if not 'required' in s: continue properties = s['properties'] required = s['required'] sorted_props = {} optional = {} for prop, value in properties.items(): if prop in required: sorted_props[prop] = value else: optional[prop] = value sorted_props.update(optional) s['properties'] = sorted_props tag_names.sort() open_api['tags'] = tags open_api['x-tagGroups'][0]['tags'] = tag_names open_api['components']['schemas'] = schemas return open_api
def test_schema_from_models(): class Foo(BaseModel): a: str class Bar(BaseModel): b: Foo class Baz(BaseModel): c: Bar class Model(BaseModel): d: Baz class Ingredient(BaseModel): name: str class Pizza(BaseModel): name: str ingredients: List[Ingredient] model_schema = schema( [Model, Pizza], title='Multi-model schema', description='Single JSON Schema with multiple definitions' ) assert model_schema == { 'title': 'Multi-model schema', 'description': 'Single JSON Schema with multiple definitions', 'definitions': { 'Pizza': { 'title': 'Pizza', 'type': 'object', 'properties': { 'name': {'title': 'Name', 'type': 'string'}, 'ingredients': { 'title': 'Ingredients', 'type': 'array', 'items': {'$ref': '#/definitions/Ingredient'}, }, }, 'required': ['name', 'ingredients'], }, 'Ingredient': { 'title': 'Ingredient', 'type': 'object', 'properties': {'name': {'title': 'Name', 'type': 'string'}}, 'required': ['name'], }, 'Model': { 'title': 'Model', 'type': 'object', 'properties': {'d': {'$ref': '#/definitions/Baz'}}, 'required': ['d'], }, 'Baz': { 'title': 'Baz', 'type': 'object', 'properties': {'c': {'$ref': '#/definitions/Bar'}}, 'required': ['c'], }, 'Bar': { 'title': 'Bar', 'type': 'object', 'properties': {'b': {'$ref': '#/definitions/Foo'}}, 'required': ['b'], }, 'Foo': { 'title': 'Foo', 'type': 'object', 'properties': {'a': {'title': 'A', 'type': 'string'}}, 'required': ['a'], }, }, }
def run(): toplevel = schema([Config], title="App Config") loading.dumpfile(toplevel)
import json from pydantic.schema import schema import pore_c.model as m top_level_schema = schema( [ m.FragmentRecord, m.AlignmentRecord, m.PoreCRecord, m.PoreCContactRecord, m.PoreCConcatemerRecord ], title="PoreC Data Model", ) print(json.dumps(top_level_schema, indent=2))
openapi: 3.0.0 info: version: v{OPENAPI_SCHEMA_VERSION} title: 'ONEm JSON response schema' description: '' paths: {{}} servers: - description: SwaggerHub API Auto Mocking url: https://virtserver.swaggerhub.com/romeo1m/schemajson/v1 components: schemas: """ if __name__ == '__main__': file_dict = yaml.safe_load(begin) top_level_schema = schema([ MenuItem, Menu, FormItem, MenuItemFormItem, Form, Response, ], ref_prefix='#/components/schemas/') top_level_schema = json.loads(json.dumps(top_level_schema)) file_dict['components']['schemas'] = top_level_schema['definitions'] file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'schema.yaml') with open(file, mode="w+") as f: f.write(yaml.dump(file_dict))
def schema(self): """Returns current plugin schema.""" return schema([self._schema])
from pydantic import BaseModel from pydantic.fields import ModelField from pydantic.schema import schema, field_schema class Message(BaseModel): message: str class Wrap(BaseModel): message: Message print(schema([Message])) print(field_schema(Message.__fields__["message"], model_name_map={})) print(schema([Wrap])) print(Wrap.__fields__) print( field_schema(Wrap.__fields__["message"], model_name_map={Message: "Message"}))