Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/rest_framework/schemas/openapi.py: 10%

416 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import re 

2import warnings 

3from collections import OrderedDict 

4from decimal import Decimal 

5from operator import attrgetter 

6from urllib.parse import urljoin 

7 

8from django.core.validators import ( 

9 DecimalValidator, EmailValidator, MaxLengthValidator, MaxValueValidator, 

10 MinLengthValidator, MinValueValidator, RegexValidator, URLValidator 

11) 

12from django.db import models 

13from django.utils.encoding import force_str 

14 

15from rest_framework import ( 

16 RemovedInDRF314Warning, exceptions, renderers, serializers 

17) 

18from rest_framework.compat import uritemplate 

19from rest_framework.fields import _UnvalidatedField, empty 

20from rest_framework.settings import api_settings 

21 

22from .generators import BaseSchemaGenerator 

23from .inspectors import ViewInspector 

24from .utils import get_pk_description, is_list_view 

25 

26 

27class SchemaGenerator(BaseSchemaGenerator): 

28 

29 def get_info(self): 

30 # Title and version are required by openapi specification 3.x 

31 info = { 

32 'title': self.title or '', 

33 'version': self.version or '' 

34 } 

35 

36 if self.description is not None: 

37 info['description'] = self.description 

38 

39 return info 

40 

41 def check_duplicate_operation_id(self, paths): 

42 ids = {} 

43 for route in paths: 

44 for method in paths[route]: 

45 if 'operationId' not in paths[route][method]: 

46 continue 

47 operation_id = paths[route][method]['operationId'] 

48 if operation_id in ids: 

49 warnings.warn( 

50 'You have a duplicated operationId in your OpenAPI schema: {operation_id}\n' 

51 '\tRoute: {route1}, Method: {method1}\n' 

52 '\tRoute: {route2}, Method: {method2}\n' 

53 '\tAn operationId has to be unique across your schema. Your schema may not work in other tools.' 

54 .format( 

55 route1=ids[operation_id]['route'], 

56 method1=ids[operation_id]['method'], 

57 route2=route, 

58 method2=method, 

59 operation_id=operation_id 

60 ) 

61 ) 

62 ids[operation_id] = { 

63 'route': route, 

64 'method': method 

65 } 

66 

67 def get_schema(self, request=None, public=False): 

68 """ 

69 Generate a OpenAPI schema. 

70 """ 

71 self._initialise_endpoints() 

72 components_schemas = {} 

73 

74 # Iterate endpoints generating per method path operations. 

75 paths = {} 

76 _, view_endpoints = self._get_paths_and_endpoints(None if public else request) 

77 for path, method, view in view_endpoints: 

78 if not self.has_view_permissions(path, method, view): 

79 continue 

80 

81 operation = view.schema.get_operation(path, method) 

82 components = view.schema.get_components(path, method) 

83 for k in components.keys(): 

84 if k not in components_schemas: 

85 continue 

86 if components_schemas[k] == components[k]: 

87 continue 

88 warnings.warn('Schema component "{}" has been overriden with a different value.'.format(k)) 

89 

90 components_schemas.update(components) 

91 

92 # Normalise path for any provided mount url. 

93 if path.startswith('/'): 

94 path = path[1:] 

95 path = urljoin(self.url or '/', path) 

96 

97 paths.setdefault(path, {}) 

98 paths[path][method.lower()] = operation 

99 

100 self.check_duplicate_operation_id(paths) 

101 

102 # Compile final schema. 

103 schema = { 

104 'openapi': '3.0.2', 

105 'info': self.get_info(), 

106 'paths': paths, 

107 } 

108 

109 if len(components_schemas) > 0: 

110 schema['components'] = { 

111 'schemas': components_schemas 

112 } 

113 

114 return schema 

115 

116# View Inspectors 

117 

118 

119class AutoSchema(ViewInspector): 

120 

121 def __init__(self, tags=None, operation_id_base=None, component_name=None): 

122 """ 

123 :param operation_id_base: user-defined name in operationId. If empty, it will be deducted from the Model/Serializer/View name. 

124 :param component_name: user-defined component's name. If empty, it will be deducted from the Serializer's class name. 

125 """ 

126 if tags and not all(isinstance(tag, str) for tag in tags): 

127 raise ValueError('tags must be a list or tuple of string.') 

128 self._tags = tags 

129 self.operation_id_base = operation_id_base 

130 self.component_name = component_name 

131 super().__init__() 

132 

133 request_media_types = [] 

134 response_media_types = [] 

135 

136 method_mapping = { 

137 'get': 'retrieve', 

138 'post': 'create', 

139 'put': 'update', 

140 'patch': 'partialUpdate', 

141 'delete': 'destroy', 

142 } 

143 

144 def get_operation(self, path, method): 

145 operation = {} 

146 

147 operation['operationId'] = self.get_operation_id(path, method) 

148 operation['description'] = self.get_description(path, method) 

149 

150 parameters = [] 

151 parameters += self.get_path_parameters(path, method) 

152 parameters += self.get_pagination_parameters(path, method) 

153 parameters += self.get_filter_parameters(path, method) 

154 operation['parameters'] = parameters 

155 

156 request_body = self.get_request_body(path, method) 

157 if request_body: 

158 operation['requestBody'] = request_body 

159 operation['responses'] = self.get_responses(path, method) 

160 operation['tags'] = self.get_tags(path, method) 

161 

162 return operation 

163 

164 def get_component_name(self, serializer): 

165 """ 

166 Compute the component's name from the serializer. 

167 Raise an exception if the serializer's class name is "Serializer" (case-insensitive). 

168 """ 

169 if self.component_name is not None: 

170 return self.component_name 

171 

172 # use the serializer's class name as the component name. 

173 component_name = serializer.__class__.__name__ 

174 # We remove the "serializer" string from the class name. 

175 pattern = re.compile("serializer", re.IGNORECASE) 

176 component_name = pattern.sub("", component_name) 

177 

178 if component_name == "": 

179 raise Exception( 

180 '"{}" is an invalid class name for schema generation. ' 

181 'Serializer\'s class name should be unique and explicit. e.g. "ItemSerializer"' 

182 .format(serializer.__class__.__name__) 

183 ) 

184 

185 return component_name 

186 

187 def get_components(self, path, method): 

188 """ 

189 Return components with their properties from the serializer. 

190 """ 

191 

192 if method.lower() == 'delete': 

193 return {} 

194 

195 request_serializer = self.get_request_serializer(path, method) 

196 response_serializer = self.get_response_serializer(path, method) 

197 

198 components = {} 

199 

200 if isinstance(request_serializer, serializers.Serializer): 

201 component_name = self.get_component_name(request_serializer) 

202 content = self.map_serializer(request_serializer) 

203 components.setdefault(component_name, content) 

204 

205 if isinstance(response_serializer, serializers.Serializer): 

206 component_name = self.get_component_name(response_serializer) 

207 content = self.map_serializer(response_serializer) 

208 components.setdefault(component_name, content) 

209 

210 return components 

211 

212 def _to_camel_case(self, snake_str): 

213 components = snake_str.split('_') 

214 # We capitalize the first letter of each component except the first one 

215 # with the 'title' method and join them together. 

216 return components[0] + ''.join(x.title() for x in components[1:]) 

217 

218 def get_operation_id_base(self, path, method, action): 

219 """ 

220 Compute the base part for operation ID from the model, serializer or view name. 

221 """ 

222 model = getattr(getattr(self.view, 'queryset', None), 'model', None) 

223 

224 if self.operation_id_base is not None: 

225 name = self.operation_id_base 

226 

227 # Try to deduce the ID from the view's model 

228 elif model is not None: 

229 name = model.__name__ 

230 

231 # Try with the serializer class name 

232 elif self.get_serializer(path, method) is not None: 

233 name = self.get_serializer(path, method).__class__.__name__ 

234 if name.endswith('Serializer'): 

235 name = name[:-10] 

236 

237 # Fallback to the view name 

238 else: 

239 name = self.view.__class__.__name__ 

240 if name.endswith('APIView'): 

241 name = name[:-7] 

242 elif name.endswith('View'): 

243 name = name[:-4] 

244 

245 # Due to camel-casing of classes and `action` being lowercase, apply title in order to find if action truly 

246 # comes at the end of the name 

247 if name.endswith(action.title()): # ListView, UpdateAPIView, ThingDelete ... 

248 name = name[:-len(action)] 

249 

250 if action == 'list' and not name.endswith('s'): # listThings instead of listThing 

251 name += 's' 

252 

253 return name 

254 

255 def get_operation_id(self, path, method): 

256 """ 

257 Compute an operation ID from the view type and get_operation_id_base method. 

258 """ 

259 method_name = getattr(self.view, 'action', method.lower()) 

260 if is_list_view(path, method, self.view): 

261 action = 'list' 

262 elif method_name not in self.method_mapping: 

263 action = self._to_camel_case(method_name) 

264 else: 

265 action = self.method_mapping[method.lower()] 

266 

267 name = self.get_operation_id_base(path, method, action) 

268 

269 return action + name 

270 

271 def get_path_parameters(self, path, method): 

272 """ 

273 Return a list of parameters from templated path variables. 

274 """ 

275 assert uritemplate, '`uritemplate` must be installed for OpenAPI schema support.' 

276 

277 model = getattr(getattr(self.view, 'queryset', None), 'model', None) 

278 parameters = [] 

279 

280 for variable in uritemplate.variables(path): 

281 description = '' 

282 if model is not None: # TODO: test this. 

283 # Attempt to infer a field description if possible. 

284 try: 

285 model_field = model._meta.get_field(variable) 

286 except Exception: 

287 model_field = None 

288 

289 if model_field is not None and model_field.help_text: 

290 description = force_str(model_field.help_text) 

291 elif model_field is not None and model_field.primary_key: 

292 description = get_pk_description(model, model_field) 

293 

294 parameter = { 

295 "name": variable, 

296 "in": "path", 

297 "required": True, 

298 "description": description, 

299 'schema': { 

300 'type': 'string', # TODO: integer, pattern, ... 

301 }, 

302 } 

303 parameters.append(parameter) 

304 

305 return parameters 

306 

307 def get_filter_parameters(self, path, method): 

308 if not self.allows_filters(path, method): 

309 return [] 

310 parameters = [] 

311 for filter_backend in self.view.filter_backends: 

312 parameters += filter_backend().get_schema_operation_parameters(self.view) 

313 return parameters 

314 

315 def allows_filters(self, path, method): 

316 """ 

317 Determine whether to include filter Fields in schema. 

318 

319 Default implementation looks for ModelViewSet or GenericAPIView 

320 actions/methods that cause filtering on the default implementation. 

321 """ 

322 if getattr(self.view, 'filter_backends', None) is None: 

323 return False 

324 if hasattr(self.view, 'action'): 

325 return self.view.action in ["list", "retrieve", "update", "partial_update", "destroy"] 

326 return method.lower() in ["get", "put", "patch", "delete"] 

327 

328 def get_pagination_parameters(self, path, method): 

329 view = self.view 

330 

331 if not is_list_view(path, method, view): 

332 return [] 

333 

334 paginator = self.get_paginator() 

335 if not paginator: 

336 return [] 

337 

338 return paginator.get_schema_operation_parameters(view) 

339 

340 def map_choicefield(self, field): 

341 choices = list(OrderedDict.fromkeys(field.choices)) # preserve order and remove duplicates 

342 if all(isinstance(choice, bool) for choice in choices): 

343 type = 'boolean' 

344 elif all(isinstance(choice, int) for choice in choices): 

345 type = 'integer' 

346 elif all(isinstance(choice, (int, float, Decimal)) for choice in choices): # `number` includes `integer` 

347 # Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5.21 

348 type = 'number' 

349 elif all(isinstance(choice, str) for choice in choices): 

350 type = 'string' 

351 else: 

352 type = None 

353 

354 mapping = { 

355 # The value of `enum` keyword MUST be an array and SHOULD be unique. 

356 # Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5.20 

357 'enum': choices 

358 } 

359 

360 # If We figured out `type` then and only then we should set it. It must be a string. 

361 # Ref: https://swagger.io/docs/specification/data-models/data-types/#mixed-type 

362 # It is optional but it can not be null. 

363 # Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5.21 

364 if type: 

365 mapping['type'] = type 

366 return mapping 

367 

368 def map_field(self, field): 

369 

370 # Nested Serializers, `many` or not. 

371 if isinstance(field, serializers.ListSerializer): 

372 return { 

373 'type': 'array', 

374 'items': self.map_serializer(field.child) 

375 } 

376 if isinstance(field, serializers.Serializer): 

377 data = self.map_serializer(field) 

378 data['type'] = 'object' 

379 return data 

380 

381 # Related fields. 

382 if isinstance(field, serializers.ManyRelatedField): 

383 return { 

384 'type': 'array', 

385 'items': self.map_field(field.child_relation) 

386 } 

387 if isinstance(field, serializers.PrimaryKeyRelatedField): 

388 model = getattr(field.queryset, 'model', None) 

389 if model is not None: 

390 model_field = model._meta.pk 

391 if isinstance(model_field, models.AutoField): 

392 return {'type': 'integer'} 

393 

394 # ChoiceFields (single and multiple). 

395 # Q: 

396 # - Is 'type' required? 

397 # - can we determine the TYPE of a choicefield? 

398 if isinstance(field, serializers.MultipleChoiceField): 

399 return { 

400 'type': 'array', 

401 'items': self.map_choicefield(field) 

402 } 

403 

404 if isinstance(field, serializers.ChoiceField): 

405 return self.map_choicefield(field) 

406 

407 # ListField. 

408 if isinstance(field, serializers.ListField): 

409 mapping = { 

410 'type': 'array', 

411 'items': {}, 

412 } 

413 if not isinstance(field.child, _UnvalidatedField): 

414 mapping['items'] = self.map_field(field.child) 

415 return mapping 

416 

417 # DateField and DateTimeField type is string 

418 if isinstance(field, serializers.DateField): 

419 return { 

420 'type': 'string', 

421 'format': 'date', 

422 } 

423 

424 if isinstance(field, serializers.DateTimeField): 

425 return { 

426 'type': 'string', 

427 'format': 'date-time', 

428 } 

429 

430 # "Formats such as "email", "uuid", and so on, MAY be used even though undefined by this specification." 

431 # see: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#data-types 

432 # see also: https://swagger.io/docs/specification/data-models/data-types/#string 

433 if isinstance(field, serializers.EmailField): 

434 return { 

435 'type': 'string', 

436 'format': 'email' 

437 } 

438 

439 if isinstance(field, serializers.URLField): 

440 return { 

441 'type': 'string', 

442 'format': 'uri' 

443 } 

444 

445 if isinstance(field, serializers.UUIDField): 

446 return { 

447 'type': 'string', 

448 'format': 'uuid' 

449 } 

450 

451 if isinstance(field, serializers.IPAddressField): 

452 content = { 

453 'type': 'string', 

454 } 

455 if field.protocol != 'both': 

456 content['format'] = field.protocol 

457 return content 

458 

459 if isinstance(field, serializers.DecimalField): 

460 if getattr(field, 'coerce_to_string', api_settings.COERCE_DECIMAL_TO_STRING): 

461 content = { 

462 'type': 'string', 

463 'format': 'decimal', 

464 } 

465 else: 

466 content = { 

467 'type': 'number' 

468 } 

469 

470 if field.decimal_places: 

471 content['multipleOf'] = float('.' + (field.decimal_places - 1) * '0' + '1') 

472 if field.max_whole_digits: 

473 content['maximum'] = int(field.max_whole_digits * '9') + 1 

474 content['minimum'] = -content['maximum'] 

475 self._map_min_max(field, content) 

476 return content 

477 

478 if isinstance(field, serializers.FloatField): 

479 content = { 

480 'type': 'number', 

481 } 

482 self._map_min_max(field, content) 

483 return content 

484 

485 if isinstance(field, serializers.IntegerField): 

486 content = { 

487 'type': 'integer' 

488 } 

489 self._map_min_max(field, content) 

490 # 2147483647 is max for int32_size, so we use int64 for format 

491 if int(content.get('maximum', 0)) > 2147483647 or int(content.get('minimum', 0)) > 2147483647: 

492 content['format'] = 'int64' 

493 return content 

494 

495 if isinstance(field, serializers.FileField): 

496 return { 

497 'type': 'string', 

498 'format': 'binary' 

499 } 

500 

501 # Simplest cases, default to 'string' type: 

502 FIELD_CLASS_SCHEMA_TYPE = { 

503 serializers.BooleanField: 'boolean', 

504 serializers.JSONField: 'object', 

505 serializers.DictField: 'object', 

506 serializers.HStoreField: 'object', 

507 } 

508 return {'type': FIELD_CLASS_SCHEMA_TYPE.get(field.__class__, 'string')} 

509 

510 def _map_min_max(self, field, content): 

511 if field.max_value: 

512 content['maximum'] = field.max_value 

513 if field.min_value: 

514 content['minimum'] = field.min_value 

515 

516 def map_serializer(self, serializer): 

517 # Assuming we have a valid serializer instance. 

518 required = [] 

519 properties = {} 

520 

521 for field in serializer.fields.values(): 

522 if isinstance(field, serializers.HiddenField): 

523 continue 

524 

525 if field.required: 

526 required.append(field.field_name) 

527 

528 schema = self.map_field(field) 

529 if field.read_only: 

530 schema['readOnly'] = True 

531 if field.write_only: 

532 schema['writeOnly'] = True 

533 if field.allow_null: 

534 schema['nullable'] = True 

535 if field.default is not None and field.default != empty and not callable(field.default): 

536 schema['default'] = field.default 

537 if field.help_text: 

538 schema['description'] = str(field.help_text) 

539 self.map_field_validators(field, schema) 

540 

541 properties[field.field_name] = schema 

542 

543 result = { 

544 'type': 'object', 

545 'properties': properties 

546 } 

547 if required: 

548 result['required'] = required 

549 

550 return result 

551 

552 def map_field_validators(self, field, schema): 

553 """ 

554 map field validators 

555 """ 

556 for v in field.validators: 

557 # "Formats such as "email", "uuid", and so on, MAY be used even though undefined by this specification." 

558 # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#data-types 

559 if isinstance(v, EmailValidator): 

560 schema['format'] = 'email' 

561 if isinstance(v, URLValidator): 

562 schema['format'] = 'uri' 

563 if isinstance(v, RegexValidator): 

564 # In Python, the token \Z does what \z does in other engines. 

565 # https://stackoverflow.com/questions/53283160 

566 schema['pattern'] = v.regex.pattern.replace('\\Z', '\\z') 

567 elif isinstance(v, MaxLengthValidator): 

568 attr_name = 'maxLength' 

569 if isinstance(field, serializers.ListField): 

570 attr_name = 'maxItems' 

571 schema[attr_name] = v.limit_value 

572 elif isinstance(v, MinLengthValidator): 

573 attr_name = 'minLength' 

574 if isinstance(field, serializers.ListField): 

575 attr_name = 'minItems' 

576 schema[attr_name] = v.limit_value 

577 elif isinstance(v, MaxValueValidator): 

578 schema['maximum'] = v.limit_value 

579 elif isinstance(v, MinValueValidator): 

580 schema['minimum'] = v.limit_value 

581 elif isinstance(v, DecimalValidator) and \ 

582 not getattr(field, 'coerce_to_string', api_settings.COERCE_DECIMAL_TO_STRING): 

583 if v.decimal_places: 

584 schema['multipleOf'] = float('.' + (v.decimal_places - 1) * '0' + '1') 

585 if v.max_digits: 

586 digits = v.max_digits 

587 if v.decimal_places is not None and v.decimal_places > 0: 

588 digits -= v.decimal_places 

589 schema['maximum'] = int(digits * '9') + 1 

590 schema['minimum'] = -schema['maximum'] 

591 

592 def get_paginator(self): 

593 pagination_class = getattr(self.view, 'pagination_class', None) 

594 if pagination_class: 

595 return pagination_class() 

596 return None 

597 

598 def map_parsers(self, path, method): 

599 return list(map(attrgetter('media_type'), self.view.parser_classes)) 

600 

601 def map_renderers(self, path, method): 

602 media_types = [] 

603 for renderer in self.view.renderer_classes: 

604 # BrowsableAPIRenderer not relevant to OpenAPI spec 

605 if issubclass(renderer, renderers.BrowsableAPIRenderer): 

606 continue 

607 media_types.append(renderer.media_type) 

608 return media_types 

609 

610 def get_serializer(self, path, method): 

611 view = self.view 

612 

613 if not hasattr(view, 'get_serializer'): 

614 return None 

615 

616 try: 

617 return view.get_serializer() 

618 except exceptions.APIException: 

619 warnings.warn('{}.get_serializer() raised an exception during ' 

620 'schema generation. Serializer fields will not be ' 

621 'generated for {} {}.' 

622 .format(view.__class__.__name__, method, path)) 

623 return None 

624 

625 def get_request_serializer(self, path, method): 

626 """ 

627 Override this method if your view uses a different serializer for 

628 handling request body. 

629 """ 

630 return self.get_serializer(path, method) 

631 

632 def get_response_serializer(self, path, method): 

633 """ 

634 Override this method if your view uses a different serializer for 

635 populating response data. 

636 """ 

637 return self.get_serializer(path, method) 

638 

639 def _get_reference(self, serializer): 

640 return {'$ref': '#/components/schemas/{}'.format(self.get_component_name(serializer))} 

641 

642 def get_request_body(self, path, method): 

643 if method not in ('PUT', 'PATCH', 'POST'): 

644 return {} 

645 

646 self.request_media_types = self.map_parsers(path, method) 

647 

648 serializer = self.get_request_serializer(path, method) 

649 

650 if not isinstance(serializer, serializers.Serializer): 

651 item_schema = {} 

652 else: 

653 item_schema = self._get_reference(serializer) 

654 

655 return { 

656 'content': { 

657 ct: {'schema': item_schema} 

658 for ct in self.request_media_types 

659 } 

660 } 

661 

662 def get_responses(self, path, method): 

663 if method == 'DELETE': 

664 return { 

665 '204': { 

666 'description': '' 

667 } 

668 } 

669 

670 self.response_media_types = self.map_renderers(path, method) 

671 

672 serializer = self.get_response_serializer(path, method) 

673 

674 if not isinstance(serializer, serializers.Serializer): 

675 item_schema = {} 

676 else: 

677 item_schema = self._get_reference(serializer) 

678 

679 if is_list_view(path, method, self.view): 

680 response_schema = { 

681 'type': 'array', 

682 'items': item_schema, 

683 } 

684 paginator = self.get_paginator() 

685 if paginator: 

686 response_schema = paginator.get_paginated_response_schema(response_schema) 

687 else: 

688 response_schema = item_schema 

689 status_code = '201' if method == 'POST' else '200' 

690 return { 

691 status_code: { 

692 'content': { 

693 ct: {'schema': response_schema} 

694 for ct in self.response_media_types 

695 }, 

696 # description is a mandatory property, 

697 # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject 

698 # TODO: put something meaningful into it 

699 'description': "" 

700 } 

701 } 

702 

703 def get_tags(self, path, method): 

704 # If user have specified tags, use them. 

705 if self._tags: 

706 return self._tags 

707 

708 # First element of a specific path could be valid tag. This is a fallback solution. 

709 # PUT, PATCH, GET(Retrieve), DELETE: /user_profile/{id}/ tags = [user-profile] 

710 # POST, GET(List): /user_profile/ tags = [user-profile] 

711 if path.startswith('/'): 

712 path = path[1:] 

713 

714 return [path.split('/')[0].replace('_', '-')] 

715 

716 def _get_path_parameters(self, path, method): 

717 warnings.warn( 

718 "Method `_get_path_parameters()` has been renamed to `get_path_parameters()`. " 

719 "The old name will be removed in DRF v3.14.", 

720 RemovedInDRF314Warning, stacklevel=2 

721 ) 

722 return self.get_path_parameters(path, method) 

723 

724 def _get_filter_parameters(self, path, method): 

725 warnings.warn( 

726 "Method `_get_filter_parameters()` has been renamed to `get_filter_parameters()`. " 

727 "The old name will be removed in DRF v3.14.", 

728 RemovedInDRF314Warning, stacklevel=2 

729 ) 

730 return self.get_filter_parameters(path, method) 

731 

732 def _get_responses(self, path, method): 

733 warnings.warn( 

734 "Method `_get_responses()` has been renamed to `get_responses()`. " 

735 "The old name will be removed in DRF v3.14.", 

736 RemovedInDRF314Warning, stacklevel=2 

737 ) 

738 return self.get_responses(path, method) 

739 

740 def _get_request_body(self, path, method): 

741 warnings.warn( 

742 "Method `_get_request_body()` has been renamed to `get_request_body()`. " 

743 "The old name will be removed in DRF v3.14.", 

744 RemovedInDRF314Warning, stacklevel=2 

745 ) 

746 return self.get_request_body(path, method) 

747 

748 def _get_serializer(self, path, method): 

749 warnings.warn( 

750 "Method `_get_serializer()` has been renamed to `get_serializer()`. " 

751 "The old name will be removed in DRF v3.14.", 

752 RemovedInDRF314Warning, stacklevel=2 

753 ) 

754 return self.get_serializer(path, method) 

755 

756 def _get_paginator(self): 

757 warnings.warn( 

758 "Method `_get_paginator()` has been renamed to `get_paginator()`. " 

759 "The old name will be removed in DRF v3.14.", 

760 RemovedInDRF314Warning, stacklevel=2 

761 ) 

762 return self.get_paginator() 

763 

764 def _map_field_validators(self, field, schema): 

765 warnings.warn( 

766 "Method `_map_field_validators()` has been renamed to `map_field_validators()`. " 

767 "The old name will be removed in DRF v3.14.", 

768 RemovedInDRF314Warning, stacklevel=2 

769 ) 

770 return self.map_field_validators(field, schema) 

771 

772 def _map_serializer(self, serializer): 

773 warnings.warn( 

774 "Method `_map_serializer()` has been renamed to `map_serializer()`. " 

775 "The old name will be removed in DRF v3.14.", 

776 RemovedInDRF314Warning, stacklevel=2 

777 ) 

778 return self.map_serializer(serializer) 

779 

780 def _map_field(self, field): 

781 warnings.warn( 

782 "Method `_map_field()` has been renamed to `map_field()`. " 

783 "The old name will be removed in DRF v3.14.", 

784 RemovedInDRF314Warning, stacklevel=2 

785 ) 

786 return self.map_field(field) 

787 

788 def _map_choicefield(self, field): 

789 warnings.warn( 

790 "Method `_map_choicefield()` has been renamed to `map_choicefield()`. " 

791 "The old name will be removed in DRF v3.14.", 

792 RemovedInDRF314Warning, stacklevel=2 

793 ) 

794 return self.map_choicefield(field) 

795 

796 def _get_pagination_parameters(self, path, method): 

797 warnings.warn( 

798 "Method `_get_pagination_parameters()` has been renamed to `get_pagination_parameters()`. " 

799 "The old name will be removed in DRF v3.14.", 

800 RemovedInDRF314Warning, stacklevel=2 

801 ) 

802 return self.get_pagination_parameters(path, method) 

803 

804 def _allows_filters(self, path, method): 

805 warnings.warn( 

806 "Method `_allows_filters()` has been renamed to `allows_filters()`. " 

807 "The old name will be removed in DRF v3.14.", 

808 RemovedInDRF314Warning, stacklevel=2 

809 ) 

810 return self.allows_filters(path, method)