def get_error_schema(errors=None): return build_object_type({ "error": { "type": "string", "description": "Machine readable error indicating what went wrong.", "enum": errors, }, "detail": { "oneOf": [ { "type": "string", "format": "string", "description": "Human readable details about what went wrong.", }, { "type": "object", "format": "object", "description": "Machine readable object about what went wrong.", }, ] }, })
def get_error_schema(errors=None): return build_object_type({ 'error': { 'type': 'string', 'description': 'Machine readable error indicating what went wrong.', 'enum': errors }, 'detail': { 'oneOf': [{ 'type': 'string', 'format': 'string', 'description': 'Human readable details about what went wrong.' }, { 'type': 'object', 'format': 'object', 'description': 'Machine readable object about what went wrong.' }] } })
def _map_basic_serializer(self, serializer, direction): serializer = force_instance(serializer) required = set() properties = {} for field in serializer.fields.values(): if isinstance(field, serializers.HiddenField): continue schema = self._map_serializer_field(field, direction) # skip field if there is no schema for the direction if not schema: continue if field.required or schema.get('readOnly'): required.add(field.field_name) self._map_field_validators(field, schema) properties[field.field_name] = safe_ref(schema) if spectacular_settings.COMPONENT_SPLIT_PATCH: if self.method == 'PATCH' and direction == 'request': required = [] return build_object_type( properties=properties, required=required, description=inspect.getdoc(serializer), )
class UploadFileView(APIView): permission_classes = (IsAuthenticated, ) parser_classes = (MultiPartParser, ) @extend_schema( tags=['User files'], operation_id='upload_file', description= ('Uploads a file to Baserow by uploading the file contents directly. A ' '`file` multipart is expected containing the file contents.'), request=build_object_type(), responses={ 200: UserFileSerializer, 400: get_error_schema( ['ERROR_INVALID_FILE', 'ERROR_FILE_SIZE_TOO_LARGE']) }) @transaction.atomic @map_exceptions({ InvalidFileStreamError: ERROR_INVALID_FILE, FileSizeTooLargeError: ERROR_FILE_SIZE_TOO_LARGE }) def post(self, request): """Uploads a file by uploading the contents directly.""" if 'file' not in request.FILES: raise InvalidFileStreamError('No file was provided.') file = request.FILES.get('file') user_file = UserFileHandler().upload_user_file(request.user, file.name, file) serializer = UserFileSerializer(user_file) return Response(serializer.data)
def _get_request_body(self): # only unsafe methods can have a body if self.method not in ('PUT', 'PATCH', 'POST'): return None serializer = force_instance(self.get_request_serializer()) request_body_required = False if is_list_serializer(serializer): if is_serializer(serializer.child): component = self.resolve_serializer(serializer.child, 'request') schema = build_array_type(component.ref) else: schema = build_array_type( self._map_serializer_field(serializer.child, 'request')) request_body_required = True elif is_serializer(serializer): if self.method == 'PATCH': serializer.partial = True component = self.resolve_serializer(serializer, 'request') if not component.schema: # serializer is empty so skip content enumeration return None schema = component.ref # request body is only required if any required property is not read-only readonly_props = [ p for p, s in component.schema.get('properties', {}).items() if s.get('readOnly') ] required_props = component.schema.get('required', []) request_body_required = any(req not in readonly_props for req in required_props) elif is_basic_type(serializer): schema = build_basic_type(serializer) if not schema: return None else: warn( f'could not resolve request body for {self.method} {self.path}. defaulting to generic ' 'free-form object. (maybe annotate a Serializer class?)') schema = build_object_type( additionalProperties={}, description='Unspecified request body', ) request_body = { 'content': { media_type: build_media_type_object( schema, self._get_examples(serializer, 'request', media_type)) for media_type in self.map_parsers() } } if request_body_required: request_body['required'] = request_body_required return request_body
def _map_basic_serializer(self, serializer, direction): serializer = force_instance(serializer) required = set() properties = {} for field in serializer.fields.values(): if isinstance(field, serializers.HiddenField): continue if field.field_name in get_override(serializer, 'exclude_fields', []): continue schema = self._map_serializer_field(field, direction) # skip field if there is no schema for the direction if not schema: continue add_to_required = ( field.required or (schema.get('readOnly') and not spectacular_settings.COMPONENT_NO_READ_ONLY_REQUIRED)) if add_to_required: required.add(field.field_name) self._map_field_validators(field, schema) properties[field.field_name] = safe_ref(schema) if spectacular_settings.COMPONENT_SPLIT_PATCH: if self.method == 'PATCH' and direction == 'request': required = [] return build_object_type( properties=properties, required=required, description=get_doc(serializer.__class__), )
from drf_spectacular.plumbing import build_object_type group_user_schema = build_object_type({ 'order': { 'type': 'int', 'description': 'The order of the group, lowest first.', 'example': 0 }, 'id': { 'type': 'int', 'description': 'The unique identifier of the group.', 'example': 1 }, 'name': { 'type': 'string', 'description': 'The name given to the group.', 'example': 'Bram\'s group' } })
) from drf_spectacular.settings import spectacular_settings from drf_spectacular.types import OpenApiTypes def build_standard_type(obj, **kwargs): """Build a basic type with optional add owns.""" schema = build_basic_type(obj) schema.update(kwargs) return schema GENERIC_ERROR = build_object_type( description=_("Generic API Error"), properties={ "detail": build_standard_type(OpenApiTypes.STR), "code": build_standard_type(OpenApiTypes.STR), }, required=["detail"], ) VALIDATION_ERROR = build_object_type( description=_("Validation Error"), properties={ "non_field_errors": build_array_type(build_standard_type(OpenApiTypes.STR)), "code": build_standard_type(OpenApiTypes.STR), }, required=[], additionalProperties={}, )
from drf_spectacular.plumbing import build_object_type create_user_response_schema = build_object_type({ 'user': { 'type': 'object', 'description': 'An object containing information related to the user.', 'properties': { 'first_name': { 'type': 'string', 'description': 'The first name of related user.' }, 'username': { 'type': 'string', 'format': 'email', 'description': 'The username of the related user. This is always ' 'an email address.' } } }, 'token': { 'type': 'string' } }) authenticate_user_schema = create_user_response_schema
from drf_spectacular.plumbing import build_object_type group_user_schema = build_object_type({ "order": { "type": "int", "description": "The order of the group, lowest first.", "example": 0, }, "id": { "type": "int", "description": "The unique identifier of the group.", "example": 1, }, "name": { "type": "string", "description": "The name given to the group.", "example": "Bram's group", }, })
def resolve_type_hint(hint) -> Any: """drf-spectacular library method modified as described above""" origin, args = _get_type_hint_origin(hint) excluded_fields = get_override(hint, "exclude_fields", []) if origin is None and is_basic_type(hint, allow_none=False): return build_basic_type(hint) elif origin is None and inspect.isclass(hint) and issubclass(hint, tuple): # a convoluted way to catch NamedTuple. suggestions welcome. if get_type_hints(hint): properties = { k: resolve_type_hint(v) for k, v in get_type_hints(hint).items() } else: properties = { k: build_basic_type(OpenApiTypes.ANY) for k in hint._fields } return build_object_type(properties=properties, required=properties.keys()) elif origin is list or hint is list: return build_array_type( resolve_type_hint(args[0]) if args else build_basic_type(OpenApiTypes.ANY)) elif origin is tuple: return build_array_type( schema=build_basic_type(args[0]), max_length=len(args), min_length=len(args), ) elif origin is dict or origin is defaultdict or origin is OrderedDict: schema = build_basic_type(OpenApiTypes.OBJECT) if args and args[1] is not typing.Any: schema["additionalProperties"] = resolve_type_hint(args[1]) return schema elif origin is set: return build_array_type(resolve_type_hint(args[0])) elif origin is frozenset: return build_array_type(resolve_type_hint(args[0])) elif origin is Literal: # Literal only works for python >= 3.8 despite typing_extensions, because it # behaves slightly different w.r.t. __origin__ schema = {"enum": list(args)} if all(type(args[0]) is type(choice) for choice in args): schema.update(build_basic_type(type(args[0]))) return schema elif inspect.isclass(hint) and issubclass(hint, Enum): schema = {"enum": [item.value for item in hint]} mixin_base_types = [t for t in hint.__mro__ if is_basic_type(t)] if mixin_base_types: schema.update(build_basic_type(mixin_base_types[0])) return schema elif isinstance(hint, _TypedDictMeta): return build_object_type( properties={ k: resolve_type_hint(v) for k, v in get_type_hints(hint).items() if k not in excluded_fields }, description=inspect.cleandoc(hint.__doc__ or ""), required=[ h for h in hint.__required_keys__ if h not in excluded_fields ], ) elif origin is Union: type_args = [arg for arg in args if arg is not type(None)] # noqa: E721 if len(type_args) > 1: schema = {"oneOf": [resolve_type_hint(arg) for arg in type_args]} else: schema = resolve_type_hint(type_args[0]) if type(None) in args: schema["nullable"] = True return schema elif origin is collections.abc.Iterable: return build_array_type(resolve_type_hint(args[0])) elif isinstance(hint, typing._TypedDictMeta): raise UnableToProceedError( "Wrong TypedDict class, please use typing_extensions.TypedDict") else: raise UnableToProceedError()
FACET_RESPONSE = build_object_type( description=_("Faceted classification"), additionalProperties=build_object_type( properties={ "doc_count": build_basic_type(int), }, additionalProperties=build_object_type( properties={ "meta": build_object_type( properties={ "param": build_basic_type(str), "missing": build_basic_type(str), "missing_param": build_basic_type(str), } ), "doc_count_error_upper_bound": build_basic_type(int), "sum_other_doc_count": build_basic_type(int), "buckets": build_array_type( build_object_type( properties={ "key": build_basic_type(str), "doc_count": build_basic_type(int), }, ), ), }, ), ), )
"""Build a basic type with optional add ons.""" schema = build_basic_type(obj) schema.update(kwargs) return schema GENERIC_ERROR = build_object_type( description=_("API error"), properties={ "code": build_standard_type(OpenApiTypes.NUMBER), "message": build_standard_type(OpenApiTypes.STR), "status_code": build_standard_type(OpenApiTypes.NUMBER), "errors": build_array_type( build_object_type( properties={ "code": build_standard_type(OpenApiTypes.NUMBER), "field": build_standard_type(OpenApiTypes.STR), "message": build_standard_type(OpenApiTypes.STR), }, required=["field", "message"], ), ), }, required=["code", "message", "errors"], ) def postprocess_schema_responses(result, generator, **kwargs): # noqa: W0613 """Workaround to set a default response for endpoints. Workaround suggested at
from drf_spectacular.plumbing import build_object_type create_user_response_schema = build_object_type({ "user": { "type": "object", "description": "An object containing information related to the user.", "properties": { "first_name": { "type": "string", "description": "The first name of related user.", }, "username": { "type": "string", "format": "email", "description": "The username of the related user. This is always " "an email address.", }, }, }, "token": { "type": "string" }, }) authenticate_user_schema = create_user_response_schema