Skip to content

Commit

Permalink
Remove partial load feature
Browse files Browse the repository at this point in the history
  • Loading branch information
lafrech committed Apr 26, 2020
1 parent 06a11fd commit e5ebd3f
Show file tree
Hide file tree
Showing 5 changed files with 10 additions and 157 deletions.
104 changes: 0 additions & 104 deletions tests/test_data_proxy.py
Expand Up @@ -365,110 +365,6 @@ class MySchema(EmbeddedSchema):
d.set('with_max', 100)
assert exc.value.args[0] == ['Must be less than or equal to 99.']

def test_partial(self):

class MySchema(EmbeddedSchema):
with_default = fields.StrField(default='default_value')
normal = fields.StrField()
loaded = fields.StrField()
loaded_but_empty = fields.StrField()
normal_with_attribute = fields.StrField(attribute='in_mongo_field')

MyDataProxy = data_proxy_factory('My', MySchema())
d = MyDataProxy()
d.from_mongo({'loaded': "foo", 'loaded_but_empty': missing}, partial=True)
assert d.partial is True
for field in ('with_default', 'normal'):
val = d._data[field]
with pytest.raises(exceptions.FieldNotLoadedError):
d.get(field)
assert d._data[field] == val
with pytest.raises(exceptions.FieldNotLoadedError):
d.set(field, "test")
assert d._data[field] == val
with pytest.raises(exceptions.FieldNotLoadedError):
d.delete(field)
assert d._data[field] == val
for field in ('normal', 'in_mongo_field'):
val = d._data[field]
with pytest.raises(exceptions.FieldNotLoadedError):
d.get_by_mongo_name(field)
assert d._data[field] == val
with pytest.raises(exceptions.FieldNotLoadedError):
d.set_by_mongo_name(field, "test")
assert d._data[field] == val
with pytest.raises(exceptions.FieldNotLoadedError):
d.delete_by_mongo_name(field)
assert d._data[field] == val
assert d.get('loaded') == "foo"
assert d.get('loaded_but_empty') is missing
d.set('loaded_but_empty', "bar")
assert d.get('loaded_but_empty') == "bar"
d.delete('loaded')
# Can still access the deleted field
assert d.get('loaded') is missing

# Same test, but using `load`
d = MyDataProxy()
d.load({'loaded': "foo", 'loaded_but_empty': missing}, partial=True)
assert d.partial is True
for field in ('with_default', 'normal'):
with pytest.raises(exceptions.FieldNotLoadedError):
d.get(field)
with pytest.raises(exceptions.FieldNotLoadedError):
d.set(field, "test")
with pytest.raises(exceptions.FieldNotLoadedError):
d.delete(field)
assert d.get('loaded') == "foo"
assert d.get('loaded_but_empty') is missing
d.set('loaded_but_empty', "bar")
assert d.get('loaded_but_empty') == "bar"
d.delete('loaded')
# Can still access the deleted field
assert d.get('loaded') is missing

# Not partial
d = MyDataProxy()
d.from_mongo({'loaded': "foo", 'loaded_but_empty': missing})
assert d.partial is False
assert d.get('with_default') == 'default_value'
assert d.get('normal') is missing
assert d.get('loaded') == "foo"
assert d.get('loaded_but_empty') == missing
# Same test with load
d = MyDataProxy()
d.load({'loaded': "foo", 'loaded_but_empty': missing})
assert d.partial is False
assert d.partial is False
assert d.get('with_default') == 'default_value'
assert d.get('normal') is missing
assert d.get('loaded') == "foo"
assert d.get('loaded_but_empty') == missing

# Partial, then not partial
d = MyDataProxy()
d.from_mongo({'loaded': "foo", 'loaded_but_empty': missing}, partial=True)
assert d.partial is True
d.from_mongo({'loaded': "foo", 'loaded_but_empty': missing})
assert d.partial is False
# Same test with load
d = MyDataProxy()
d.load({'loaded': "foo", 'loaded_but_empty': missing}, partial=True)
assert d.partial is True
d.load({'loaded': "foo", 'loaded_but_empty': missing})
assert d.partial is False

# Partial, then update turns it into not partial
d = MyDataProxy()
d.from_mongo({'loaded': "foo", 'loaded_but_empty': missing}, partial=True)
assert len(d.not_loaded_fields) == 3
d.update({'with_default': 'test', 'normal_with_attribute': 'foo'})
assert len(d.not_loaded_fields) == 1
assert d.partial is True
d.update({'normal': 'test'})
assert d.partial is False
assert not d.not_loaded_fields

def test_required_validate(self):

@self.instance.register
Expand Down
2 changes: 0 additions & 2 deletions umongo/__init__.py
Expand Up @@ -21,7 +21,6 @@
UpdateError,
DeleteError,
NotCreatedError,
FieldNotLoadedError,
NoneReferenceError,
UnknownFieldInDBError,
)
Expand Down Expand Up @@ -58,7 +57,6 @@
'UpdateError',
'DeleteError',
'NotCreatedError',
'FieldNotLoadedError',
'NoneReferenceError',
'UnknownFieldInDBError',

Expand Down
48 changes: 6 additions & 42 deletions umongo/data_proxy.py
@@ -1,7 +1,7 @@
from marshmallow import ValidationError, missing

from .abstract import BaseDataObject
from .exceptions import FieldNotLoadedError, UnknownFieldInDBError
from .exceptions import UnknownFieldInDBError
from .i18n import gettext as _


Expand All @@ -10,23 +10,17 @@

class BaseDataProxy:

__slots__ = ('not_loaded_fields', '_data', '_modified_data')
__slots__ = ('_data', '_modified_data')
schema = None
_fields = None
_fields_from_mongo_key = None

def __init__(self, data=None):
self.not_loaded_fields = set()
# Inside data proxy, data are stored in mongo world representation
self._modified_data = set()
self._data = {}
self.load(data or {})

@property
def partial(self):
# TODO: rename to `is_partialy_loaded` ?
return bool(self.not_loaded_fields)

def to_mongo(self, update=False):
if update:
return self._to_mongo_update()
Expand Down Expand Up @@ -59,7 +53,7 @@ def _to_mongo_update(self):
mongo_data['$unset'] = {k: "" for k in unset_data}
return mongo_data or None

def from_mongo(self, data, partial=False):
def from_mongo(self, data):
self._data = {}
for key, val in data.items():
try:
Expand All @@ -70,10 +64,6 @@ def from_mongo(self, data, partial=False):
.format(key=key, cls=self.__class__.__name__)
))
self._data[key] = field.deserialize_from_mongo(val)
if partial:
self._collect_partial_fields(data.keys(), as_mongo_fields=True)
else:
self.not_loaded_fields.clear()
self.clear_modified()
self._add_missing_fields()

Expand All @@ -87,35 +77,24 @@ def update(self, data):
# Always use marshmallow partial load to skip required checks
loaded_data = self.schema.load(data, partial=True)
self._data.update(loaded_data)
if self.not_loaded_fields:
for k in loaded_data:
self.not_loaded_fields.discard(self._fields_from_mongo_key[k])
for key in loaded_data:
self._mark_as_modified(key)

def load(self, data, partial=False):
def load(self, data):
# Always use marshmallow partial load to skip required checks
loaded_data = self.schema.load(data, partial=True)
self._data = loaded_data
# Map the modified fields list on the the loaded data
self.clear_modified()
for key in loaded_data:
self._mark_as_modified(key)
if partial:
self._collect_partial_fields(data)
else:
self.not_loaded_fields.clear()
# Must be done last given it modify `loaded_data`
# TODO: mark added missing fields as modified?
self._add_missing_fields()

def get_by_mongo_name(self, name):
if self._fields_from_mongo_key[name] in self.not_loaded_fields:
raise FieldNotLoadedError(name)
return self._data[name]

def set_by_mongo_name(self, name, value):
if self._fields_from_mongo_key[name] in self.not_loaded_fields:
raise FieldNotLoadedError(name)
self._data[name] = value
self._mark_as_modified(name)

Expand All @@ -126,8 +105,6 @@ def _get_field(self, name, to_raise):
if name not in self._fields:
raise to_raise(name)
field = self._fields[name]
if field in self.not_loaded_fields:
raise FieldNotLoadedError(name)
name = field.attribute or name
return name, field

Expand Down Expand Up @@ -188,15 +165,6 @@ def is_modified(self):
for v in self._data.values())
)

def _collect_partial_fields(self, loaded_fields, as_mongo_fields=False):
if as_mongo_fields:
self.not_loaded_fields = set(
self._fields_from_mongo_key[k]
for k in self._fields_from_mongo_key.keys() - set(loaded_fields))
else:
self.not_loaded_fields = set(
self._fields[k] for k in self._fields.keys() - set(loaded_fields))

def _add_missing_fields(self):
# TODO: we should be able to do that by configuring marshmallow...
for name, field in self._fields.items():
Expand Down Expand Up @@ -258,7 +226,7 @@ def _to_mongo(self):
mongo_data.update(self._additional_data)
return mongo_data

def from_mongo(self, data, partial=False):
def from_mongo(self, data):
self._data = {}
for key, val in data.items():
try:
Expand All @@ -267,10 +235,6 @@ def from_mongo(self, data, partial=False):
self._additional_data[key] = val
else:
self._data[key] = field.deserialize_from_mongo(val)
if partial:
self._collect_partial_fields(data.keys(), as_mongo_fields=True)
else:
self.not_loaded_fields.clear()
self.clear_modified()
self._add_missing_fields()

Expand Down
9 changes: 4 additions & 5 deletions umongo/document.py
Expand Up @@ -201,7 +201,7 @@ def dbref(self):
return DBRef(collection=self.collection.name, id=self.pk)

@classmethod
def build_from_mongo(cls, data, partial=False, use_cls=False):
def build_from_mongo(cls, data, use_cls=False):
"""
Create a document instance from MongoDB data
Expand All @@ -213,17 +213,16 @@ def build_from_mongo(cls, data, partial=False, use_cls=False):
if use_cls and '_cls' in data:
cls = cls.opts.instance.retrieve_document(data['_cls'])
doc = cls()
doc.from_mongo(data, partial=partial)
doc.from_mongo(data)
return doc

def from_mongo(self, data, partial=False):
def from_mongo(self, data):
"""
Update the document with the MongoDB data
:param data: data as retrieved from MongoDB
"""
# TODO: handle partial
self._data.from_mongo(data, partial=partial)
self._data.from_mongo(data)
self.is_created = True

def to_mongo(self, update=False):
Expand Down
4 changes: 0 additions & 4 deletions umongo/exceptions.py
Expand Up @@ -42,10 +42,6 @@ class NotCreatedError(UMongoError):
"""Document does not exist in database"""


class FieldNotLoadedError(UMongoError):
"""Accessing a field not loaded after partial load"""


class NoneReferenceError(UMongoError):
"""Retrieving a None reference"""

Expand Down

0 comments on commit e5ebd3f

Please sign in to comment.