diff --git a/src/prefect/settings.py b/src/prefect/settings.py index 357d51d0098e..3ee6f0b928f4 100644 --- a/src/prefect/settings.py +++ b/src/prefect/settings.py @@ -323,7 +323,11 @@ def templater(settings, value): setting.name: setting.value_from(settings) for setting in upstream_settings } template = string.Template(str(value)) - return original_type(template.substitute(template_values)) + # Note the use of `safe_substitute` to avoid raising an exception if a + # template value is missing. In this case, template values will be left + # as-is in the string. Using `safe_substitute` prevents us raising when + # the DB password contains a `$` character. + return original_type(template.safe_substitute(template_values)) return templater diff --git a/tests/test_settings.py b/tests/test_settings.py index 0b434ec72284..ac45a3b2a4d7 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -569,6 +569,31 @@ def test_unknown_driver_raises(self): ): pass + def test_connection_string_with_dollar_sign(self): + """ + Regression test for https://github.com/PrefectHQ/prefect/issues/11067. + + This test ensures that passwords with dollar signs do not cause issues when + templating the connection string. + """ + with temporary_settings( + { + PREFECT_API_DATABASE_CONNECTION_URL: ( + "postgresql+asyncpg://" + "the-user:the-$password@" + "the-database-server.example.com:5432" + "/the-database" + ), + PREFECT_API_DATABASE_USER: "the-user", + } + ): + assert PREFECT_API_DATABASE_CONNECTION_URL.value() == ( + "postgresql+asyncpg://" + "the-user:the-$password@" + "the-database-server.example.com:5432" + "/the-database" + ) + class TestTemporarySettings: def test_temporary_settings(self):