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 MutableSet
?
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)