From d4775be7686fa7eabc2f177d7762a205518f021b Mon Sep 17 00:00:00 2001 From: Ben Steadman Date: Thu, 7 Mar 2019 08:12:39 +0000 Subject: [PATCH 1/3] implement dot_escape kwarg for allowing dot notation in non-nested fields --- flask_restplus/fields.py | 22 +++++++++++++++---- flask_restplus/marshalling.py | 2 ++ tests/test_fields.py | 12 +++++++++++ tests/test_marshalling.py | 40 +++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/flask_restplus/fields.py b/flask_restplus/fields.py index 7e75700a..83c2a79c 100644 --- a/flask_restplus/fields.py +++ b/flask_restplus/fields.py @@ -42,14 +42,22 @@ def is_indexable_but_not_string(obj): return not hasattr(obj, "strip") and hasattr(obj, "__iter__") -def get_value(key, obj, default=None): +def get_value(key, obj, default=None, dot_escape=False): '''Helper for pulling a keyed value off various types of objects''' if isinstance(key, int): return _get_value_for_key(key, obj, default) elif callable(key): return key(obj) else: - return _get_value_for_keys(key.split('.'), obj, default) + keys = ( + [ + k.replace("\.", ".") + for k in re.split(r"(? Date: Fri, 8 Mar 2019 07:28:12 +0000 Subject: [PATCH 2/3] dot_escape documentation --- doc/marshalling.rst | 52 +++++++++++++++++++++++++++++++++++ flask_restplus/fields.py | 21 +++++++++++++- flask_restplus/marshalling.py | 9 ++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/doc/marshalling.rst b/doc/marshalling.rst index 098c9369..0ecc66dc 100644 --- a/doc/marshalling.rst +++ b/doc/marshalling.rst @@ -107,6 +107,58 @@ you can specify a default value to return instead of :obj:`None`. } +Nested Field Names +------------------ + +By default, '.' is used as a separator to indicate nested properties when values +are fetched from objects: + +.. code-block:: python + + data = { + 'address': { + 'country': 'UK', + 'postcode': 'CO1' + } + } + + model = { + 'address.country': fields.String, + 'address.postcode': fields.String, + } + + marshal(data, model) + {'address.country': 'UK', 'address.postcode': 'CO1'} + +If the object to be marshalled has '.' characters within a single field name, +nested property access can be prevented by passing `dot_escape=True` and escaping +the '.' with a backslash: + +.. code-block:: python + + data = { + 'address.country': 'UK', + 'address.postcode': 'CO1', + 'user.name': { + 'first': 'John', + 'last': 'Smith', + } + } + + model = { + 'address\.country': fields.String(dot_escape=True), + 'address\.postcode': fields.String(dot_escape=True), + 'user\.name.first': fields.String(dot_escape=True), + 'user\.name.last': fields.String(dot_escape=True), + } + + marshal(data, model) + {'address.country': 'UK', + 'address.postcode': 'CO1', + 'user.name.first': 'John', + 'user.name.last': 'Smith'} + + Custom Fields & Multiple Values ------------------------------- diff --git a/flask_restplus/fields.py b/flask_restplus/fields.py index 83c2a79c..622a5fc1 100644 --- a/flask_restplus/fields.py +++ b/flask_restplus/fields.py @@ -43,7 +43,24 @@ def is_indexable_but_not_string(obj): def get_value(key, obj, default=None, dot_escape=False): - '''Helper for pulling a keyed value off various types of objects''' + '''Helper for pulling a keyed value off various types of objects + + :param bool dot_escape: Allow escaping of '.' character in field names to + indicate non-nested property access + + >>> data = {'a': 'foo', b: {'c': 'bar', 'd.e': 'baz'}}} + >>> get_value('a', data) + 'foo' + + >>> get_value('b.c', data) + 'bar' + + >>> get_value('x', data, default='foobar') + 'foobar' + + >>> get_value('b.d\.e', data, dot_escape=True) + 'baz' + ''' if isinstance(key, int): return _get_value_for_key(key, obj, default) elif callable(key): @@ -112,6 +129,8 @@ class Raw(object): :param bool readonly: Is the field read only ? (for documentation purpose) :param example: An optional data example (for documentation purpose) :param callable mask: An optional mask function to be applied to output + :param bool dot_escape: Allow escaping of '.' character in field names to + indicate non-nested property access ''' #: The JSON/Swagger schema type __schema_type__ = 'object' diff --git a/flask_restplus/marshalling.py b/flask_restplus/marshalling.py index e0fe277e..007c00d5 100644 --- a/flask_restplus/marshalling.py +++ b/flask_restplus/marshalling.py @@ -149,6 +149,15 @@ def _marshal(data, fields, envelope=None, skip_none=False, mask=None, ordered=Fa >>> marshal(data, mfields, skip_none=True, ordered=True) OrderedDict([('a', 100)]) + >>> data = { 'a': 100, 'b.c': 'foo', 'd': None } + >>> mfields = { + 'a': fields.Raw, + 'b\.c': fields.Raw(dot_escape=True), + 'd': fields.Raw + } + + >>> marshal(data, mfields) + {'a': 100, 'b.c': 'foo', 'd': None} """ # ugly local import to avoid dependency loop from .fields import Wildcard From b30acdc1f89a14bfd82647031b04440c0e5d089f Mon Sep 17 00:00:00 2001 From: Ben Steadman Date: Fri, 8 Mar 2019 07:46:15 +0000 Subject: [PATCH 3/3] dot_escape CHANGELOG --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7bd08370..3df808cd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,7 @@ Current ------- - Ensure `basePath` is always a path +- Add `dot_escape` kwarg to `fields.Raw` to prevent nested property access 0.12.1 (2018-09-28) -------------------