Skip to content

Commit e4ce07f

Browse files
carltongibsonPierre Chiquet
authored and
Pierre Chiquet
committed
Extract method for manual_fields processing (encode#5633)
* Extract method for `manual_fields` processing Allows reuse of logic to replace Field instances in a field list by `Field.name`. Adds a utility function for the logic plus a wrapper method on `AutoSchema`. Closes encode#5632 * Manual fields suggestions (encode#2) * Use OrderedDict in inspectors * Move empty check to 'update_fields()' * Make 'update_fields()' an AutoSchema staticmethod * Add 'AutoSchema.get_manual_fields()' * Conform '.get_manual_fields()' to other methods * Add test for update_fields * Make sure `manual_fields` is a list. (As documented to be) * Add docs for new AutoSchema methods. * `get_manual_fields` * `update_fields` * Add release notes for PR.
1 parent 3a96e58 commit e4ce07f

File tree

4 files changed

+111
-8
lines changed

4 files changed

+111
-8
lines changed

docs/api-guide/schemas.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,31 @@ Return a list of `coreapi.Link()` instances, as returned by the `get_schema_fiel
603603

604604
Return a list of `coreapi.Link()` instances, as returned by the `get_schema_fields()` method of any filter classes used by the view.
605605

606+
### get_manual_fields(self, path, method)
607+
608+
Return a list of `coreapi.Field()` instances to be added to or replace generated fields. Defaults to (optional) `manual_fields` passed to `AutoSchema` constructor.
609+
610+
May be overridden to customise manual fields by `path` or `method`. For example, a per-method adjustment may look like this:
611+
612+
```python
613+
def get_manual_fields(self, path, method):
614+
"""Example adding per-method fields."""
615+
616+
extra_fields = []
617+
if method=='GET':
618+
extra_fields = # ... list of extra fields for GET ...
619+
if method=='POST':
620+
extra_fields = # ... list of extra fields for POST ...
621+
622+
manual_fields = super().get_manual_fields()
623+
return manual_fields + extra_fields
624+
```
625+
626+
### update_fields(fields, update_with)
627+
628+
Utility `staticmethod`. Encapsulates logic to add or replace fields from a list
629+
by `Field.name`. May be overridden to adjust replacement criteria.
630+
606631

607632
## ManualSchema
608633

docs/topics/release-notes.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ You can determine your currently installed version using `pip freeze`:
4040

4141
## 3.7.x series
4242

43+
### 3.7.4
44+
45+
**Date**: UNRELEASED
46+
47+
* Extract method for `manual_fields` processing [#5633][gh5633]
48+
49+
Allows for easier customisation of `manual_fields` processing, for example
50+
to provide per-method manual fields. `AutoSchema` adds `get_manual_fields`,
51+
as the intended override point, and a utility method `update_fields`, to
52+
handle by-name field replacement from a list, which, in general, you are not
53+
expected to override.
54+
55+
Note: `AutoSchema.__init__` now ensures `manual_fields` is a list.
56+
Previously may have been stored internally as `None`.
57+
58+
59+
[gh5633]: https://github.com/encode/django-rest-framework/issues/5633
60+
61+
4362

4463
### 3.7.3
4564

rest_framework/schemas/inspectors.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ def __init__(self, manual_fields=None):
172172
* `manual_fields`: list of `coreapi.Field` instances that
173173
will be added to auto-generated fields, overwriting on `Field.name`
174174
"""
175-
175+
if manual_fields is None:
176+
manual_fields = []
176177
self._manual_fields = manual_fields
177178

178179
def get_link(self, path, method, base_url):
@@ -181,11 +182,8 @@ def get_link(self, path, method, base_url):
181182
fields += self.get_pagination_fields(path, method)
182183
fields += self.get_filter_fields(path, method)
183184

184-
if self._manual_fields is not None:
185-
by_name = {f.name: f for f in fields}
186-
for f in self._manual_fields:
187-
by_name[f.name] = f
188-
fields = list(by_name.values())
185+
manual_fields = self.get_manual_fields(path, method)
186+
fields = self.update_fields(fields, manual_fields)
189187

190188
if fields and any([field.location in ('form', 'body') for field in fields]):
191189
encoding = self.get_encoding(path, method)
@@ -379,6 +377,31 @@ def get_filter_fields(self, path, method):
379377
fields += filter_backend().get_schema_fields(self.view)
380378
return fields
381379

380+
def get_manual_fields(self, path, method):
381+
return self._manual_fields
382+
383+
@staticmethod
384+
def update_fields(fields, update_with):
385+
"""
386+
Update list of coreapi.Field instances, overwriting on `Field.name`.
387+
388+
Utility function to handle replacing coreapi.Field fields
389+
from a list by name. Used to handle `manual_fields`.
390+
391+
Parameters:
392+
393+
* `fields`: list of `coreapi.Field` instances to update
394+
* `update_with: list of `coreapi.Field` instances to add or replace.
395+
"""
396+
if not update_with:
397+
return fields
398+
399+
by_name = OrderedDict((f.name, f) for f in fields)
400+
for f in update_with:
401+
by_name[f.name] = f
402+
fields = list(by_name.values())
403+
return fields
404+
382405
def get_encoding(self, path, method):
383406
"""
384407
Return the 'encoding' parameter to use for a given endpoint.

tests/test_schemas.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ def test_4605_regression(self):
516516
assert prefix == '/'
517517

518518

519-
class TestDescriptor(TestCase):
519+
class TestAutoSchema(TestCase):
520520

521521
def test_apiview_schema_descriptor(self):
522522
view = APIView()
@@ -528,7 +528,43 @@ def test_get_link_requires_instance(self):
528528
with pytest.raises(AssertionError):
529529
descriptor.get_link(None, None, None) # ???: Do the dummy arguments require a tighter assert?
530530

531-
def test_manual_fields(self):
531+
def test_update_fields(self):
532+
"""
533+
That updating fields by-name helper is correct
534+
535+
Recall: `update_fields(fields, update_with)`
536+
"""
537+
schema = AutoSchema()
538+
fields = []
539+
540+
# Adds a field...
541+
fields = schema.update_fields(fields, [
542+
coreapi.Field(
543+
"my_field",
544+
required=True,
545+
location="path",
546+
schema=coreschema.String()
547+
),
548+
])
549+
550+
assert len(fields) == 1
551+
assert fields[0].name == "my_field"
552+
553+
# Replaces a field...
554+
fields = schema.update_fields(fields, [
555+
coreapi.Field(
556+
"my_field",
557+
required=False,
558+
location="path",
559+
schema=coreschema.String()
560+
),
561+
])
562+
563+
assert len(fields) == 1
564+
assert fields[0].required is False
565+
566+
def test_get_manual_fields(self):
567+
"""That get_manual_fields is applied during get_link"""
532568

533569
class CustomView(APIView):
534570
schema = AutoSchema(manual_fields=[

0 commit comments

Comments
 (0)