Skip to content

Commit

Permalink
Merge 37f10a8 into 9d16ca8
Browse files Browse the repository at this point in the history
  • Loading branch information
matllubos committed Sep 21, 2018
2 parents 9d16ca8 + 37f10a8 commit 9c9a717
Show file tree
Hide file tree
Showing 24 changed files with 237 additions and 208 deletions.
147 changes: 59 additions & 88 deletions chamber/utils/datastructures.py
@@ -1,18 +1,30 @@
import re

from collections import MutableSet, OrderedDict
from itertools import chain


ENUM_KEY_PATTERN = re.compile(r'^[a-zA-Z][a-zA-Z0-9_]*$')


class AbstractEnum:

def __init__(self, *items):
for k, _ in items:
if not isinstance(k, str):
raise ValueError('Enum key "{}" must be string'.format(k))
if not ENUM_KEY_PATTERN.match(k):
raise ValueError('Enum key "{}" has invalid format'.format(k))

self._container = OrderedDict(items)
self._reverse_container = {item[1]: item[0] for item in items}

def _has_attr(self, name):
return name in self._container

def _get_attr_val(self, name):
return name

def __getattr__(self, name):
if self._has_attr(name):
return self._get_attr_val(name)
return self._container[name]
raise AttributeError('Missing attribute %s' % name)

def __copy__(self, *args, **kwargs):
Expand All @@ -23,127 +35,86 @@ def __deepcopy__(self, *args, **kwargs):
# Enum is immutable
return self

def __contains__(self, item):
return item in self._reverse_container

def __iter__(self):
return self._container.values().__iter__()

@property
def all(self):
return tuple(self)

def get_name(self, val):
return self._reverse_container.get(val)


class Enum(AbstractEnum):

def __init__(self, *items):
self._container = OrderedDict((
super().__init__(*(
item if isinstance(item, (list, tuple)) else (item, item)
for item in items
))
super(Enum, self).__init__()

def _get_attr_val(self, name):
return self._container[name]

def __iter__(self):
return self._container.values().__iter__()


class NumEnum(AbstractEnum):
class NumEnum(Enum):

def __init__(self, *items):
self._container = OrderedDict()
super(NumEnum, self).__init__()
used_ids = set()

enum_items = []
i = 0
for item in items:
if len(item) == 2:
key, i = item
if not isinstance(i, int):
raise ValueError('Last value of item must by integer')
raise ValueError('Choice value of item must by integer')
else:
key = item
i += 1

if i in self._container.values():
if i in used_ids:
raise ValueError('Index %s already exists, please renumber choices')
self._container[key] = i

def _get_attr_val(self, name):
return self._container[name]
used_ids.add(i)
enum_items.append((key, i))
super().__init__(*enum_items)


class AbstractChoicesEnum:

def _get_labels_dict(self):
return dict(self._get_choices())
def __init__(self, *items):
enum_items = []
for item in items:
assert len(item) in {2, 3}, 'Choice item array length must be two or three'

def _get_choices(self):
raise NotImplementedError
if len(item) == 3:
enum_items.append((item[0], item[2]))
else:
enum_items.append(item[0])

@property
def choices(self):
return self._get_choices()
super().__init__(*enum_items)
self.choices = tuple(
(k, items[i][1]) for i, k in enumerate(self._container.values())
)

@property
def all(self):
return (key for key, _ in self._get_choices())
def _get_labels_dict(self):
return dict(self.choices)

def get_label(self, name):
labels = dict(self._get_choices())
labels = self._get_labels_dict()
if name in labels:
return labels[name]
raise AttributeError('Missing label with index %s' % name)


class ChoicesEnum(AbstractChoicesEnum, AbstractEnum):

def __init__(self, *items):
self._container = OrderedDict()
super(ChoicesEnum, self).__init__()
for item in items:
if len(item) == 3:
key, label, val = item
elif len(item) == 2:
key, label = item
val = key
self._container[key] = (val, label)

def get_name(self, i):
for key, (val, _) in self._container.items():
if val == i:
return key
return None

def _get_attr_val(self, name):
return self._container[name][0]

def _get_choices(self):
return list(self._container.values())


class ChoicesNumEnum(AbstractChoicesEnum, AbstractEnum):

def __init__(self, *items):
self._container = OrderedDict()
super(ChoicesNumEnum, self).__init__()
i = 0
for item in items:
if len(item) == 3:
key, val, i = item
if not isinstance(i, int):
raise ValueError('Last value of item must by integer')
elif len(item) == 2:
key, val = item
i += 1
else:
raise ValueError('Wrong input data format')

if i in {j for j, _ in self._container.values()}:
raise ValueError('Index %s already exists, please renumber choices')
self._container[key] = (i, val)

def get_name(self, i):
for key, (number, _) in self._container.items():
if number == i:
return key
return None
class ChoicesEnum(AbstractChoicesEnum, Enum):
pass

def _get_attr_val(self, name):
return self._container[name][0]

def _get_choices(self):
return list(self._container.values())
class ChoicesNumEnum(AbstractChoicesEnum, NumEnum):
pass


class SubstatesChoicesNumEnum(ChoicesNumEnum):
Expand Down Expand Up @@ -171,7 +142,7 @@ def __init__(self, items, initial_states=None):
self.initial_states = initial_states

# The last value of every item are omitted and send to ChoicesEnum constructor
super(SequenceChoicesEnumMixin, self).__init__(*(item[:-1] for item in items if item[0] is not None))
super().__init__(*(item[:-1] for item in items if item[0] is not None))

self.first_choices = self._get_first_choices(items)

Expand Down
4 changes: 2 additions & 2 deletions docs/models.rst
Expand Up @@ -91,11 +91,11 @@ SmartModel

List of defined pre or post save dispatchers. More obout it will find _dispatchers

.. property:: has_changed
.. attribute:: has_changed

Returns ``True`` or ``False`` depending on whether instance was changed

.. property:: initial_values
.. attribute:: initial_values

Returns initial values of the object from loading instance from database. It should represent actual state of the object in the database

Expand Down
81 changes: 73 additions & 8 deletions docs/utils.rst
Expand Up @@ -33,18 +33,75 @@ Enums

Base enumeration class with controlled ``__getattr__``.

.. attribute:: chamber.utils.datastructures.AbstractEnum.all

Returns all enum values in a tuple.

.. method:: chamber.utils.datastructures.AbstractEnum.__iter__

Returns iterator over enum values.

.. method:: chamber.utils.datastructures.AbstractEnum.get_name

Returns name of the choice accorting to its value.

.. method:: chamber.utils.datastructures.AbstractEnum.get_name

Implementation of in operator. Return ``True`` if value is part of the enum.


.. class:: chamber.utils.datastructures.Enum

Python's ``set`` with ``AbstractEnum`` behaviour.
Python's ``set`` with ``AbstractEnum`` behaviour. Enum key can be only string type. Key and values can be different.

::

>>> enum = NumEnum('A', 'B') # {'A': 'A', 'B': 'B'}
>>> enum.all
('A', 'B')
>>> 'A' in enum
True
>>> list(enum)
['A', 'B']
>>> enum.A
'A'

>>> enum = NumEnum(('A', 'a'), ('B', 'b')) # {'A': 'a', 'B': 'b'}
>>> enum.all
('a', 'b')
>>> 'a' in enum
True
>>> list(enum)
['a', 'b']
>>> enum.A
'a'


.. class:: chamber.utils.datastructures.NumEnum

Python's ``dict`` with ``AbstractEnum`` behaviour.

::

>>> NumEnum('a', 'b')
{'a': 1, 'b': 2}
>>> enum = NumEnum('A', 'B') # {'A': 1, 'B': 2}
>>> enum.all
(1, 2)
>>> 1 in enum
True
>>> list(enum)
[1, 2]
>>> enum.A
1

>>> enum = NumEnum(('A', 6), ('B', 5)) # {'A': 6, 'B': 5}
>>> enum.all
(6, 5)
>>> 5 in enum
True
>>> list(enum)
[6, 5]
>>> enum.A
6

.. class:: chamber.utils.datastructures.AbstractChoicesEnum

Expand All @@ -57,13 +114,21 @@ Base choices class (can be used as a model field's ``choices`` argument).

::

>>> enum = ChoicesEnum(('OK', 'ok'), ('KO', 'ko'))
>>> enum
{'OK': 'ok', 'KO': 'ko'}
>>> enum = ChoicesEnum(('OK', 'label ok'), ('KO', 'label ko')) # {'OK': ('OK', 'label ok), 'ko': ('KO', 'label ko)}
>>> enum.OK
'ok'
'OK'
>>> enum.choices
[('OK', 'ok'), ('KO', 'ko')]
>>> enum.get_label(enum.OK)
'label ok'

>>> enum = ChoicesEnum(('OK', 'label ok', 'ok'), ('KO', 'label ko', 'ko')) # {'OK': ('ok', 'label ok), 'ko': ('ko', 'label ko)}
>>> enum.OK
'ok'
>>> enum.choices
[('ok', 'label ok'), ('ko', 'label ko')]
>>> enum.get_label(enum.OK)
'label ok'

.. class:: chamber.utils.datastructures.ChoicesNumEnum

Expand All @@ -72,7 +137,7 @@ Base choices class (can be used as a model field's ``choices`` argument).

::

>>> enum = ChoicesNumEnum(('OK', 'ok', 1), ('KO', 'ko', 2))
>>> enum = ChoicesNumEnum(('OK', 'label ok', 1), ('KO', 'label ko', 2))
>>> enum.KO
2
>>> enum.choices
Expand Down
8 changes: 2 additions & 6 deletions example/Makefile
Expand Up @@ -17,7 +17,6 @@ PYTHON = python3.6
PYTHON_VERSION_FULL := $(wordlist 2,4,$(subst ., ,$(shell python --version 2>&1)))
PYTHON_VERSION_MAJOR := $(word 1,${PYTHON_VERSION_FULL}).$(word 2,${PYTHON_VERSION_FULL})
TYPE = dev
OS = $(shell uname)

INIT_DATA_PATH = data
INIT_DATA_FILE = $(INIT_DATA_PATH)/init.json
Expand All @@ -38,12 +37,11 @@ cleanvar: clean cleanvirtualenv
cleanall: cleanvar

pip:
$(PYTHON_BIN)/pip install --process-dependency-links --allow-all-external -r requirements/base.txt
$(PYTHON_BIN)/pip install --process-dependency-links -r requirements/base.txt

initvirtualenv:
virtualenv -p $(PYTHON) --no-site-packages $(VIRTUAL_ENV)
$(PYTHON_BIN)/pip install --upgrade
$(PYTHON_BIN)/pip install setuptools --no-use-wheel --upgrade
$(PYTHON_BIN)/pip install setuptools --upgrade

bootstrap: initvirtualenv pip

Expand Down Expand Up @@ -92,7 +90,5 @@ install: cleanvar bootstrap initlog linklibrary initdb syncdb initdata initenv

update: clean cleanvirtualenv cleanjs bootstrap syncdb initenv

include build/Makefile.$(OS)

linklibrary:
ln -sf $$(pwd)/../chamber/ $$($(PYTHON_BIN)/python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())");
14 changes: 0 additions & 14 deletions example/build/Makefile.Darwin

This file was deleted.

0 comments on commit 9c9a717

Please sign in to comment.