def _try_get_schema_for_enum(self, object_type: Type) -> Optional[Schema]: if issubclass(object_type, IntEnum): return Schema(type=ValueType.INTEGER, enum=[v.value for v in object_type]) if issubclass(object_type, Enum): return Schema(type=ValueType.STRING, enum=[v.value for v in object_type]) return None
def _get_schema_by_type( self, object_type: Type[Any]) -> Union[Schema, Reference]: # check_union if is_dataclass(object_type): return self._get_shema_for_dataclass(object_type) if (BaseModel is not ... and inspect.isclass(object_type) and issubclass(object_type, BaseModel) # type: ignore ): return self._get_schema_for_pydantic_model(object_type) if isinstance(object_type, ForwardRef): # Note: this code does not support different classes with the same name # but defined in different modules as contracts of the API # Note: this is not supported in Swagger UI # https://github.com/swagger-api/swagger-ui/issues/3325 ref_name = object_type.__forward_arg__ # type: ignore return Reference(f"#/components/schemas/{ref_name}") schema = self._try_get_schema_for_simple_type(object_type) if schema: return schema schema = self._try_get_schema_for_iterable(object_type) if schema: return schema if inspect.isclass(object_type): schema = self._try_get_schema_for_enum(object_type) return schema or Schema()
def _get_schema_for_pydantic_model(self, object_type: Type) -> Reference: assert BaseModel is not ..., "pydantic must be installed to use this method" assert issubclass(object_type, BaseModel) # type: ignore assert hasattr(object_type, "__fields__") if object_type in self._objects_references: return self._objects_references[object_type] required: List[str] = [] properties: Dict[str, Union[Schema, Reference]] = {} fields = object_type.__fields__ # type: ignore for field in fields.values(): is_optional, child_type = check_union(field.type_) if not is_optional: required.append(field.name) properties[field.name] = self.get_schema_by_type(child_type) reference = self._register_schema( self._handle_subclasses( Schema( type=ValueType.OBJECT, required=required or None, properties=properties, ), object_type, ), object_type.__name__, ) self._objects_references[object_type] = reference return reference
def _get_shema_for_dataclass(self, object_type: Type) -> Reference: assert is_dataclass(object_type) if object_type in self._objects_references: return self._objects_references[object_type] required: List[str] = [] properties: Dict[str, Union[Schema, Reference]] = {} # handle optional for field in fields(object_type): is_optional, child_type = check_union(field.type) if not is_optional: required.append(field.name) if isinstance(child_type, str): # this is a forward reference child_type = child_type.strip("'") properties[field.name] = Reference(f"#/components/schemas/{child_type}") else: properties[field.name] = self.get_schema_by_type(child_type) reference = self._register_schema( self._handle_subclasses( Schema( type=ValueType.OBJECT, required=required or None, properties=properties, ), object_type, ), object_type.__name__, ) self._objects_references[object_type] = reference return reference
def _try_get_schema_for_simple_type(self, object_type: Type) -> Optional[Schema]: if object_type is str: return Schema(type=ValueType.STRING) if object_type is int: # TODO: support control over format return Schema(type=ValueType.INTEGER, format=ValueFormat.INT64) if object_type is float: return Schema(type=ValueType.NUMBER, format=ValueFormat.FLOAT) if object_type is bool: return Schema(type=ValueType.BOOLEAN) if object_type is UUID: return Schema(type=ValueType.STRING, format=ValueFormat.UUID) if object_type is date: return Schema(type=ValueType.STRING, format=ValueFormat.DATE) if object_type is datetime: return Schema(type=ValueType.STRING, format=ValueFormat.DATETIME) return None
def _try_get_schema_for_iterable(self, object_type: Type) -> Optional[Schema]: if object_type in {list, set, tuple}: # the user didn't specify the item type return Schema(type=ValueType.ARRAY, items=Schema(type=ValueType.STRING)) try: object_type.__origin__ # type: ignore except AttributeError: pass else: # can be List, List[str] or list[str] (Python 3.9), # note: it could also be union if it wasn't handled above for dataclasses try: type_args = object_type.__args__ # type: ignore except AttributeError: # pragma: no cover item_type = str else: item_type = next(iter(type_args), str) return Schema( type=ValueType.ARRAY, items=self.get_schema_by_type(item_type) ) return None
def on_polymorph_example_docs_created_pydantic( docs: OpenAPIHandler, operation: Operation ) -> None: docs.register_schema_for_type(PetModel) pet_schema = docs.components.schemas["Pet"] assert isinstance(pet_schema, Schema) pet_schema.discriminator = Discriminator("type", {"cat": "CatPet", "dog": "DogPet"}) cat_ref = docs.register_schema_for_type(CatPetModel) dog_ref = docs.register_schema_for_type(DogPetModel) operation.responses["200"] = ResponseDoc( "Polymorph example", content={ "application/json": MediaType(schema=Schema(any_of=[cat_ref, dog_ref])) }, )
def _handle_subclasses(self, schema: Schema, object_type: Type) -> Schema: """ Method that implements automatic support for subclasses, handling Schema.allOf """ assert inspect.isclass(object_type) direct_parent = object_type.__mro__[1] if direct_parent is object: return schema direct_parent_ref = self.register_schema_for_type(direct_parent) for inherited_class in object_type.__mro__[2:]: if inherited_class is object or not self._is_handled_object_type( inherited_class): continue self.register_schema_for_type(inherited_class) return Schema(all_of=[direct_parent_ref, schema])