Selectively assign the getter for better performance

Introduces the `inherit` param for FieldAttributes, which is now used
in BaseMeta when constructing the getter property to enhance performance
by reducing the amount of work the getter generally has to do.
This commit is contained in:
James Cammarata 2016-08-24 11:41:05 -05:00
parent 3a51587220
commit 96e2be9bf8
3 changed files with 60 additions and 21 deletions

View file

@ -23,7 +23,7 @@ from copy import deepcopy
class Attribute:
def __init__(self, isa=None, private=False, default=None, required=False, listof=None, priority=0, class_type=None, always_post_validate=False):
def __init__(self, isa=None, private=False, default=None, required=False, listof=None, priority=0, class_type=None, always_post_validate=False, inherit=True):
self.isa = isa
self.private = private
@ -33,6 +33,7 @@ class Attribute:
self.priority = priority
self.class_type = class_type
self.always_post_validate = always_post_validate
self.inherit = inherit
if default is not None and self.isa in ('list', 'dict', 'set'):
self.default = deepcopy(default)

View file

@ -47,10 +47,19 @@ except ImportError:
def _generic_g(prop_name, self):
try:
return self._attributes[prop_name]
except KeyError:
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, prop_name))
def _generic_g_method(prop_name, self):
method = "_get_attr_%s" % prop_name
try:
value = getattr(self, method)()
except AttributeError:
return getattr(self, method)()
except KeyError:
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, prop_name))
def _generic_g_parent(prop_name, self):
try:
value = self._attributes[prop_name]
if value is None and not self._finalized:
@ -71,8 +80,19 @@ def _generic_d(prop_name, self):
class BaseMeta(type):
"""
Metaclass for the Base object, which is used to construct the class
attributes based on the FieldAttributes available.
"""
def __new__(cls, name, parents, dct):
def _create_attrs(src_dict, dst_dict):
'''
Helper method which creates the attributes based on those in the
source dictionary of attributes. This also populates the other
attributes used to keep track of these attributes and via the
getter/setter/deleter methods.
'''
keys = list(src_dict.keys())
for attr_name in keys:
value = src_dict[attr_name]
@ -80,7 +100,18 @@ class BaseMeta(type):
if attr_name.startswith('_'):
attr_name = attr_name[1:]
# here we selectively assign the getter based on a few
# things, such as whether we have a _get_attr_<name>
# method, or if the attribute is marked as not inheriting
# its value from a parent object
method = "_get_attr_%s" % attr_name
if method in src_dict or method in dst_dict:
getter = partial(_generic_g_method, attr_name)
elif ('_get_parent_attribute' in src_dict or '_get_parent_attribute' in dst_dict) and value.inherit:
getter = partial(_generic_g_parent, attr_name)
else:
getter = partial(_generic_g, attr_name)
setter = partial(_generic_s, attr_name)
deleter = partial(_generic_d, attr_name)
@ -89,14 +120,21 @@ class BaseMeta(type):
dst_dict['_attributes'][attr_name] = value.default
def _process_parents(parents, dst_dict):
'''
Helper method which creates attributes from all parent objects
recursively on through grandparent objects
'''
for parent in parents:
if hasattr(parent, '__dict__'):
_create_attrs(parent.__dict__, dst_dict)
_process_parents(parent.__bases__, dst_dict)
# create some additional class attributes
dct['_attributes'] = dict()
dct['_valid_attrs'] = dict()
# now create the attributes based on the FieldAttributes
# available, including from parent (and grandparent) objects
_create_attrs(dct, dct)
_process_parents(parents, dct)
@ -140,10 +178,10 @@ class Base(with_metaclass(BaseMeta, object)):
# every object gets a random uuid:
self._uuid = uuid.uuid4()
# initialize the default field attribute values
#self._attributes = dict()
#for (name, attr) in iteritems(self._valid_attrs):
# self._attributes[name] = attr.default
# we create a copy of the attributes here due to the fact that
# it was intialized as a class param in the meta class, so we
# need a unique object here (all members contained within are
# unique already).
self._attributes = self._attributes.copy()
# and init vars, avoid using defaults in field declaration as it lives across plays

View file

@ -30,9 +30,9 @@ from ansible.playbook.taggable import Taggable
class Block(Base, Become, Conditional, Taggable):
_block = FieldAttribute(isa='list', default=[])
_rescue = FieldAttribute(isa='list', default=[])
_always = FieldAttribute(isa='list', default=[])
_block = FieldAttribute(isa='list', default=[], inherit=False)
_rescue = FieldAttribute(isa='list', default=[], inherit=False)
_always = FieldAttribute(isa='list', default=[], inherit=False)
_delegate_to = FieldAttribute(isa='list')
_delegate_facts = FieldAttribute(isa='bool', default=False)
_any_errors_fatal = FieldAttribute(isa='bool')