I don't like how static and WET your class is.
You don't really need all the _right
s, and you should probably remove the repetition in all your properties, except permissions
.
I'd make a function to build properties for you.
Take the cmd property:
@property
def cmd(self):
return self._perms & AppliancePerms._cmd_right
@cmd.setter
def cmd(self, boolean):
if boolean:
self._perms |= AppliancePerms._cmd_right
else:
del self.cmd
@cmd.deleter
def cmd(self):
if self._perms & AppliancePerms._cmd_right:
self._perms -= AppliancePerms._cmd_right
This is almost identical to the other two functions, the only difference is AppliancePerms._cmd_right
.
And so if we were to want to write a function to build this then we'd start by replacing that, with a say variable bit
.
Since you're using @property
, rather than property
I'm not sure how the del fn_name
would work,
and so I'd change it to use the property
function.
This means we can get:
def build_permission_property(bit):
def get(self):
return self._perms & bit
def set(self, boolean):
if boolean:
self._perms |= bit
else:
delete(self)
def delete(self):
if self._perms & bit:
self._perms -= bit
return property(get, set, delete)
This works as you'd want, but I'd change the delete to use a bit mask.
Since it'll be a mask we can then use &=
, and remove the branch.
To make the mask we need it actually really easy.
It's just ~bit
and since Python's int's are 'infinite' it'll work on all numbers.
I'd also change get
to return 1
or 0
as it stands it can return 0
, 1
, 2
, 4
, ....
Changing these can result in:
def build_permission_property(bit_index):
bit = 1 << bit_index
def get(self):
return (self._perms & bit) >> bit_index
def set(self, boolean):
if boolean:
self._perms |= bit
else:
delete(self)
def delete(self):
self._perms &= ~bit
return property(get, set, delete)
And to use it is quite simple.
We just set the class' variable to the property.
I'd keep _num_of_rights
, and would change the permissions getter to use str.format
And so could result in:
class AppliancePerms(object):
def __init__(self, perm_digits=None):
# Default to all permissions
if perm_digits is None:
perm_digits = (1 << type(self)._num_of_rights) - 1
self._perms = perm_digits
cmd = build_permission_property(0)
rest = build_permission_property(1)
ssh = build_permission_property(2)
_num_of_rights = 3
@property
def permissions(self):
return '{:0>{}b}'.format(self._perms, type(self)._num_of_rights)
@permissions.setter
def permissions(self, perm_digits):
self._perms = perm_digits
@permissions.deleter
def permissions(self):
self._perms = 0
This is quite nice, all you need to do is manually set the properties, and _num_of_rights
to the amount.
But it's still too WET for me.
And so I'd use the more advanced Python feature of metaclasses.
This may not be good for developers that don't know Python too well.
I'd base this metaclass off one of martineau's answers on SO.
You can program this in two classes,
but I'd prefer three as then if you need more permission classes it's easy to implement.
These classes are:
Permission metaclass.
This will take a list from the class, and build the properties.
Base permissions class.
This will inherit the metaclass in a Python 2 and Python 3 friendly way. (the cross-Python metaclass inheritance is odd.)
It will also define the permissions property, and default _perms
to zero, so subclasses don't have to.
Subclass.
This will be a domain specific class that provides the list of properties.
It will also define custom functionality, such as defaulting to all permissions.
This could become:
def build_permission_property(bit_index):
bit = 1 << bit_index
def get(self):
return (self._perms & bit) >> bit_index
def set(self, boolean):
if boolean:
self._perms |= bit
else:
delete(self)
def delete(self):
self._perms &= ~bit
return property(get, set, delete)
class PermissionsMetaClass(type):
def __new__(meta, classname, bases, class_dict):
dict_ = class_dict.copy()
dict_.pop('__module__', None)
dict_.pop('__qualname__', None)
for i, perm in enumerate(dict_.get('_permissions', [])):
dict_[perm] = build_permission_property(i)
return type.__new__(meta, classname, bases, dict_)
class Permissions(PermissionsMetaClass("Permissions", (object,), {})):
_perms = 0
@property
def permissions(self):
return '{:0>{}b}'.format(self._perms, len(type(self)._permissions))
@permissions.setter
def permissions(self, perm_digits):
self._perms = perm_digits
@permissions.deleter
def permissions(self):
self._perms = 0
class AppliancePerms(Permissions):
_permissions = ['cmd', 'rest', 'ssh']
def __init__(self, perm_digits=None):
# Default to all permissions
if perm_digits is None:
perm_digits = (1 << len(type(self)._permissions)) - 1
self._perms = perm_digits
I've provided both with and without a metaclass, as metaclasses are a bit harder to understand. And so it's up to you which design you'd rather use.
I mostly just recommend adding the function build_permission_property
.