Skip to content
This repository has been archived by the owner on Oct 3, 2019. It is now read-only.

Release v2.0 #141

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .pycodestyle.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Revision History

## 1.4 (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`.
- **DEPRECIATION**: Renamed `ModelMixin.new` to `ModelMixin.create`.

## 1.3 (2017/01/24)

- Optimized the formatting of empty lists to create consistent diffs.
Expand Down
2 changes: 1 addition & 1 deletion yorm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
from .mixins import ModelMixin

__project__ = 'YORM'
__version__ = '1.3'
__version__ = '1.4b1'
8 changes: 8 additions & 0 deletions yorm/mixins.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import warnings

from yorm import utilities


class ModelMixin:
"""Adds ORM methods to a mapped class."""

@classmethod
def create(cls, *args, **kwargs):
return utilities.create(cls, *args, **kwargs)

@classmethod
def new(cls, *args, **kwargs):
msg = "ModelMixin.new() has been renamed to ModelMixin.create()"
warnings.warn(msg, DeprecationWarning)
return utilities.create(cls, *args, **kwargs)

@classmethod
Expand Down
4 changes: 2 additions & 2 deletions yorm/tests/test_mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
54 changes: 46 additions & 8 deletions yorm/tests/test_types_extended.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pylint: disable=missing-docstring,unused-variable,expression-not-assigned
# pylint: disable=missing-docstring,unused-variable,expression-not-assigned,singleton-comparison

import pytest
from expecter import expect
Expand Down Expand Up @@ -54,26 +54,64 @@ 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 MyAttributeDictionary(AttributeDictionary): pass
return MyAttributeDictionary

@pytest.fixture
def converter():
return SampleAttributeDictionary()
def cls_with_init():
@attr(var1=Integer)
class MyAttributeDictionary(AttributeDictionary):
def __init__(self, *args, var2="42", **kwargs):
super().__init__(*args, **kwargs)
self.var2 = var2
return MyAttributeDictionary

@pytest.fixture
def cls_with_args():
@attr(var1=Integer)
class MyAttributeDictionary(AttributeDictionary):
def __init__(self, var1, var2="42"):
super().__init__()
self.var1 = var1
self.var2 = var2
return MyAttributeDictionary

def it_cannot_be_used_directly():
with expect.raises(NotImplementedError):
AttributeDictionary.to_value(None)
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) == "42"

def it_allows_positional_arguments(cls_with_args):
converter = cls_with_args(99)

value = converter.to_value({'var1': 1})
print(value.__dict__)

expect(value.var1) == 1
expect(hasattr(value, 'var2')) == False


def describe_sorted_list():

Expand Down
11 changes: 11 additions & 0 deletions yorm/tests/test_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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

Expand All @@ -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

Expand Down
14 changes: 12 additions & 2 deletions yorm/types/extended.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
"""Converter classes for extensions to builtin types."""

import re
import logging

from .standard import String, Integer, Float, Boolean
from .containers import Dictionary, List
from ._representers import LiteralString


log = logging.getLogger(__name__)


# NULLABLE BUILTINS ############################################################


Expand Down Expand Up @@ -145,8 +149,14 @@ def create_default(cls):
msg = "AttributeDictionary class must be subclassed to use"
raise NotImplementedError(msg)

obj = cls.__new__(cls)
obj.__dict__ = obj
try:
obj = cls()
except TypeError as exc:
log.info("Default values in %s are not available when "
"positional arguments are used: %s", cls.__name__, exc)
obj = cls.__new__(cls)
obj.__dict__ = obj

return obj


Expand Down
44 changes: 23 additions & 21 deletions yorm/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -45,23 +44,26 @@ 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()

return 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)
Expand All @@ -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
Expand Down