diff --git a/.pycodestyle.ini b/.pycodestyle.ini index 85baad5..2326271 100644 --- a/.pycodestyle.ini +++ b/.pycodestyle.ini @@ -3,6 +3,7 @@ # E401 multiple imports on one line (checked by PyLint) # E402 module level import not at top of file (checked by PyLint) # E501: line too long (checked by PyLint) +# E701: multiple statements on one line (used to shorten test syntax) # E711: comparison to None (used to improve test style) # E712: comparison to True (used to improve test style) -ignore = E401,E402,E501,E711,E712 +ignore = E401,E402,E501,E701,E711,E712 diff --git a/CHANGELOG.md b/CHANGELOG.md index cc8caa8..3adc2bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Revision History +## 2.0 (unreleased) + +- Removed warnings about calling save/load unnecessarily. +- Allow keyword arguments to be passed to class construction via `new` and `find` utilities. +- Fixed missing attributes added in `__init__` on `AttributeDictionary`. +- **BREAKING**: Disallowed positional arguments to construct dictionary-like converters. +- **BREAKING**: Renamed `ModelMixin.new` to `ModelMixin.create`. + ## 1.3 (2017/01/24) - Optimized the formatting of empty lists to create consistent diffs. diff --git a/yorm/__init__.py b/yorm/__init__.py index 3b54cfe..fca2bc0 100644 --- a/yorm/__init__.py +++ b/yorm/__init__.py @@ -8,4 +8,4 @@ from .mixins import ModelMixin __project__ = 'YORM' -__version__ = '1.3' +__version__ = '2.0b1' diff --git a/yorm/mixins.py b/yorm/mixins.py index 21d5e2d..0f26324 100644 --- a/yorm/mixins.py +++ b/yorm/mixins.py @@ -5,7 +5,7 @@ class ModelMixin: """Adds ORM methods to a mapped class.""" @classmethod - def new(cls, *args, **kwargs): + def create(cls, *args, **kwargs): return utilities.create(cls, *args, **kwargs) @classmethod diff --git a/yorm/tests/test_mixins.py b/yorm/tests/test_mixins.py index c856f2b..59bf6e2 100644 --- a/yorm/tests/test_mixins.py +++ b/yorm/tests/test_mixins.py @@ -25,8 +25,8 @@ def mixed_instance(mixed_class): return mixed_class() @patch('yorm.mixins.utilities') - def it_adds_a_new_method(utilities, mixed_class): - mixed_class.new('foobar', overwrite=True) + def it_adds_a_create_method(utilities, mixed_class): + mixed_class.create('foobar', overwrite=True) expect(utilities.mock_calls) == [ call.create(mixed_class, 'foobar', overwrite=True) diff --git a/yorm/tests/test_types_extended.py b/yorm/tests/test_types_extended.py index 48d0381..a883f91 100644 --- a/yorm/tests/test_types_extended.py +++ b/yorm/tests/test_types_extended.py @@ -54,14 +54,21 @@ def it_converts_correctly(value, data): def describe_attribute_dictionary(): - @attr(var1=Integer) - @attr(var2=String) - class SampleAttributeDictionary(AttributeDictionary): - """Sample attribute dictionary.""" + @pytest.fixture + def cls(): + @attr(var1=Integer) + @attr(var2=String) + class SampleAttributeDictionary(AttributeDictionary): pass + return SampleAttributeDictionary @pytest.fixture - def converter(): - return SampleAttributeDictionary() + def cls_with_init(): + @attr(var1=Integer) + class SampleAttributeDictionaryWithInit(AttributeDictionary): + def __init__(self, *args, var2="2", **kwargs): + super().__init__(*args, **kwargs) + self.var2 = var2 + return SampleAttributeDictionaryWithInit def it_cannot_be_used_directly(): with expect.raises(NotImplementedError): @@ -69,8 +76,20 @@ def it_cannot_be_used_directly(): with expect.raises(NotImplementedError): AttributeDictionary.to_data(None) - def it_has_keys_available_as_attributes(converter): + def it_has_keys_available_as_attributes(cls): + converter = cls() + value = converter.to_value({'var1': 1, 'var2': "2"}) + + expect(value.var1) == 1 + expect(value.var2) == "2" + + def it_adds_extra_attributes_from_init(cls_with_init): + converter = cls_with_init() + + value = converter.to_value({'var1': 1}) + print(value.__dict__) + expect(value.var1) == 1 expect(value.var2) == "2" diff --git a/yorm/tests/test_utilities.py b/yorm/tests/test_utilities.py index 48d7e92..381e2ba 100644 --- a/yorm/tests/test_utilities.py +++ b/yorm/tests/test_utilities.py @@ -41,6 +41,7 @@ def it_creates_files(model_class): instance = utilities.create(model_class, 'foo', 'bar') expect(instance.__mapper__.exists) == True + expect(instance.__mapper__.modified) == False def it_requires_files_to_not_yet_exist(model_class, instance): instance.__mapper__.create() @@ -53,6 +54,11 @@ def it_can_overwrite_files(model_class, instance): utilities.create(model_class, 'foo', 'bar', overwrite=True) + def it_supports_keyword_arguments(model_class): + instance = utilities.create(model_class, 'foo', key='bar') + + expect(instance.__mapper__.exists) == True + def it_can_also_be_called_with_an_instance(instance): expect(yorm.create(instance)) == instance @@ -75,6 +81,11 @@ def it_allows_objects_to_be_created(model_class): expect(utilities.find(model_class, 'new', 'one', create=True)) == \ model_class('new', 'one') + def it_supports_keyword_arguments(model_class, instance): + instance.__mapper__.create() + + expect(utilities.find(model_class, 'foo', key='bar')) == instance + def it_can_also_be_called_with_an_instance(instance): expect(yorm.find(instance, create=True)) == instance diff --git a/yorm/types/extended.py b/yorm/types/extended.py index 7d47a4d..ce5fca6 100644 --- a/yorm/types/extended.py +++ b/yorm/types/extended.py @@ -145,9 +145,7 @@ def create_default(cls): msg = "AttributeDictionary class must be subclassed to use" raise NotImplementedError(msg) - obj = cls.__new__(cls) - obj.__dict__ = obj - return obj + return cls() class SortedList(List): diff --git a/yorm/utilities.py b/yorm/utilities.py index 7d550c8..8b38fdd 100644 --- a/yorm/utilities.py +++ b/yorm/utilities.py @@ -2,32 +2,31 @@ import inspect import logging -import warnings from . import common, exceptions log = logging.getLogger(__name__) -def create(class_or_instance, *args, overwrite=False): - """Create a new mapped object.""" - instance = _instantiate(class_or_instance, *args) - mapper = common.get_mapper(instance, expected=True) +def create(class_or_instance, *args, overwrite=False, **kwargs): + """Create a new mapped object. + + NOTE: Calling this function is unnecessary with 'auto_create' enabled. - if mapper.auto_create: - msg = "'create' is called automatically with 'auto_create' enabled" - warnings.warn(msg) + """ + instance = _instantiate(class_or_instance, *args, **kwargs) + mapper = common.get_mapper(instance, expected=True) if mapper.exists and not overwrite: msg = "{!r} already exists".format(mapper.path) raise exceptions.DuplicateMappingError(msg) - return save(instance) + return load(save(instance)) -def find(class_or_instance, *args, create=False): # pylint: disable=redefined-outer-name +def find(class_or_instance, *args, create=False, **kwargs): # pylint: disable=redefined-outer-name """Find a matching mapped object or return None.""" - instance = _instantiate(class_or_instance, *args) + instance = _instantiate(class_or_instance, *args, **kwargs) mapper = common.get_mapper(instance, expected=True) if mapper.exists: @@ -45,10 +44,13 @@ def match(cls, **kwargs): def load(instance): - """Force the loading of a mapped object's file.""" - mapper = common.get_mapper(instance, expected=True) + """Force the loading of a mapped object's file. - warnings.warn("'load' is called automatically") + NOTE: Calling this function is unnecessary. It exists for the + aesthetic purpose of having symmetry between save and load. + + """ + mapper = common.get_mapper(instance, expected=True) mapper.load() @@ -56,12 +58,12 @@ def load(instance): def save(instance): - """Save a mapped object to file.""" - mapper = common.get_mapper(instance, expected=True) + """Save a mapped object to file. - if mapper.auto_save: - msg = "'save' is called automatically with 'auto_save' enabled" - warnings.warn(msg) + NOTE: Calling this function is unnecessary with 'auto_save' enabled. + + """ + mapper = common.get_mapper(instance, expected=True) if mapper.deleted: msg = "{!r} was deleted".format(mapper.path) @@ -84,9 +86,9 @@ def delete(instance): return None -def _instantiate(class_or_instance, *args): +def _instantiate(class_or_instance, *args, **kwargs): if inspect.isclass(class_or_instance): - instance = class_or_instance(*args) + instance = class_or_instance(*args, **kwargs) else: assert not args instance = class_or_instance