Class Meta is a ubiquitous Python pattern that's often used to define class-specific attributes in a framework.
For example, Django models use the Meta pattern to store model-specific attributes, and django-rest-framework uses it to specify serializer options.
This is a powerful pattern, but parsing options can get redundant. I was recently prototyping a system and found myself spending more type writing type-checks and option validation than I did actually solving the problem at hand. This inspired me to come up with a simple design pattern to reduce complexity. This pattern leverages Python’s new excellent dataclass
construct.
Here’s an example:
class Model:
@dataclass
class MetaParser:
quantity: int=0
translations: field(default_factory=dict)
class Meta:
quantity = 123
translations = { 'foo': 'bar' }
def __init__(self):
self.meta = None
if hasattr(self, 'Meta'):
data = {
k: v for k, v in self.Meta.__dict__.items()
if not k.startswith('__') and not callable(k)
}
self.meta = self.MetaParser(**data)
What this does:
- Ensures that valid arguments are passed and that they’re of the correct type. If, for example,
quantity
was a string, your program would throw aTypeError
. - Ensures that no invalid options are passed. If, for example, we added an extraneous option in our
class Meta
block, the program would also throw aTypeError
.
All this in ~10 lines of code. This pattern is especially useful when dealing with tall inheritance chains and/or metaclasses.