I'm currently building some software that works with systemd unit files, and I've been trying to improve how we construct the unit files.
This is all running in Python 3.4
This first block is the base classes I'm building things up with.
class LimitedDict(dict):
"""
Sub Class of the dictionary object that restricts the allowed keys.
The allowed keys are set when creating the object.
>>> a = LimitedDict(allowed_keys=['foo'], foo='bar')
>>> a == {'foo': 'bar'}
True
>>> ok_keywords = {'foo': 'bar'}
>>> a = LimitedDict(allowed_keys=['foo'], **ok_keywords)
>>> a == {'foo': 'bar'}
True
>>> LimitedDict(allowed_keys=['foo'], foo='bar', fail='true')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 13, in __init__
KeyError: "key: 'fail' not in allowed keys: '['foo']'"
>>> not_ok_keywords = {'foo': 'bar', 'fail': 'oops'}
>>> LimitedDict(allowed_keys=['foo'], **not_ok_keywords)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 13, in __init__
KeyError: "key: 'fail' not in allowed keys: '['foo']'"
>>> a = LimitedDict(allowed_keys=['foo'], foo='bar')
>>> a['fail'] = 'true'
Traceback (most recent call last):
...
KeyError: "key: 'fail' not in allowed keys: '['foo']'"
"""
_allowed_keys = list()
def __init__(self, allowed_keys, **kwargs):
if not isinstance(allowed_keys, list):
AttributeError("'allowed_keys' must be a list")
self._allowed_keys = allowed_keys
for key in kwargs.keys():
if key not in self._allowed_keys:
raise KeyError("key: '" + str(key) + "' not in allowed keys: '" + str(self._allowed_keys) + "'")
self[key] = kwargs[key]
def __setitem__(self, key, val):
if key not in self._allowed_keys:
raise KeyError("key: '" + str(key) + "' not in allowed keys: '" + str(self._allowed_keys) + "'")
dict.__setitem__(self, key, val)
class UnitDict(LimitedDict):
"""
This LimitedDict object is further customized for use in building a
json representation of a systemd unit file. The additional restrictions
ensure we don't introduce elements outside the allowed values for a
systemd unit file.
Create a dictionary with the correct keys:
>>> a = UnitDict(name='Test', desiredState='Loaded', options='Test')
>>> a == {'name': 'Test', 'desiredState': 'Loaded', 'options': 'Test'}
True
Ignore incorrect keys
>>> a = UnitDict(name='Test', desiredState='Loaded', options='Test', ignored='True')
>>> a == {'options': 'Test', 'name': 'Test', 'desiredState': 'Loaded'}
True
"""
def __init__(self, **kwargs):
super_kwargs = {
'name': kwargs['name'],
'desiredState': kwargs['desiredState'],
'options': kwargs['options']}
super().__init__(allowed_keys=list(super_kwargs.keys()), **super_kwargs)
class UnitOptionDict(LimitedDict):
def __init__(self, **kwargs):
super_kwargs = {
'name': kwargs['name'],
'section': kwargs['section'],
'value': kwargs['value']}
super().__init__(allowed_keys=list(super_kwargs.keys()), **super_kwargs)
This is the code I want feedback on. I'm fairly sure this can be improved. Most likely with metaclasses but after much reading, I'm still not completely sure just how to construct such a metaclass, or if that really is the best way to refactor this to be cleaner.
def build_option_dict(caller, **kwargs):
if not kwargs['value']:
raise KeyError
if not isinstance(kwargs['value'], str):
raise ValueError
return {
'section': str(caller.__class__.__qualname__).split('.')[-2],
'name': str(caller.__class__.__qualname__).split('.')[-1],
'value': str(kwargs['value'])
}
class MightyMorphingMetaMagic(UnitOptionDict):
def __init__(self, **kwargs):
super_kwargs = build_option_dict(self, **kwargs)
super().__init__(allowed_keys=list(super_kwargs.keys()), **super_kwargs)
class OptionSections:
class Unit:
class Description(MightyMorphingMetaMagic):
pass
class After(MightyMorphingMetaMagic):
pass
class Require(MightyMorphingMetaMagic):
pass
class Service:
class TimeoutStartSec(MightyMorphingMetaMagic):
pass
class ExecStartPre(MightyMorphingMetaMagic):
pass
class ExecStart(MightyMorphingMetaMagic):
pass
class ExecStartPost(MightyMorphingMetaMagic):
pass
class ExecStopPre(MightyMorphingMetaMagic):
pass
class ExecStop(MightyMorphingMetaMagic):
pass
class ExecStopPost(MightyMorphingMetaMagic):
pass
The way this ends up being used is typically like this, building a json representation from a fairly stable list of unit file parts.
from lib.systemd import OptionSections as Sections
unit_json = [
Sections.Unit.Description(value=str(self.description)),
Sections.Service.ExecStartPre(value=str(self.execstartpre)),
Sections.Service.ExecStart(value=str(self.execstart)),
Sections.Service.ExecStop(value=str(self.execstop))
]