Repository: PrefectHQ/prefect

Issue:
Add human-friendly block placeholders
[`resolve_block_document_references`](https://github.com/PrefectHQ/prefect/blob/9774e2dd87a8bd6f35967a6744ea6e90509257d2/src/prefect/utilities/templating.py#LL118C11-L118C44) currently enables the fetching of block document contents before a template is provided to a project step or worker, it only resolves block document references in the format:
```
{
    "$ref": {
        "block_document_id": <block_document_id>
    }
}
```
which is not human-friendly.

This issue is to add the ability to resolve placeholders in the format `{{ prefect.blocks.<block_type_slug>.<block_document_name> }}` (e.g., `{{ prefect.blocks.aws-credentials.dev-creds }}`). This new human-friendly placeholder can be used in worker base job templates or projects steps.


Discussion:

Resolving patch:
diff --git a/src/prefect/utilities/templating.py b/src/prefect/utilities/templating.py
--- a/src/prefect/utilities/templating.py
+++ b/src/prefect/utilities/templating.py
@@ -1,29 +1,44 @@
+import enum
 import re
-from copy import copy
-from typing import TYPE_CHECKING, Any, Dict, NamedTuple, Set, TypeVar, Union
+from typing import TYPE_CHECKING, Any, Dict, NamedTuple, Set, Type, TypeVar, Union
 
 from prefect.client.utilities import inject_client
+from prefect.utilities.annotations import NotSet
 
 if TYPE_CHECKING:
     from prefect.client.orchestration import PrefectClient
 
 
-class Unset:
-    def __bool__(self) -> bool:
-        return False
+T = TypeVar("T", str, int, float, bool, dict, list, None)
 
+PLACEHOLDER_CAPTURE_REGEX = re.compile(r"({{\s*([\w\.-]+)\s*}})")
+BLOCK_DOCUMENT_PLACEHOLDER_PREFIX = "prefect.blocks."
 
-UNSET: Unset = Unset()
 
-
-T = TypeVar("T", str, int, float, bool, dict, list)
-
-PLACEHOLDER_CAPTURE_REGEX = re.compile(r"({{\s*(\w+)\s*}})")
+class PlaceholderType(enum.Enum):
+    STANDARD = "standard"
+    BLOCK_DOCUMENT = "block_document"
 
 
 class Placeholder(NamedTuple):
     full_match: str
     name: str
+    type: PlaceholderType
+
+
+def determine_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
+    """
+    if name.startswith(BLOCK_DOCUMENT_PLACEHOLDER_PREFIX):
+        return PlaceholderType.BLOCK_DOCUMENT
+    return PlaceholderType.STANDARD
 
 
 def find_placeholders(template: T) -> Set[Placeholder]:
@@ -40,7 +55,10 @@ def find_placeholders(template: T) -> Set[Placeholder]:
         return set()
     if isinstance(template, str):
         result = PLACEHOLDER_CAPTURE_REGEX.findall(template)
-        return {Placeholder(*match) for match in result}
+        return {
+            Placeholder(full_match, name, determine_placeholder_type(name))
+            for full_match, name in result
+        }
     elif isinstance(template, dict):
         return set().union(
             *[find_placeholders(value) for key, value in template.items()]
@@ -51,7 +69,7 @@ def find_placeholders(template: T) -> Set[Placeholder]:
         raise ValueError(f"Unexpected type: {type(template)}")
 
 
-def apply_values(template: T, values: Dict[str, Any]) -> Union[T, Unset]:
+def apply_values(template: T, values: Dict[str, Any]) -> Union[T, Type[NotSet]]:
     """
     Replaces placeholders in a template with values from a supplied dictionary.
 
@@ -78,28 +96,36 @@ def apply_values(template: T, values: Dict[str, Any]) -> Union[T, Unset]:
     Returns:
         The template with the values applied
     """
-    if isinstance(template, (int, float, bool, Unset)):
+    if isinstance(template, (int, float, bool, type(NotSet), type(None))):
         return template
     if isinstance(template, str):
         placeholders = find_placeholders(template)
         if not placeholders:
             # If there are no values, we can just use the template
             return template
-        elif len(placeholders) == 1 and list(placeholders)[0].full_match == template:
+        elif (
+            len(placeholders) == 1
+            and list(placeholders)[0].full_match == template
+            and list(placeholders)[0].type is PlaceholderType.STANDARD
+        ):
             # If there is only one variable with no surrounding text,
             # we can replace it. If there is no variable value, we
             # return UNSET to indicate that the value should not be included.
-            return values.get(list(placeholders)[0].name, UNSET)
+            return values.get(list(placeholders)[0].name, NotSet)
         else:
-            for full_match, name in placeholders:
-                if name in values and values[name] is not None:
+            for full_match, name, placeholder_type in placeholders:
+                if (
+                    name in values
+                    and values[name] is not None
+                    and placeholder_type is PlaceholderType.STANDARD
+                ):
                     template = template.replace(full_match, str(values.get(name, "")))
             return template
     elif isinstance(template, dict):
         updated_template = {}
         for key, value in template.items():
             updated_value = apply_values(value, values)
-            if updated_value is not UNSET:
+            if updated_value is not NotSet:
                 updated_template[key] = updated_value
 
         return updated_template
@@ -107,17 +133,17 @@ def apply_values(template: T, values: Dict[str, Any]) -> Union[T, Unset]:
         updated_list = []
         for value in template:
             updated_value = apply_values(value, values)
-            if updated_value is not UNSET:
+            if updated_value is not NotSet:
                 updated_list.append(updated_value)
         return updated_list
     else:
-        raise ValueError(f"Unexpected type: {type(template)}")
+        raise ValueError(f"Unexpected template type {type(template).__name__!r}")
 
 
 @inject_client
 async def resolve_block_document_references(
     template: T, client: "PrefectClient" = None
-) -> T:
+) -> Union[T, Dict[str, Any]]:
     """
     Resolve block document references in a template by replacing each reference with
     the data of the block document.
@@ -149,22 +175,42 @@ async def resolve_block_document_references(
         if block_document_id:
             block_document = await client.read_block_document(block_document_id)
             return block_document.data
-        template_copy = copy(template)
-        for key, value in template_copy.items():
-            if isinstance(value, dict):
-                block_document_id = value.get("$ref", {}).get("block_document_id")
-                if block_document_id:
-                    block_document = await client.read_block_document(block_document_id)
-                    template_copy[key] = block_document.data
-                else:
-                    template_copy[key] = await resolve_block_document_references(
-                        template=value, client=client
-                    )
-        return template_copy
+        updated_template = {}
+        for key, value in template.items():
+            updated_value = await resolve_block_document_references(value)
+            updated_template[key] = updated_value
+        return updated_template
     elif isinstance(template, list):
         return [
             await resolve_block_document_references(item, client=client)
             for item in template
         ]
+    elif isinstance(template, str):
+        placeholders = find_placeholders(template)
+        has_block_document_placeholder = any(
+            placeholder.type is PlaceholderType.BLOCK_DOCUMENT
+            for placeholder in placeholders
+        )
+        if len(placeholders) == 0 or not has_block_document_placeholder:
+            return template
+        elif (
+            len(placeholders) == 1
+            and list(placeholders)[0].full_match == template
+            and list(placeholders)[0].type is PlaceholderType.BLOCK_DOCUMENT
+        ):
+            block_type_slug, block_document_name = (
+                list(placeholders)[0]
+                .name.replace(BLOCK_DOCUMENT_PLACEHOLDER_PREFIX, "")
+                .split(".")
+            )
+            block_document = await client.read_block_document_by_name(
+                name=block_document_name, block_type_slug=block_type_slug
+            )
+            return block_document.data
+        else:
+            raise ValueError(
+                f"Invalid template: {template!r}. Only a single block placeholder is"
+                " allowed in a string and no surrounding text is allowed."
+            )
 
     return template


Test patch:
diff --git a/tests/utilities/test_templating.py b/tests/utilities/test_templating.py
--- a/tests/utilities/test_templating.py
+++ b/tests/utilities/test_templating.py
@@ -1,8 +1,9 @@
 import pytest
 
 from prefect.blocks.core import Block
+from prefect.utilities.annotations import NotSet
 from prefect.utilities.templating import (
-    UNSET,
+    PlaceholderType,
     apply_values,
     find_placeholders,
     resolve_block_document_references,
@@ -67,6 +68,14 @@ def test_template_with_unconventional_spacing(self):
         names = set(p.name for p in placeholders)
         assert names == {"first_name", "last_name"}
 
+    def test_finds_block_document_placeholders(self):
+        template = "Hello {{prefect.blocks.document.name}}!"
+        placeholders = find_placeholders(template)
+        assert len(placeholders) == 1
+        placeholder = placeholders.pop()
+        assert placeholder.name == "prefect.blocks.document.name"
+        assert placeholder.type is PlaceholderType.BLOCK_DOCUMENT
+
 
 class TestApplyValues:
     def test_apply_values_simple_string_with_one_placeholder(self):
@@ -102,8 +111,8 @@ def test_apply_values_nested_dictionary_with_placeholders(self):
             "age": 30,
         }
 
-    def test_apply_values_dictionary_with_UNSET_value_removed(self):
-        template = {"name": UNSET, "age": "{{age}}"}
+    def test_apply_values_dictionary_with_notset_value_removed(self):
+        template = {"name": NotSet, "age": "{{age}}"}
         values = {"age": 30}
         assert apply_values(template, values) == {"age": 30}
 
@@ -127,6 +136,13 @@ def test_apply_values_float_input(self):
     def test_apply_values_boolean_input(self):
         assert apply_values(True, {"flag": False}) is True
 
+    def test_apply_values_none_input(self):
+        assert apply_values(None, {"key": "value"}) is None
+
+    def test_does_not_apply_values_to_block_document_placeholders(self):
+        template = "Hello {{prefect.blocks.document.name}}!"
+        assert apply_values(template, {"name": "Alice"}) == template
+
 
 class TestResolveBlockDocumentReferences:
     @pytest.fixture
@@ -163,26 +179,78 @@ async def test_resolve_block_document_references_with_nested_block_document_refe
                 "other_nested_key": {"$ref": {"block_document_id": block_document_id}},
             }
         }
-        block_document_data = {"a": 1, "b": "hello"}
+        block_document = await orion_client.read_block_document(block_document_id)
 
         result = await resolve_block_document_references(template, client=orion_client)
 
         assert result == {
             "key": {
-                "nested_key": block_document_data,
-                "other_nested_key": block_document_data,
+                "nested_key": block_document.data,
+                "other_nested_key": block_document.data,
             }
         }
 
     async def test_resolve_block_document_references_with_list_of_block_document_references(
         self, orion_client, block_document_id
     ):
-        # given
         template = [{"$ref": {"block_document_id": block_document_id}}]
-        block_document_data = {"a": 1, "b": "hello"}
+        block_document = await orion_client.read_block_document(block_document_id)
+
+        result = await resolve_block_document_references(template, client=orion_client)
+
+        assert result == [block_document.data]
+
+    async def test_resolve_block_document_references_with_dot_delimited_syntax(
+        self, orion_client, block_document_id
+    ):
+        template = {"key": "{{ prefect.blocks.arbitraryblock.arbitrary-block }}"}
+
+        block_document = await orion_client.read_block_document(block_document_id)
 
-        # when
         result = await resolve_block_document_references(template, client=orion_client)
 
-        # then
-        assert result == [block_document_data]
+        assert result == {"key": block_document.data}
+
+    async def test_resolve_block_document_references_raises_on_multiple_placeholders(
+        self, orion_client
+    ):
+        template = {
+            "key": (
+                "{{ prefect.blocks.arbitraryblock.arbitrary-block }} {{"
+                " another_placeholder }}"
+            )
+        }
+
+        with pytest.raises(
+            ValueError,
+            match=(
+                "Only a single block placeholder is allowed in a string and no"
+                " surrounding text is allowed."
+            ),
+        ):
+            await resolve_block_document_references(template, client=orion_client)
+
+    async def test_resolve_block_document_references_raises_on_extra_text(
+        self, orion_client
+    ):
+        template = {
+            "key": "{{ prefect.blocks.arbitraryblock.arbitrary-block }} extra text"
+        }
+
+        with pytest.raises(
+            ValueError,
+            match=(
+                "Only a single block placeholder is allowed in a string and no"
+                " surrounding text is allowed."
+            ),
+        ):
+            await resolve_block_document_references(template, client=orion_client)
+
+    async def test_resolve_block_document_references_does_not_change_standard_placeholders(
+        self,
+    ):
+        template = {"key": "{{ standard_placeholder }}"}
+
+        result = await resolve_block_document_references(template)
+
+        assert result == template