Skip to content

Instantly share code, notes, and snippets.

@iAnanich
Created February 1, 2018 14:22
Show Gist options
  • Save iAnanich/622be82fd762fcac26cf718501df953b to your computer and use it in GitHub Desktop.
Save iAnanich/622be82fd762fcac26cf718501df953b to your computer and use it in GitHub Desktop.
set of FieldsStorage classes which behaves like mutable mapping with preset set of keys
import typing
TypeOrNone = typing.Union[type, None]
def has_wrong_type(obj, expected_obj_type: TypeOrNone) -> bool:
"""
Checks if given `obj` object has not given `expected_obj_type` type. If
`expected_obj_type` is `None` than it will return `True`
:param obj: any object
:param expected_obj_type: expected type of the object or `None`
:return: `True` if `obj` object is not of `expected_obj_type` type, `False`
if `expected_obj_type` is `None` or `obj` object has `expected_obj_type` type
"""
# if `expected` type is `None` it will
# return False without `isinstance` call
return expected_obj_type is not None and not isinstance(obj, expected_obj_type)
def raise_type_error(obj_repr: str, obj_type: type, expected_obj_type: type,
obj_name: str ='This'):
raise TypeError(
f'{obj_name} {obj_repr} has "{obj_type}" type while '
f'"{expected_obj_type}" is expected.'
)
def check_obj_type(obj, expected_obj_type: TypeOrNone, obj_name: str ='object'):
if has_wrong_type(obj=obj, expected_obj_type=expected_obj_type):
raise_type_error(
obj_name=obj_name,
obj_repr=repr(obj),
obj_type=type(obj),
expected_obj_type=expected_obj_type,
)
import abc
from typing import FrozenSet, Dict, NoReturn
from .check import check_object_type
class FieldsStorageABC(abc.ABC):
"""
Defines interface for `FieldsStorage` classes.
"""
FieldNameType: type = object
FieldValueType: type = object
FieldsDictType: type = Dict[FieldNameType, FieldValueType]
@abc.abstractmethod
def __init__(self, field_names: FrozenSet[FieldNameType]):
self._all_field_names: frozenset = frozenset(field_names)
self._filled_field_names: set = set()
@abc.abstractmethod
def update(self, dictionary: FieldsDictType) -> NoReturn:
"""
Behaves as ``dict.update`` method.
:param dictionary:
:return:
"""
pass
@abc.abstractmethod
def dict_copy(self) -> FieldsDictType:
"""
Returns dictionary copy
:return:
"""
pass
@abc.abstractmethod
def set(self, field_name: FieldNameType, field_value: FieldValueType) -> NoReturn:
"""
Validates field and value, then sets field's value if given ``field``
is allowed by all fields set, and updates set of filled fields.
:param field_name: ``str``
:param field_value: ``str``
:return: ``None``
"""
pass
@abc.abstractmethod
def get(self, field_name: FieldNameType) -> FieldValueType:
"""
Validates field and returns it's value.
:param field_name: field's name
:raise KeyError: if no such ``field_name`` was found.
:return: related value
"""
pass
@abc.abstractmethod
def reset(self) -> NoReturn:
"""
Drops fields' values, and clears set of filled fields.
:return: ``None``
"""
pass
@abc.abstractmethod
def release(self) -> FieldsDictType:
"""
Returns dict copy and clears storage.
:return: field name to it's value mapping.
"""
pass
@abc.abstractmethod
def is_full(self) -> bool:
"""
Checks whether or not all fields were filled wis valid values.
:return: ``True`` if all fields has their values.
"""
pass
@abc.abstractmethod
def has_value(self, field_name: FieldNameType) -> bool:
"""
Checks whether or net given ``field`` filed has it's value.
:param field_name: one of the allowed fields.
:return: ``True`` if field has value.
"""
@abc.abstractmethod
def validate_field_name(self, field_name) -> FieldNameType:
"""
Validates field name, by just searching for it in set of allowed fields.
:param field_name: field name to validate
:raise KeyError: if no such ``field_name`` was found.
:return: validated field name
"""
pass
@abc.abstractmethod
def validate_field_value(self, field_value, *,
valid_field_name: FieldNameType) -> FieldValueType:
"""
Validates value. Uses ``valid_field_name`` for error messages.
:param field_value: value to validate
:param valid_field_name: valid field name.
:raise ValueError: no such case yet
:raise TypeError: if ``field_value`` is not an instance of ``FieldValueType``
:return: validated value
"""
pass
class BaseStringFieldsStorage(FieldsStorageABC, metaclass=abc.ABCMeta):
FieldNameType = str
FieldValueType = str
FieldsDictType = Dict[FieldNameType, FieldValueType]
def __init__(self, field_names: FrozenSet[FieldNameType]):
for f in field_names:
check_obj_type(f, self.FieldNameType, f'Field')
self._all_field_names: frozenset = frozenset(field_names)
self._filled_field_names: set = set()
# do nothing
super().__init__(field_names)
def validate_field_name(self, field_name) -> FieldNameType:
if field_name not in self._all_field_names:
raise KeyError(
f'No such "{field_name}" field name found.'
)
return field_name
def validate_field_value(self, field_value, *,
valid_field_name: FieldNameType) -> FieldValueType:
check_obj_type(field_value, str, f'Value of the "{valid_field_name}" field.')
return field_value
def set(self, field_name: FieldNameType, field_value: FieldValueType) -> NoReturn:
valid_field_name = self.validate_field_name(field_name)
valid_field_value = self.validate_field_value(
field_value, valid_field_name=valid_field_name)
self._set(valid_field=valid_field_name, valid_value=valid_field_value)
self._filled_field_names.add(valid_field_name)
def get(self, field: FieldNameType) -> FieldValueType:
valid_field = self.validate_field_name(field)
return self._get(valid_field)
def update(self, dictionary: FieldsDictType) -> NoReturn:
for key, value in dictionary.items():
self.set(field_name=key, field_value=value)
def reset(self) -> NoReturn:
self._reset()
self._filled_field_names.clear()
def release(self) -> FieldsDictType:
res = self.dict_copy()
self.reset()
return res
def has_value(self, field_name: FieldNameType) -> bool:
if field_name in self._filled_field_names:
return True
elif field_name in self._all_field_names:
return False
else:
raise KeyError(f'Unknown field: "{field_name}.')
def is_full(self) -> bool:
return self._filled_field_names == self._all_field_names
def dict_copy(self) -> dict:
return {field_name: self.get(field_name)
for field_name in self._filled_field_names}
@abc.abstractmethod
def _set(self, valid_field: FieldNameType, valid_value: FieldValueType) -> NoReturn:
"""
Abstraction over implementation details for setting ``field``'s
``value`` without overriding it.
:param valid_field: validated field
:param valid_value: validated value
:return:
"""
pass
@abc.abstractmethod
def _reset(self) -> NoReturn:
"""
Drops fields' values.
:return: ``None``
"""
pass
@abc.abstractmethod
def _get(self, field_name: FieldNameType) -> FieldValueType:
"""
Returns value related to given ``field`` field.
:raise KeyError: if no such ``field_name`` was found.
:return: related value
"""
pass
class DictStringsFieldsStorage(BaseStringFieldsStorage):
def __init__(self, field_names: typing.FrozenSet[str]):
super().__init__(field_names)
self._storage: dict = self._new_storage()
def _new_storage(self) -> dict:
return {k: None for k in self._all_field_names}
def _reset(self):
self._storage.clear()
def _set(self, valid_field_name: str, valid_field_value: str):
self._storage[valid_field_name] = valid_field_value
def _get(self, field_name: str) -> str:
return self._storage[field_name]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment