Skip to content

Commit

Permalink
Refactor, changing attribute to field where appropriate.
Browse files Browse the repository at this point in the history
  • Loading branch information
Warren Smith committed Nov 25, 2016
1 parent 50356c0 commit cce8019
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 75 deletions.
52 changes: 26 additions & 26 deletions staticmodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@
A model is defined using a class definition to create a sub-class
of StaticModel.
Member attribute names are declared with the `_attr_names` class
Member field names are declared with the `_field_names` class
attribute. The value should be a sequence of strings.
Members are declared with an uppercase class attribute. Member values
should be sequences with the same number of items as the value of
`_attr_names`.
`_field_names`.
When the class definition is processed by the underlying metaclass,
the **members become instances of the model**.
the **members are transformed into instances of the model**.
>>> from staticmodel import StaticModel
>>>
>>>
>>> class Animal(StaticModel):
... _attr_names = 'name', 'description', 'domesticated'
... _field_names = 'name', 'description', 'domesticated'
...
... DOG = 'Spot', "Man's best friend", True
... CAT = 'Fluffy', "Man's gracious overlord", True
Expand Down Expand Up @@ -66,9 +66,9 @@
... ANTELOPE = 'Speedy', 'Likes to run', False
>>>
Sub-models **inherit the _attr_names attribute** of their parent model.
Sub-models **inherit the _field_names attribute** of their parent model.
>>> WildAnimal._attr_names
>>> WildAnimal._field_names
('name', 'description', 'domesticated')
>>>
>>> WildAnimal.DEER
Expand All @@ -80,7 +80,7 @@
>>> WildAnimal.DOG
Traceback (most recent call last):
...
AttributeError: 'WildAnimal' object has no attribute 'DOG'
AttributeError: 'WildAnimal' model does not contain member 'DOG'
>>>
>>> pp(list(WildAnimal.members.all()))
[<WildAnimal.DEER: name='Bambi', description='Likes to hide', domesticated=False>,
Expand Down Expand Up @@ -123,25 +123,25 @@
<Animal.CAT: name='Fluffy', description="Man's gracious overlord", domesticated=True>]
>>>
Additional attribute names can be provided by overriding `_attr_names`
Additional field names can be provided by overriding `_field_names`
in sub-models. If the intent is to extend the parent model's
attribute definitions, a good practice is to reference the parent
field definitions, a good practice is to reference the parent
model's values as demonstrated in the **SmallHousePet** model below.
>>> class SmallHousePet(Animal):
... _attr_names = Animal._attr_names + ('facility',)
... _field_names = Animal._field_names + ('facility',)
...
... FISH = 'Nemo', 'Found at last', True, 'tank'
... RODENT = 'Freddy', 'The Golden One', True, 'cage'
>>>
Filtering a model with an attribute name that is not in `_attr_names`
Filtering a model with an field name that is not in `_field_names`
will raise a ValueError exception.
>>> pp(list(SmallHousePet.members.filter(species='hamster')))
Traceback (most recent call last):
...
ValueError: Invalid attribute 'species'
ValueError: Invalid field 'species'
>>>
The name of the member used on the model is also available.
Expand All @@ -156,10 +156,10 @@
[<SmallHousePet.RODENT: name='Freddy', description='The Golden One', domesticated=True, facility='cage'>]
>>>
Sub-models can provide completely different attribute names if desired.
Sub-models can provide completely different field names if desired.
>>> class FarmAnimal(Animal):
... _attr_names = 'food_provided', 'character', 'occupation', 'butcher_involved'
... _field_names = 'food_provided', 'character', 'occupation', 'butcher_involved'
... PIG = 'bacon', 'Porky Pig', "President, All Folks Actors Guild", True
... CHICKEN = 'eggs', 'Chicken Little', 'Salesman, Falling Sky Insurance', False
>>>
Expand All @@ -171,13 +171,13 @@
[<FarmAnimal.PIG: food_provided='bacon', character='Porky Pig', occupation='President, All Folks Actors Guild', butcher_involved=True>]
>>>
Only attribute names that exist on the model can be used. Parent models
know nothing about sub-model attributes.
Only field names that exist on the model can be used. Parent models
know nothing about sub-model fields.
>>> pp(list(Animal.members.filter(butcher_involved=True)))
Traceback (most recent call last):
...
ValueError: Invalid attribute 'butcher_involved'
ValueError: Invalid field 'butcher_involved'
>>>
=====================
Expand Down Expand Up @@ -237,20 +237,20 @@
>>>
The same rules apply for `criteria` as for .filter() with regards to
valid attributes.
valid fields.
>>> jsonify(list(Animal.members.values(criteria={'species': 'hamster'})))
Traceback (most recent call last):
...
ValueError: Invalid attribute 'species'
ValueError: Invalid field 'species'
>>>
Notice that when the `Animal` model was used to execute .values() or
.values_list(), the `facility` attribute was not included in the
results. This is because the default attributes for these methods is
the value of Animal._attr_names, which does not include `facility`.
.values_list(), the `facility` field was not included in the
results. This is because the default fields for these methods is
the value of Animal._field_names, which does not include `facility`.
Specific attributes for model.values() and model.values_list() may be
Specific fields for model.values() and model.values_list() may be
provided by passing them as positional parameters to those methods.
>>> jsonify(list(Animal.members.values('name', 'domesticated', 'facility')))
Expand Down Expand Up @@ -314,11 +314,11 @@
]
>>>
Notice that some members have the `facility` attribute and some don't,
Notice that some members have the `facility` field and some don't,
reflecting their actual contents. No placeholders are added in the
results.
Members that don't have ANY of the attributes are excluded from the
Members that don't have ANY of the fields are excluded from the
results. In the following examples, notice the absence of FarmAnimal members.
>>> jsonify(list(Animal.members.values()))
Expand Down Expand Up @@ -392,7 +392,7 @@
The model.values_list() method can be passed the `flat=True` parameter
to collapse the values in the result. This usually only makes sense
when combined with limiting the results to a single attribute name.
when combined with limiting the results to a single field name.
>>> jsonify(list(Animal.members.values_list('name', flat=True)))
[
Expand Down
86 changes: 43 additions & 43 deletions staticmodel/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

class ATTR_NAME:
class CLASS_VAR:
ATTR_NAMES = '_attr_names'
ATTR_NAMES = '_field_names'
class INSTANCE_VAR:
MEMBER_NAME = '_member_name'
RAW_VALUE = '_raw_value'
Expand Down Expand Up @@ -79,16 +79,16 @@ class MultipleObjectsReturned(StaticModelError):
pass

def __repr__(cls):
return '<StaticModel {}: Instances: {}, Attributes: {}>'.format(
cls.__name__, len(cls._instances.by_id), cls._attr_names)
return '<StaticModel {}: Instances: {}, Fields: {}>'.format(
cls.__name__, len(cls._instances.by_id), cls._field_names)

def __getattribute__(cls, item):
item = str(item)
if item.upper() == item:
try:
return cls.__dict__[item]
except KeyError:
raise AttributeError('{!r} object has no attribute {!r}'.format(
raise AttributeError('{!r} model does not contain member {!r}'.format(
cls.__name__, item))
else:
return super(StaticModelMeta, cls).__getattribute__(item)
Expand All @@ -114,9 +114,9 @@ def __setattr__(cls, key, value):
super(StaticModelMeta, cls).__setattr__(key, instance)

def __call__(
cls, raw_value=None, member_name=None, attr_names=None, *attr_values,
cls, raw_value=None, member_name=None, field_names=None, *field_values,
**kwargs):
if attr_values and raw_value:
if field_values and raw_value:
raise ValueError("Positional and 'raw_value' parameters are mutually"
" exclusive")

Expand All @@ -126,13 +126,13 @@ def __call__(

if raw_value and isinstance(raw_value, Iterable) and not isinstance(
raw_value, str):
attr_values = raw_value
field_values = raw_value

instance = super(StaticModelMeta, cls).__call__()

attr_names = attr_names or cls._attr_names or tuple(
'value{}'.format(i) for i in range(len(attr_values)))
instance.__dict__.update(dict(zip(attr_names, attr_values)))
field_names = field_names or cls._field_names or tuple(
'value{}'.format(i) for i in range(len(field_values)))
instance.__dict__.update(dict(zip(field_names, field_values)))

setattr(instance, ATTR_NAME.INSTANCE_VAR.RAW_VALUE, raw_value)

Expand Down Expand Up @@ -186,7 +186,7 @@ def _process_new_instance(cls, member_name, instance):
cls._index_instance(instance)

def _index_instance(cls, instance):
for index_attr in (ATTR_NAME.INSTANCE_VAR.RAW_VALUE, ) + cls._attr_names:
for index_attr in (ATTR_NAME.INSTANCE_VAR.RAW_VALUE, ) + cls._field_names:
index = cls._indexes.setdefault(index_attr, OrderedDict())
try:
value = getattr(instance, index_attr)
Expand All @@ -211,24 +211,24 @@ def _get_index_search_results(cls, kwargs):
sorted_kwargs = sorted(
kwargs.items(), key=lambda x: x[0] != ATTR_NAME.INSTANCE_VAR.MEMBER_NAME)

for attr_name, attr_value in sorted_kwargs:
if attr_name == ATTR_NAME.INSTANCE_VAR.MEMBER_NAME:
for field_name, field_value in sorted_kwargs:
if field_name == ATTR_NAME.INSTANCE_VAR.MEMBER_NAME:
index = cls._instances.by_member_name
try:
result = index[attr_value]
result = index[field_value]
except KeyError:
raise StopIteration
else:
yield result

else:
try:
index = cls._indexes[attr_name]
index = cls._indexes[field_name]
except KeyError:
raise ValueError(
'Invalid attribute {!r}'.format(attr_name))
'Invalid field {!r}'.format(field_name))
else:
result = index.get(cls._index_key_for_value(attr_value), [])
result = index.get(cls._index_key_for_value(field_value), [])
for item in result:
yield item

Expand All @@ -247,24 +247,24 @@ def __init__(self, model):
#
# Private API
#
def _values_base(self, item_func, *attr_names, **kwargs):
def _values_base(self, item_func, *field_names, **kwargs):
criteria = kwargs.pop('criteria', {})
allow_flat = kwargs.pop('allow_flat', False)
flat = kwargs.pop('flat', False)

if not attr_names:
attr_names = self.model._attr_names
if not field_names:
field_names = self.model._field_names

elif not frozenset(attr_names).issubset(frozenset(chain(
elif not frozenset(field_names).issubset(frozenset(chain(
(ATTR_NAME.INSTANCE_VAR.MEMBER_NAME,
ATTR_NAME.INSTANCE_VAR.MEMBER_NAME),
chain(self.model.__dict__.keys(), self.model._attr_names),
chain(self.model.__dict__.keys(), self.model._field_names),
chain.from_iterable(chain(
submodel.__dict__.keys(), submodel._attr_names)
submodel.__dict__.keys(), submodel._field_names)
for submodel in self.model.submodels())
))):
raise ValueError(
"Attribute names must be a subset of those available.")
"Field names must be a subset of those available.")

if not criteria:
results = self.all()
Expand All @@ -273,12 +273,12 @@ def _values_base(self, item_func, *attr_names, **kwargs):

if allow_flat and flat:
for rendered_item in chain.from_iterable(
item_func(item, attr_names) for item in results):
item_func(item, field_names) for item in results):
if rendered_item:
yield rendered_item
else:
for item in results:
rendered_item = item_func(item, attr_names)
rendered_item = item_func(item, field_names)
if rendered_item:
yield rendered_item

Expand All @@ -298,18 +298,18 @@ def filter(self, **kwargs):

validated_result_ids = set()
for result in index_search_results:
for attr_name, attr_value in kwargs.items():
if (attr_name == ATTR_NAME.INSTANCE_VAR.MEMBER_NAME and
for field_name, field_value in kwargs.items():
if (field_name == ATTR_NAME.INSTANCE_VAR.MEMBER_NAME and
self.model._instances.by_member_name.get(
attr_value) is result):
field_value) is result):
continue

try:
value = getattr(result, attr_name)
value = getattr(result, field_name)
except AttributeError:
break

if value != attr_value:
if value != field_value:
break

else:
Expand Down Expand Up @@ -342,21 +342,21 @@ def get(self, _return_none=False, **kwargs):
'{}.get({}) yielded multiple objects.'.format(
self.model.__name__, format_kwargs(kwargs)))

def _values_item(item, attr_names):
def _values_item(item, field_names):
rendered_item = []
for attr_name in attr_names:
attr_value = getattr(item, attr_name, None)
if attr_value is not None:
rendered_item.append((attr_name, attr_value))
for field_name in field_names:
field_value = getattr(item, field_name, None)
if field_value is not None:
rendered_item.append((field_name, field_value))
return OrderedDict(rendered_item)
values = partialmethod(_values_base, _values_item)

def _values_list_item(item, attr_names):
def _values_list_item(item, field_names):
rendered_item = []
for attr_name in attr_names:
attr_value = getattr(item, attr_name, None)
if attr_value is not None:
rendered_item.append(attr_value)
for field_name in field_names:
field_value = getattr(item, field_name, None)
if field_value is not None:
rendered_item.append(field_value)
return tuple(rendered_item)
values_list = partialmethod(_values_base, _values_list_item, allow_flat=True)

Expand All @@ -370,6 +370,6 @@ def __repr__(self):
self.__class__.__name__,
getattr(self, ATTR_NAME.INSTANCE_VAR.MEMBER_NAME),
format_kwargs(OrderedDict(
(attr_name, getattr(self, attr_name, None))
for attr_name in self._attr_names)),
(field_name, getattr(self, field_name, None))
for field_name in self._field_names)),
)
2 changes: 1 addition & 1 deletion staticmodel/django/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def __init__(self, *args, **kwargs):
assert self._static_model, 'static_model required'

self._value_field_name = kwargs.pop('value_field_name',
self._static_model._attr_names[0])
self._static_model._field_names[0])
self._validate_values()

self._display_field_name = kwargs.pop('display_field_name',
Expand Down

0 comments on commit cce8019

Please sign in to comment.