Skip to content

Commit

Permalink
better support for eager loading
Browse files Browse the repository at this point in the history
  • Loading branch information
joamag committed Mar 23, 2016
1 parent 107b0f4 commit d5d6a5a
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 14 deletions.
76 changes: 66 additions & 10 deletions src/appier/model.py
Expand Up @@ -451,9 +451,10 @@ def singleton(

@classmethod
def get(cls, *args, **kwargs):
fields, eager, map, rules, meta, build, fill, skip, limit, sort, raise_e = cls._get_attrs(kwargs, (
fields, eager, eager_l, map, rules, meta, build, fill, skip, limit, sort, raise_e = cls._get_attrs(kwargs, (
("fields", None),
("eager", None),
("eager_l", True),
("map", False),
("rules", True),
("meta", False),
Expand All @@ -465,6 +466,7 @@ def get(cls, *args, **kwargs):
("raise_e", True)
))

if eager_l: eager = cls._eager_b(eager)
fields = cls._sniff(fields, rules = rules)
collection = cls._collection()
model = collection.find_one(
Expand All @@ -486,14 +488,15 @@ def get(cls, *args, **kwargs):
cls.types(model)
if fill: cls.fill(model)
if build: cls.build(model, map = map, rules = rules, meta = meta)
if eager: model = cls._eager(model, eager)
if eager: model = cls._eager(model, eager, map = map)
return model if map else cls.old(model = model, safe = False)

@classmethod
def find(cls, *args, **kwargs):
fields, eager, map, rules, meta, build, fill, skip, limit, sort = cls._get_attrs(kwargs, (
fields, eager, eager_l, map, rules, meta, build, fill, skip, limit, sort = cls._get_attrs(kwargs, (
("fields", None),
("eager", None),
("eager_l", False),
("map", False),
("rules", True),
("meta", False),
Expand All @@ -504,6 +507,7 @@ def find(cls, *args, **kwargs):
("sort", None)
))

if eager_l: eager = cls._eager_b(eager)
cls._find_s(kwargs)
cls._find_d(kwargs)

Expand Down Expand Up @@ -1154,6 +1158,33 @@ def immutables(cls):
cls._immutables = immutables
return immutables

@classmethod
def eagers(cls):
# in case the eagers are already "cached" in the current
# class (fast retrieval) returns immediately
if "_eagers" in cls.__dict__: return cls._eagers

# creates the list that will hold the various names that are
# meant to be eager values in the data source
eagers = []

# retrieves the map containing the definition of the class with
# the name of the fields associated with their definition
definition = cls.definition()

# iterate over all the names in the definition to retrieve their
# definition and check if their are of type eager
for name in definition:
_definition = cls.definition_n(name)
is_eager = _definition.get("eager", False)
if not is_eager: continue
eagers.append(name)

# saves the eagers list under the class and then
# returns the sequence to the caller method
cls._eagers = eagers
return eagers

@classmethod
def default(cls):
# in case the default are already "cached" in the current
Expand Down Expand Up @@ -1269,12 +1300,12 @@ def _name(cls):
return name

@classmethod
def _eager(cls, model, names):
def _eager(cls, model, names, map = False):
# verifies if the provided model instance is a sequence and if
# that's the case runs the recursive eager loading of names and
# returns the resulting sequence to the caller method
is_list = isinstance(model, (list, tuple))
if is_list: return [cls._eager(_model, names) for _model in model]
if is_list: return [cls._eager(_model, names, map = map) for _model in model]

# iterates over the complete set of names that are meant to be
# eager loaded from the model and runs the "resolution" process
Expand All @@ -1283,20 +1314,20 @@ def _eager(cls, model, names):
_model = model
for part in name.split("."):
is_sequence = type(_model) in (list, tuple)
if is_sequence: _model = [cls._res(value, part) for value in _model]
else: _model = cls._res(_model, part)
if is_sequence: _model = [cls._res(value, part, map = map) for value in _model]
else: _model = cls._res(_model, part, map = map)
if not _model: break

# returns the resulting model to the caller method, most of the
# times this model should have not been touched
return model

@classmethod
def _res(cls, model, part):
def _res(cls, model, part, map = False):
value = model[part]
is_reference = isinstance(value, TYPE_REFERENCES)
if not value and not is_reference: return value
if is_reference: model[part] = value.resolve()
if is_reference: model[part] = value.resolve(map = map)
model = model[part]
return model

Expand Down Expand Up @@ -1522,6 +1553,31 @@ def _increment(cls, name):
})
return value["seq"]

@classmethod
def _eager_b(cls, eager):
"""
Builds the provided list of eager values, preparing them
according to the current model rules.
The composition process includes the extension of the provided
sequence of eager values with the base ones defined in the
model, if not handled correctly this is an expensive operation.
:type eager: List/Tuple
:param eager: The base sequence containing the various fields
that should be eagerly loaded for the operation.
:rtype: Tuple
:return: The "final" resolved tuple that may be used for the eager
loaded operation performance.
"""

eager = list(eager) if eager else []
eagers = cls.eagers()
eager.extend(eagers)
if not eager: return eager
eager = tuple(set(eager))
return eager

@property
def request(self):
return common.base().get_request()
Expand Down Expand Up @@ -1551,7 +1607,7 @@ def build_m(self, model = None, rules = True):
This method should me used carefully to avoid validation
problems and other side effects.
:type model: Map
:type model: Dictionary
:param model: The model map to be used for the build
operation in case none is specified the currently set
model is used instead.
Expand Down
14 changes: 10 additions & 4 deletions src/appier/typesf.py
Expand Up @@ -468,12 +468,12 @@ def value(self):
if is_empty: return None
return self._type(self.id)

def resolve(self, strict = False):
def resolve(self, map = False, strict = False):
# verifies if the underlying object reference exists
# in the current names dictionary and if it exists
# verifies if it's valid (value is valid) if that's
# the case returns the current value immediately
exists = "_object" in self.__dict__
exists = "_object" in self.__dict__ and not map
if exists and self._object: return self._object

# verifies if there's an id value currently set in
Expand All @@ -492,8 +492,14 @@ def resolve(self, strict = False):
kwargs = {
name : self._target.cast(name, self.id)
}
if map: kwargs["map"] = map
_object = self._target.get(raise_e = strict, **kwargs)

# in case the map flag is active the retrieved value is returned
# immediately, not valid to store a map base retrieval as the
# resolved object as the serialization process is not compatible
if map: return _object

# sets the resolved object (using the current id attribute)
# in the current instance's dictionary and then returns this
# value to the caller method as the resolved value
Expand Down Expand Up @@ -583,8 +589,8 @@ def map_v(self, *args, **kwargs):
def list(self):
return [object.value() for object in self.objects]

def resolve(self):
return [object.resolve() for object in self.objects]
def resolve(self, map = False):
return [object.resolve(map = map) for object in self.objects]

def is_empty(self):
ids_l = len(self.ids)
Expand Down

0 comments on commit d5d6a5a

Please sign in to comment.