Skip to content

Commit

Permalink
Adds lazy loading to deserialisation
Browse files Browse the repository at this point in the history
  • Loading branch information
Panaetius committed May 27, 2020
1 parent 4eb763e commit 09bcb7c
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 2 deletions.
11 changes: 10 additions & 1 deletion calamus/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# limitations under the License.
"""Marshmallow fields for use with Json-LD."""

import lazy_object_proxy
import marshmallow.fields as fields
from marshmallow.base import SchemaABC
from marshmallow import class_registry, utils
Expand Down Expand Up @@ -241,6 +242,7 @@ def schema(self):
context=context,
load_only=self._nested_normalized_option("load_only"),
dump_only=self._nested_normalized_option("dump_only"),
lazy=self.parent.lazy,
)
self._schema["to"][model] = self._schema["from"][rdf_type]
return self._schema
Expand Down Expand Up @@ -277,8 +279,12 @@ def load_single_entry(self, value, partial):
type_ = normalize_type(value["@type"])

schema = self.schema["from"][str(type_)]

if not schema:
ValueError("Type {} not found in {}.{}".format(value["@type"], type(self.parent), self.data_key))

if schema.lazy:
return lazy_object_proxy.Proxy(lambda: schema.load(value, unknown=self.unknown, partial=partial))
return schema.load(value, unknown=self.unknown, partial=partial)

def _load(self, value, data, partial=None, many=False):
Expand Down Expand Up @@ -335,7 +341,10 @@ def _dereference_flattened(self, value, attr, **kwargs):

def _deserialize(self, value, attr, data, **kwargs):
"""Deserialize nested object."""
if "flattened" in kwargs and kwargs["flattened"]:

kwargs["_ignore_lazy"] = False

if kwargs.get("flattened", False):
# could be id references, dereference them to continue deserialization
value = self._dereference_flattened(value, attr, **kwargs)

Expand Down
7 changes: 7 additions & 0 deletions calamus/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ def __init__(self, meta, *args, **kwargs):
class JsonLDSchema(Schema):
"""Schema for a JsonLD class.
Args:
flattened (bool): If the jsonld should be loaded/dumped in flattened form
lazy (bool): Enables lazy loading of nested attributes
Example:
.. code-block:: python
Expand Down Expand Up @@ -90,6 +94,7 @@ def __init__(
partial=False,
unknown=None,
flattened=False,
lazy=False,
_all_objects=None,
):
super().__init__(
Expand All @@ -105,6 +110,7 @@ def __init__(
)

self.flattened = flattened
self.lazy = lazy
self._all_objects = _all_objects

if not self.opts.rdf_type or not self.opts.model:
Expand Down Expand Up @@ -255,6 +261,7 @@ def _deserialize(

d_kwargs["_all_objects"] = self._all_objects
d_kwargs["flattened"] = self.flattened
d_kwargs["lazy"] = self.lazy
getter = lambda val: field_obj.deserialize(val, field_name, data, **d_kwargs)
value = self._call_and_store(
getter_func=getter, data=raw_value, field_name=field_name, error_store=error_store, index=index,
Expand Down
33 changes: 32 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pyld = "^2.0.2"
sphinx = {version = "^3.0.3", optional = true}
sphinx-rtd-theme = {version = "^0.4.3", optional = true}
sphinxcontrib-spelling = {version = "^5.0.0", optional = true}
lazy-object-proxy = "^1.4.3"

[tool.poetry.dev-dependencies]
pytest = "^5.2"
Expand Down
77 changes: 77 additions & 0 deletions tests/test_deserialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,80 @@ class Meta:

assert book2._id == "http://example.com/books/2"
assert book2.name == "We Are Legion (We Are Bob)"


def test_lazy_deserialization():
"""Tests that lazy deserialization works."""
import lazy_object_proxy

class Genre:
def __init__(self, name):
self.name = name

def test(self):
return self.name

def __call__(self):
return self.name

class Book:
def __init__(self, name, genre):
self.name = name
self.genre = genre

class Author:
def __init__(self, name, books):
self.name = name
self.books = books

schema = fields.Namespace("https://schema.org/")

class GenreSchema(JsonLDSchema):
_id = fields.BlankNodeId()
name = fields.String(schema.name)

class Meta:
rdf_type = schema.Genre
model = Genre

class BookSchema(JsonLDSchema):
_id = fields.BlankNodeId()
name = fields.String(schema.name)
genre = fields.Nested(schema.genre, GenreSchema)

class Meta:
rdf_type = schema.Book
model = Book

class AuthorSchema(JsonLDSchema):
_id = fields.BlankNodeId()
name = fields.String(schema.name)
books = fields.Nested(schema.books, BookSchema, many=True)

class Meta:
rdf_type = schema.Author
model = Author

data = {
"@type": ["https://schema.org/Author"],
"https://schema.org/books": [
{
"@type": ["https://schema.org/Book"],
"https://schema.org/genre": {"@type": ["https://schema.org/Genre"], "https://schema.org/name": "Novel"},
"https://schema.org/name": "Don Quixote",
}
],
"https://schema.org/name": "Miguel de Cervantes",
}

a = AuthorSchema(lazy=True).load(data)

assert a.name == "Miguel de Cervantes"
book = a.books[0]

assert isinstance(book, lazy_object_proxy.Proxy)
assert " wrapping " not in repr(book) # make sure proxy is not evaluated yet

assert book.name == "Don Quixote"
assert " wrapping " in repr(book) # make sure proxy is not evaluated yet
assert book.genre.name == "Novel"

0 comments on commit 09bcb7c

Please sign in to comment.