@ -81,6 +81,7 @@ class AnsibleVariableVisitor:
convert_custom_scalars : bool = False ,
convert_custom_scalars : bool = False ,
convert_to_native_values : bool = False ,
convert_to_native_values : bool = False ,
apply_transforms : bool = False ,
apply_transforms : bool = False ,
visit_keys : bool = False ,
encrypted_string_behavior : EncryptedStringBehavior = EncryptedStringBehavior . DECRYPT ,
encrypted_string_behavior : EncryptedStringBehavior = EncryptedStringBehavior . DECRYPT ,
) :
) :
super ( ) . __init__ ( ) # supports StateTrackingMixIn
super ( ) . __init__ ( ) # supports StateTrackingMixIn
@ -92,6 +93,7 @@ class AnsibleVariableVisitor:
self . convert_custom_scalars = convert_custom_scalars
self . convert_custom_scalars = convert_custom_scalars
self . convert_to_native_values = convert_to_native_values
self . convert_to_native_values = convert_to_native_values
self . apply_transforms = apply_transforms
self . apply_transforms = apply_transforms
self . visit_keys = visit_keys
self . encrypted_string_behavior = encrypted_string_behavior
self . encrypted_string_behavior = encrypted_string_behavior
if apply_transforms :
if apply_transforms :
@ -134,6 +136,13 @@ class AnsibleVariableVisitor:
return result
return result
def _visit_key ( self , key : t . Any ) - > t . Any :
""" Internal implementation to recursively visit a key if visit_keys is enabled. """
if not self . visit_keys :
return key
return self . _visit ( None , key ) # key=None prevents state tracking from seeing the key as value
def _visit ( self , key : t . Any , value : _T ) - > _T :
def _visit ( self , key : t . Any , value : _T ) - > _T :
""" Internal implementation to recursively visit a data structure ' s contents. """
""" Internal implementation to recursively visit a data structure ' s contents. """
self . _current = key # supports StateTrackingMixIn
self . _current = key # supports StateTrackingMixIn
@ -144,15 +153,15 @@ class AnsibleVariableVisitor:
value = self . _template_engine . transform ( value )
value = self . _template_engine . transform ( value )
value_type = type ( value )
value_type = type ( value )
# DTFIX0: need to handle native copy for keys too
if self . convert_to_native_values and isinstance ( value , _datatag . AnsibleTaggedObject ) :
if self . convert_to_native_values and isinstance ( value , _datatag . AnsibleTaggedObject ) :
value = value . _native_copy ( )
value = value . _native_copy ( )
value_type = type ( value )
value_type = type ( value )
result : _T
result : _T
# DTFIX0: the visitor is ignoring dict/mapping keys except for debugging and schema-aware checking, it should be doing type checks on keys
# DTFIX-FUTURE: Visitor generally ignores dict/mapping keys by default except for debugging and schema-aware checking.
# keep in mind the allowed types for keys is a more restrictive set than for values (str and tagged str only, not EncryptedString)
# It could be checking keys destined for variable storage to apply more strict rules about key shape and type.
# DTFIX0: some type lists being consulted (the ones from datatag) are probably too permissive, and perhaps should not be dynamic
# DTFIX0: some type lists being consulted (the ones from datatag) are probably too permissive, and perhaps should not be dynamic
if ( result := self . _early_visit ( value , value_type ) ) is not _sentinel :
if ( result := self . _early_visit ( value , value_type ) ) is not _sentinel :
@ -160,7 +169,7 @@ class AnsibleVariableVisitor:
# DTFIX7: de-duplicate and optimize; extract inline generator expressions and fallback function or mapping for native type calculation?
# DTFIX7: de-duplicate and optimize; extract inline generator expressions and fallback function or mapping for native type calculation?
elif value_type in _ANSIBLE_ALLOWED_MAPPING_VAR_TYPES : # check mappings first, because they're also collections
elif value_type in _ANSIBLE_ALLOWED_MAPPING_VAR_TYPES : # check mappings first, because they're also collections
with self : # supports StateTrackingMixIn
with self : # supports StateTrackingMixIn
result = AnsibleTagHelper . tag_copy ( value , ( ( k , self . _visit ( k , v ) ) for k , v in value . items ( ) ) , value_type = value_type )
result = AnsibleTagHelper . tag_copy ( value , ( ( self . _visit_key ( k ) , self . _visit ( k , v ) ) for k , v in value . items ( ) ) , value_type = value_type )
elif value_type in _ANSIBLE_ALLOWED_NON_SCALAR_COLLECTION_VAR_TYPES :
elif value_type in _ANSIBLE_ALLOWED_NON_SCALAR_COLLECTION_VAR_TYPES :
with self : # supports StateTrackingMixIn
with self : # supports StateTrackingMixIn
result = AnsibleTagHelper . tag_copy ( value , ( self . _visit ( k , v ) for k , v in enumerate ( t . cast ( t . Iterable , value ) ) ) , value_type = value_type )
result = AnsibleTagHelper . tag_copy ( value , ( self . _visit ( k , v ) for k , v in enumerate ( t . cast ( t . Iterable , value ) ) ) , value_type = value_type )
@ -174,7 +183,7 @@ class AnsibleVariableVisitor:
result = str ( value ) # type: ignore[assignment]
result = str ( value ) # type: ignore[assignment]
elif self . convert_mapping_to_dict and _internal . is_intermediate_mapping ( value ) :
elif self . convert_mapping_to_dict and _internal . is_intermediate_mapping ( value ) :
with self : # supports StateTrackingMixIn
with self : # supports StateTrackingMixIn
result = { k : self . _visit ( k , v ) for k , v in value . items ( ) } # type: ignore[assignment]
result = { self . _visit_key ( k ) : self . _visit ( k , v ) for k , v in value . items ( ) } # type: ignore[assignment]
elif self . convert_sequence_to_list and _internal . is_intermediate_iterable ( value ) :
elif self . convert_sequence_to_list and _internal . is_intermediate_iterable ( value ) :
with self : # supports StateTrackingMixIn
with self : # supports StateTrackingMixIn
result = [ self . _visit ( k , v ) for k , v in enumerate ( t . cast ( t . Iterable , value ) ) ] # type: ignore[assignment]
result = [ self . _visit ( k , v ) for k , v in enumerate ( t . cast ( t . Iterable , value ) ) ] # type: ignore[assignment]