Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleaned enum structures and added new features #61

Merged
merged 1 commit into from
Oct 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
147 changes: 59 additions & 88 deletions chamber/utils/datastructures.py
Original file line number Diff line number Diff line change
@@ -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_]*$')
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omezil jsem názvy klíčů enumů



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}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

původně se iterovalo v _container, což může být časově trošku horší, tak si připravuji i reverzní dict


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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

na enumy funguje operátor in


def __iter__(self):
return self._container.values().__iter__()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enumy jsou iterable, není nutné volat all, ale all vytvoří tuple namísto generátoru, což se může hodit


@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):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

num enum je nyní jen speciální typ enumu, který kontroluje, že hodnoty jsou čísla a umí hondnoty auto generovat (jinak vlastně není rozdíl)


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(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nyní jsou choices ve vlastní struktuře, dříve se prasila základní struktura enumu a blbě se s tím pracovalo, navíc nám správně nefungovala dědičnost enumů

(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
Original file line number Diff line number Diff line change
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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

property neexistuje.


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
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,75 @@ Enums

Base enumeration class with controlled ``__getattr__``.

.. attribute:: chamber.utils.datastructures.AbstractEnum.all
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

troška té dokumentace s příklady


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
Original file line number Diff line number Diff line change
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.