Replaces placeholders in a template with values from a supplied dictionary.
Will recursively replace placeholders in dictionaries and lists.
If a value has no placeholders, it will be returned unchanged.
If a template contains only a single placeholder, the placeholder will be
fully replaced with the value.
If a template contains text before or after a placeholder or there are
multiple placeholders, the placeholders will be replaced with the
corresponding variable values.
If a template contains a placeholder that is not in values, NotSet will
be returned to signify that no placeholder replacement occurred. If
template is a dictionary that contains a key with a value of NotSet,
the key will be removed in the return value unless remove_notset is set to False.
Parameters:
Name
Type
Description
Default
template
T
template to discover and replace values in
required
values
Dict[str, Any]
The values to apply to placeholders in the template
defapply_values(template:T,values:Dict[str,Any],remove_notset:bool=True)->Union[T,Type[NotSet]]:""" Replaces placeholders in a template with values from a supplied dictionary. Will recursively replace placeholders in dictionaries and lists. If a value has no placeholders, it will be returned unchanged. If a template contains only a single placeholder, the placeholder will be fully replaced with the value. If a template contains text before or after a placeholder or there are multiple placeholders, the placeholders will be replaced with the corresponding variable values. If a template contains a placeholder that is not in `values`, NotSet will be returned to signify that no placeholder replacement occurred. If `template` is a dictionary that contains a key with a value of NotSet, the key will be removed in the return value unless `remove_notset` is set to False. Args: template: template to discover and replace values in values: The values to apply to placeholders in the template remove_notset: If True, remove keys with an unset value Returns: The template with the values applied """ifisinstance(template,(int,float,bool,type(NotSet),type(None))):returntemplateifisinstance(template,str):placeholders=find_placeholders(template)ifnotplaceholders:# If there are no values, we can just use the templatereturntemplateelif(len(placeholders)==1andlist(placeholders)[0].full_match==templateandlist(placeholders)[0].typeisPlaceholderType.STANDARD):# If there is only one variable with no surrounding text,# we can replace it. If there is no variable value, we# return NotSet to indicate that the value should not be included.returnget_from_dict(values,list(placeholders)[0].name,NotSet)else:forfull_match,name,placeholder_typeinplaceholders:ifplaceholder_typeisPlaceholderType.STANDARD:value=get_from_dict(values,name,NotSet)elifplaceholder_typeisPlaceholderType.ENV_VAR:name=name.lstrip(ENV_VAR_PLACEHOLDER_PREFIX)value=os.environ.get(name,NotSet)else:continueifvalueisNotSetandnotremove_notset:continueelifvalueisNotSet:template=template.replace(full_match,"")else:template=template.replace(full_match,str(value))returntemplateelifisinstance(template,dict):updated_template={}forkey,valueintemplate.items():updated_value=apply_values(value,values,remove_notset=remove_notset)ifupdated_valueisnotNotSet:updated_template[key]=updated_valueelifnotremove_notset:updated_template[key]=valuereturnupdated_templateelifisinstance(template,list):updated_list=[]forvalueintemplate:updated_value=apply_values(value,values,remove_notset=remove_notset)ifupdated_valueisnotNotSet:updated_list.append(updated_value)returnupdated_listelse:raiseValueError(f"Unexpected template type {type(template).__name__!r}")
Determines the type of a placeholder based on its name.
Parameters:
Name
Type
Description
Default
name
str
The name of the placeholder
required
Returns:
Type
Description
PlaceholderType
The type of the placeholder
Source code in src/prefect/utilities/templating.py
353637383940414243444546474849505152
defdetermine_placeholder_type(name:str)->PlaceholderType:""" Determines the type of a placeholder based on its name. Args: name: The name of the placeholder Returns: The type of the placeholder """ifname.startswith(BLOCK_DOCUMENT_PLACEHOLDER_PREFIX):returnPlaceholderType.BLOCK_DOCUMENTelifname.startswith(VARIABLE_PLACEHOLDER_PREFIX):returnPlaceholderType.VARIABLEelifname.startswith(ENV_VAR_PLACEHOLDER_PREFIX):returnPlaceholderType.ENV_VARelse:returnPlaceholderType.STANDARD
deffind_placeholders(template:T)->Set[Placeholder]:""" Finds all placeholders in a template. Args: template: template to discover placeholders in Returns: A set of all placeholders in the template """ifisinstance(template,(int,float,bool)):returnset()ifisinstance(template,str):result=PLACEHOLDER_CAPTURE_REGEX.findall(template)return{Placeholder(full_match,name,determine_placeholder_type(name))forfull_match,nameinresult}elifisinstance(template,dict):returnset().union(*[find_placeholders(value)forkey,valueintemplate.items()])elifisinstance(template,list):returnset().union(*[find_placeholders(item)foritemintemplate])else:raiseValueError(f"Unexpected type: {type(template)}")
@inject_clientasyncdefresolve_block_document_references(template:T,client:"PrefectClient"=None)->Union[T,Dict[str,Any]]:""" Resolve block document references in a template by replacing each reference with the data of the block document. Recursively searches for block document references in dictionaries and lists. Identifies block document references by the as dictionary with the following structure: ``` { "$ref": { "block_document_id": <block_document_id> } } ``` where `<block_document_id>` is the ID of the block document to resolve. Once the block document is retrieved from the API, the data of the block document is used to replace the reference. Accessing Values: ----------------- To access different values in a block document, use dot notation combined with the block document's prefix, slug, and block name. For a block document with the structure: ```json { "value": { "key": { "nested-key": "nested-value" }, "list": [ {"list-key": "list-value"}, 1, 2 ] } } ``` examples of value resolution are as follows: 1. Accessing a nested dictionary: Format: prefect.blocks.<block_type_slug>.<block_document_name>.value.key Example: Returns {"nested-key": "nested-value"} 2. Accessing a specific nested value: Format: prefect.blocks.<block_type_slug>.<block_document_name>.value.key.nested-key Example: Returns "nested-value" 3. Accessing a list element's key-value: Format: prefect.blocks.<block_type_slug>.<block_document_name>.value.list[0].list-key Example: Returns "list-value" Default Resolution for System Blocks: ------------------------------------- For system blocks, which only contain a `value` attribute, this attribute is resolved by default. Args: template: The template to resolve block documents in Returns: The template with block documents resolved """ifisinstance(template,dict):block_document_id=template.get("$ref",{}).get("block_document_id")ifblock_document_id:block_document=awaitclient.read_block_document(block_document_id)returnblock_document.dataupdated_template={}forkey,valueintemplate.items():updated_value=awaitresolve_block_document_references(value,client=client)updated_template[key]=updated_valuereturnupdated_templateelifisinstance(template,list):return[awaitresolve_block_document_references(item,client=client)foritemintemplate]elifisinstance(template,str):placeholders=find_placeholders(template)has_block_document_placeholder=any(placeholder.typeisPlaceholderType.BLOCK_DOCUMENTforplaceholderinplaceholders)iflen(placeholders)==0ornothas_block_document_placeholder:returntemplateelif(len(placeholders)==1andlist(placeholders)[0].full_match==templateandlist(placeholders)[0].typeisPlaceholderType.BLOCK_DOCUMENT):# value_keypath will be a list containing a dot path if additional# attributes are accessed and an empty list otherwise.block_type_slug,block_document_name,*value_keypath=(list(placeholders)[0].name.replace(BLOCK_DOCUMENT_PLACEHOLDER_PREFIX,"").split(".",2))block_document=awaitclient.read_block_document_by_name(name=block_document_name,block_type_slug=block_type_slug)value=block_document.data# resolving system blocks to their data for backwards compatibilityiflen(value)==1and"value"invalue:# only resolve the value if the keypath is not already pointing to "value"iflen(value_keypath)==0orvalue_keypath[0][:5]!="value":value=value["value"]# resolving keypath/block attributesiflen(value_keypath)>0:value_keypath:str=value_keypath[0]value=get_from_dict(value,value_keypath,default=NotSet)ifvalueisNotSet:raiseValueError(f"Invalid template: {template!r}. Could not resolve the"" keypath in the block document data.")returnvalueelse:raiseValueError(f"Invalid template: {template!r}. Only a single block placeholder is"" allowed in a string and no surrounding text is allowed.")returntemplate
@inject_clientasyncdefresolve_variables(template:T,client:"PrefectClient"=None):""" Resolve variables in a template by replacing each variable placeholder with the value of the variable. Recursively searches for variable placeholders in dictionaries and lists. Strips variable placeholders if the variable is not found. Args: template: The template to resolve variables in Returns: The template with variables resolved """ifisinstance(template,str):placeholders=find_placeholders(template)has_variable_placeholder=any(placeholder.typeisPlaceholderType.VARIABLEforplaceholderinplaceholders)ifnotplaceholdersornothas_variable_placeholder:# If there are no values, we can just use the templatereturntemplateelif(len(placeholders)==1andlist(placeholders)[0].full_match==templateandlist(placeholders)[0].typeisPlaceholderType.VARIABLE):variable_name=list(placeholders)[0].name.replace(VARIABLE_PLACEHOLDER_PREFIX,"")variable=awaitclient.read_variable_by_name(name=variable_name)ifvariableisNone:return""else:returnvariable.valueelse:forfull_match,name,placeholder_typeinplaceholders:ifplaceholder_typeisPlaceholderType.VARIABLE:variable_name=name.replace(VARIABLE_PLACEHOLDER_PREFIX,"")variable=awaitclient.read_variable_by_name(name=variable_name)ifvariableisNone:template=template.replace(full_match,"")else:template=template.replace(full_match,variable.value)returntemplateelifisinstance(template,dict):return{key:awaitresolve_variables(value,client=client)forkey,valueintemplate.items()}elifisinstance(template,list):return[awaitresolve_variables(item,client=client)foritemintemplate]else:returntemplate