avoid persistent containers in attribute defaults

moved from the field attribute declaration and created a placeholder
which then is resolved in the field attribute class.

this is to avoid unwanted persistent of the defaults across objects which introduces
stealth bugs when multiple objects of the same kind are used in succession while
not overriding the default values.
This commit is contained in:
Brian Coca 2015-12-09 07:21:00 -08:00
parent 0719eb3e2d
commit 87969868d4
9 changed files with 29 additions and 18 deletions

View file

@ -32,6 +32,17 @@ class Attribute:
self.priority = priority self.priority = priority
self.always_post_validate = always_post_validate self.always_post_validate = always_post_validate
# This is here to avoid `default=<container>` unwanted persistence across object instances
# We cannot rely on None as some fields use it to skip the code
# that would detect an empty container as a user error
if self.default == '_ansible_container':
if self.isa == 'list':
self.default = []
elif self.isa == 'dict':
self.default = {}
elif self.isa == 'set':
self.default = set()
def __eq__(self, other): def __eq__(self, other):
return other.priority == self.priority return other.priority == self.priority

View file

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

View file

@ -33,7 +33,7 @@ class Conditional:
to be run conditionally when a condition is met or skipped. to be run conditionally when a condition is met or skipped.
''' '''
_when = FieldAttribute(isa='list', default=[]) _when = FieldAttribute(isa='list', default='_ansible_container')
def __init__(self, loader=None): def __init__(self, loader=None):
# when used directly, this class needs a loader, but we want to # when used directly, this class needs a loader, but we want to

View file

@ -64,22 +64,22 @@ class Play(Base, Taggable, Become):
# Connection # Connection
_gather_facts = FieldAttribute(isa='bool', default=None, always_post_validate=True) _gather_facts = FieldAttribute(isa='bool', default=None, always_post_validate=True)
_hosts = FieldAttribute(isa='list', default=[], required=True, listof=string_types, always_post_validate=True) _hosts = FieldAttribute(isa='list', default='_ansible_container', required=True, listof=string_types, always_post_validate=True)
_name = FieldAttribute(isa='string', default='', always_post_validate=True) _name = FieldAttribute(isa='string', default='', always_post_validate=True)
# Variable Attributes # Variable Attributes
_vars_files = FieldAttribute(isa='list', default=[], priority=99) _vars_files = FieldAttribute(isa='list', default='_ansible_container', priority=99)
_vars_prompt = FieldAttribute(isa='list', default=[], always_post_validate=True) _vars_prompt = FieldAttribute(isa='list', default='_ansible_container', always_post_validate=True)
_vault_password = FieldAttribute(isa='string', always_post_validate=True) _vault_password = FieldAttribute(isa='string', always_post_validate=True)
# Role Attributes # Role Attributes
_roles = FieldAttribute(isa='list', default=[], priority=90) _roles = FieldAttribute(isa='list', default='_ansible_container', priority=90)
# Block (Task) Lists Attributes # Block (Task) Lists Attributes
_handlers = FieldAttribute(isa='list', default=[]) _handlers = FieldAttribute(isa='list', default='_ansible_container')
_pre_tasks = FieldAttribute(isa='list', default=[]) _pre_tasks = FieldAttribute(isa='list', default='_ansible_container')
_post_tasks = FieldAttribute(isa='list', default=[]) _post_tasks = FieldAttribute(isa='list', default='_ansible_container')
_tasks = FieldAttribute(isa='list', default=[]) _tasks = FieldAttribute(isa='list', default='_ansible_container')
# Flag/Setting Attributes # Flag/Setting Attributes
_any_errors_fatal = FieldAttribute(isa='bool', default=False, always_post_validate=True) _any_errors_fatal = FieldAttribute(isa='bool', default=False, always_post_validate=True)

View file

@ -171,8 +171,8 @@ class PlayContext(Base):
# general flags # general flags
_verbosity = FieldAttribute(isa='int', default=0) _verbosity = FieldAttribute(isa='int', default=0)
_only_tags = FieldAttribute(isa='set', default=set()) _only_tags = FieldAttribute(isa='set', default='_ansible_container')
_skip_tags = FieldAttribute(isa='set', default=set()) _skip_tags = FieldAttribute(isa='set', default='_ansible_container')
_check_mode = FieldAttribute(isa='bool', default=False) _check_mode = FieldAttribute(isa='bool', default=False)
_force_handlers = FieldAttribute(isa='bool', default=False) _force_handlers = FieldAttribute(isa='bool', default=False)
_start_at_task = FieldAttribute(isa='string') _start_at_task = FieldAttribute(isa='string')

View file

@ -35,7 +35,7 @@ class PlaybookInclude(Base, Conditional, Taggable):
_name = FieldAttribute(isa='string') _name = FieldAttribute(isa='string')
_include = FieldAttribute(isa='string') _include = FieldAttribute(isa='string')
_vars = FieldAttribute(isa='dict', default=dict()) _vars = FieldAttribute(isa='dict', default='_ansible_container')
@staticmethod @staticmethod
def load(data, basedir, variable_manager=None, loader=None): def load(data, basedir, variable_manager=None, loader=None):

View file

@ -40,7 +40,7 @@ class RoleMetadata(Base):
''' '''
_allow_duplicates = FieldAttribute(isa='bool', default=False) _allow_duplicates = FieldAttribute(isa='bool', default=False)
_dependencies = FieldAttribute(isa='list', default=[]) _dependencies = FieldAttribute(isa='list', default='_ansible_container')
_galaxy_info = FieldAttribute(isa='GalaxyInfo') _galaxy_info = FieldAttribute(isa='GalaxyInfo')
def __init__(self, owner=None): def __init__(self, owner=None):

View file

@ -29,7 +29,7 @@ from ansible.template import Templar
class Taggable: class Taggable:
untagged = frozenset(['untagged']) untagged = frozenset(['untagged'])
_tags = FieldAttribute(isa='list', default=[], listof=(string_types,int)) _tags = FieldAttribute(isa='list', default='_ansible_container', listof=(string_types,int))
def __init__(self): def __init__(self):
super(Taggable, self).__init__() super(Taggable, self).__init__()

View file

@ -64,7 +64,7 @@ class Task(Base, Conditional, Taggable, Become):
# will be used if defined # will be used if defined
# might be possible to define others # might be possible to define others
_args = FieldAttribute(isa='dict', default=dict()) _args = FieldAttribute(isa='dict', default='_ansible_container')
_action = FieldAttribute(isa='string') _action = FieldAttribute(isa='string')
_any_errors_fatal = FieldAttribute(isa='bool') _any_errors_fatal = FieldAttribute(isa='bool')