I'm modelling the core entities in a callcenter-related system that deals with Operators and groups ("pools") of them. While I've written some Python before, this is my first time building a larger project in it from scratch, and I'd like to know if my approach and code is pythonic enough or will get me into trouble later on.
I really like the idea of interfaces in general and for explorational design specifically, and while Python doesn't have those, ABCs seem like the next best equivalent thing. It there anything to be said about my use of them?
Any specific hints about my use of a
I'm a bit worried about file structures and putting every concrete class in its own file, as that results in
from Foo.Bar.Baz import Baz; is that acceptable, or is the common wisdom to include more classes in a single module?
I'm also using a lot of type hinting; while I'm aware that it doesn't actually do anything in Python, it does help me keep the ducks straight in my head while typing and helps my IDE help me. Any concerns here?
# core/abstract_base_classes/operators.py from abc import ABCMeta, abstractmethod from core.abstract_base_classes.attributes import AttributeBag from collections.abc import MutableSet from uuid import UUID class Operator(metaclass=ABCMeta): """ An abstract class defining the interface for an Operator entity representing a call center agent. """ @property @abstractmethod def id(self) -> UUID: pass @property @abstractmethod def status(self): pass @status.setter @abstractmethod def status(self, value): pass @property @abstractmethod def attributes(self) -> AttributeBag: pass class Pool(MutableSet): """ An abstract class defining the interface for an operator pool. """ @abstractmethod def __contains__(self, operator: Operator): pass @abstractmethod def discard(self, operator: Operator): pass @abstractmethod def __iter__(self): pass @abstractmethod def add(self, operator: Operator): pass @abstractmethod def __len__(self): pass
# core/Entities/Operator.py from core.abstract_base_classes.operators import Operator as AbstractOperator from core.abstract_base_classes.attributes import AttributeBag as AbstractAttributeBag from core.abstract_base_classes.attributes import AttributeTag as AbstractAttributeTag from core.Entities.AttributeBag import AttributeBag from core import status from uuid import UUID, uuid4 class Operator(AbstractOperator): def __init__(self, attributes: AbstractAttributeBag=None): if not attributes: attributes = AttributeBag() super().__init__() self._id = uuid4() self._attributes = attributes self._status = status.INITIALIZED @property def id(self) -> UUID: return self._id @property def status(self): return self._status @status.setter def status(self, value): self._status = value @property def attributes(self) -> AbstractAttributeBag: return self._attributes @attributes.setter def attributes(self, attributes: AbstractAttributeBag): self._attributes = attributes def add_attribute(self, attribute: AbstractAttributeTag): self._attributes.add(attribute)
# core/Entities/OperatorPool.py from core.abstract_base_classes import operators class Pool(operators.Pool): def __init__(self): super().__init__() self._operators = set() def __iter__(self): return self._operators.__iter__() def __contains__(self, operator: operators.Operator): return operator in self._operators def discard(self, operator: operators.Operator): self._operators.remove(operator) def add(self, operator: operators.Operator): self._operators.add(operator) def __len__(self): return len(self._operators)