Skip to content

Commit e4e2ebc

Browse files
committed
improve wildcard parsing performances by dropping its complexity to O(n)
1 parent 6a81133 commit e4e2ebc

File tree

2 files changed

+48
-36
lines changed

2 files changed

+48
-36
lines changed

flask_restplus/fields.py

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -711,13 +711,11 @@ class Wildcard(Raw):
711711
712712
:param cls_or_instance: The field type the list will contain.
713713
'''
714-
exclude = []
715-
# keep a track of the last object
716-
_idx = 0
714+
exclude = set()
717715
# cache the flat object
718716
_flat = None
719717
_obj = None
720-
_cache = []
718+
_cache = set()
721719
_last = None
722720

723721
def __init__(self, cls_or_instance, **kwargs):
@@ -733,43 +731,63 @@ def __init__(self, cls_or_instance, **kwargs):
733731
self.container = cls_or_instance
734732

735733
def _flatten(self, obj):
736-
if obj == self._obj and self._flat:
734+
if obj is None:
735+
return None
736+
if obj == self._obj and self._flat is not None:
737737
return self._flat
738738
if isinstance(obj, dict):
739-
self._flat = obj.items()
739+
self._flat = [x for x in obj.items()]
740740
else:
741-
attributes = inspect.getmembers(obj, lambda a: not(inspect.isroutine(a)))
742-
self._flat = [x for x in attributes if match_attributes(x)]
743741

744-
self._idx = 0
745-
self._cache = []
742+
def __match_attributes(attribute):
743+
attr_name, attr_obj = attribute
744+
if inspect.isroutine(attr_obj) or \
745+
(attr_name.startswith('__') and attr_name.endswith('__')):
746+
return False
747+
return True
748+
749+
attributes = inspect.getmembers(obj)
750+
self._flat = [x for x in attributes if __match_attributes(x)]
751+
752+
self._cache = set()
746753
self._obj = obj
747754
return self._flat
748755

749756
@property
750757
def key(self):
751758
return self._last
752759

760+
def reset(self):
761+
self.exclude = set()
762+
self._flat = None
763+
self._obj = None
764+
self._cache = set()
765+
self._last = None
766+
753767
def output(self, key, obj, ordered=False):
754-
flat = self._flatten(obj)
755768
value = None
756769
reg = fnmatch.translate(key)
757770

758-
for idx, (objkey, val) in enumerate(flat):
759-
if idx < self._idx:
760-
continue
761-
if objkey not in self._cache and \
762-
objkey not in self.exclude and \
763-
re.match(reg, objkey, re.IGNORECASE):
764-
value = val
765-
self._cache.append(objkey)
766-
self._last = objkey
767-
self._idx = idx
768-
break
771+
if self._flatten(obj):
772+
while True:
773+
try:
774+
# we are using pop() so that we don't
775+
# loop over the whole object every time dropping the
776+
# complexity to O(n)
777+
(objkey, val) = self._flat.pop()
778+
if objkey not in self._cache and \
779+
objkey not in self.exclude and \
780+
re.match(reg, objkey, re.IGNORECASE):
781+
value = val
782+
self._cache.add(objkey)
783+
self._last = objkey
784+
break
785+
except IndexError:
786+
break
769787

770788
if value is None:
771789
if self.default is not None:
772-
return self.default
790+
return self.container.format(self.default)
773791
return None
774792

775793
return self.container.format(value)
@@ -786,10 +804,3 @@ def clone(self, mask=None):
786804
if mask:
787805
model = mask.apply(model)
788806
return self.__class__(model, **kwargs)
789-
790-
791-
def match_attributes(attribute):
792-
attr_name, _ = attribute
793-
if attr_name.startswith('__') and attr_name.endswith('__'):
794-
return False
795-
return True

flask_restplus/marshalling.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ def marshal(data, fields, envelope=None, skip_none=False, mask=None, ordered=Fal
7171
field = make(val)
7272
is_wildcard = isinstance(field, Wildcard)
7373
# exclude already parsed keys from the wildcard
74-
if is_wildcard and keys:
75-
for tmp in keys:
76-
if tmp not in field.exclude:
77-
field.exclude.append(tmp)
78-
keys = []
74+
if is_wildcard:
75+
field.reset()
76+
if keys:
77+
field.exclude |= set(keys)
78+
keys = []
7979
value = field.output(dkey, data)
8080
if is_wildcard:
8181

@@ -88,7 +88,8 @@ def _append(k, v):
8888
_append(key, value)
8989
while True:
9090
value = field.output(dkey, data, ordered=ordered)
91-
if value is None or value == field.default:
91+
if value is None or \
92+
value == field.container.format(field.default):
9293
break
9394
key = field.key
9495
_append(key, value)

0 commit comments

Comments
 (0)