Chris Marriott
12/17/2025, 3:49 PMMarvin
12/17/2025, 3:49 PMMarvin
12/17/2025, 3:52 PMprefect.variables.Variable.get("x") outside of a task or flow. It’s “sync-compatible,” so it will work in regular Python code as well as inside flows/tasks.
Key notes and caveats:
- Sync vs async:
- Outside flows/tasks, you can call it synchronously.
- In async contexts (e.g., async def flow), await Variable.get(...) also works.
- Import-time caveat: Avoid calling it at module import time (top-level constants), since it will make a blocking HTTP call to the Prefect API when the module is imported. Prefer calling it inside functions/flows.
- Client/API requirements: Outside a flow/task run, Prefect will create its own API client under the hood. Ensure your `PREFECT_API_URL`/auth is configured and the API is reachable.
- Return type: If the variable exists, you get a Variable object (use .value). If it doesn’t and you provided default=..., the default is returned directly.
Examples:
# Good: inside a function or flow
from prefect.variables import Variable
def load_config():
v = Variable.get("my_config") # returns a Variable if found
return v.value if hasattr(v, "value") else v # handle default case
# Avoid: at import time (this will run at import and block on an API call)
CONFIG = Variable.get("my_config").value
There’s also a convenience function in 2.x:
from prefect import variables
val = variables.get("my_config") # returns just the value (str), not a Variable
References:
- 2.20.20 source (Variable implementation): prefect/variables.py
- 2.20.20 tests showing usage outside flows: tests/test_variables.py
If you’re planning to load variables at import time for configuration, I can suggest a safe pattern for that (e.g., a lazy accessor or small helper function).Chris Marriott
12/17/2025, 3:58 PMChris Marriott
12/17/2025, 4:00 PMMarvin
12/17/2025, 4:01 PMMarvin
12/17/2025, 4:03 PMVariable.get(name, default=None) returns a str only when the variable does not exist and you provided a default string. The method returns the default value “as-is” in that case.
So the return types break down as:
- Variable exists → returns a Variable instance (access the string with .value)
- Variable missing and default is a string → returns that str
- Variable missing and default is None (or omitted) → returns None
Example:
from prefect.variables import Variable
# Variable exists
v = Variable.get("existing")
assert hasattr(v, "value") # True
# Variable missing, default provided
v = Variable.get("missing", default="fallback")
assert isinstance(v, str) # True
# Variable missing, no default
v = Variable.get("missing")
assert v is None # True
If you prefer to always end up with the string value, you can normalize like:
res = Variable.get("maybe_missing", default="fallback")
value = res.value if hasattr(res, "value") else res
Note: There’s also a convenience function prefect.variables.get(...) in 2.x that always returns Optional[str] (it unwraps .value for you), but it’s deprecated:
- Source (2.20.20): prefect/variables.py
- API reference: Variables APIChris Marriott
12/17/2025, 4:12 PMvar_val = var.value if hasattr(var, "value") else var because .value may not exist as an attribute of var.
It doesn't mind this, though:
from prefect.variables import Variable
var = Variable.get("env")
match var:
case Variable():
var_val = var.value
case None:
raise ValueError("Variable 'env' is not set")
case str():
var_val = var
case _:
raise TypeError(f"Unexpected type for variable 'env': {type(var)}")Marvin
12/17/2025, 4:12 PMMarvin
12/17/2025, 4:12 PMhasattr doesn’t help Pylance narrow a union type. Static type checkers won’t infer that var is a Variable just because .value exists at runtime.
Your match version is a solid approach in 3.10+ and reads nicely.
If you prefer without match, this pattern type-checks cleanly with Pylance:
from prefect.variables import Variable
var = Variable.get("env")
if var is None:
raise ValueError("Variable 'env' is not set")
if isinstance(var, Variable):
var_val = var.value # Narrowed to Variable
else:
var_val = var # Narrowed to str
If you want to normalize this everywhere, a tiny helper with overloads works well:
from typing import Optional, overload
from prefect.variables import Variable
@overload
def get_variable_value(name: str) -> Optional[str]: ...
@overload
def get_variable_value(name: str, *, default: str) -> str: ...
@overload
def get_variable_value(name: str, *, default: None) -> None: ...
def get_variable_value(name: str, *, default: Optional[str] = None) -> Optional[str]:
res = Variable.get(name, default=default)
if res is None:
return None
return res.value if isinstance(res, Variable) else res
# Usage
val = get_variable_value("env") # Optional[str]
val_required = get_variable_value("env", default="dev") # str
Note: In 2.20.20 there’s also prefect.variables.get(...) which always returns Optional[str], but it’s deprecated. If you want that behavior without deprecation, the helper above is a good drop-in.
References:
- Source for Variable.get (2.20.20): variables.py
- API reference: Variables API