<@ULVA73B9P>: Is the expectation for Variable inst...
# marvin-ai
c
@Marvin: Is the expectation for Variable instance to be accessed (via prefect.variables.Variable.get("x")) only within in task or flow? For context, we're using Prefect version 2.20.20.
m
thought for 180.6 seconds
Short answer: No — in Prefect 2.20.20 you can call
prefect.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:
Copy code
# 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:
Copy code
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).
c
According to the type hint, the return type for Variable.get() can be Variable, str, or None. A None, I understand; that's the fallback value when the variable doesn't exist. But what could cause the return type to be a str instead of a Variable instance?
@Marvin: According to the type hint, the return type for Variable.get() can be Variable, str, or None. A None, I understand; that's the fallback value when the variable doesn't exist. But what could cause the return type to be a str instead of a Variable instance?
m
thought for 132.5 seconds
Great question. In Prefect 2.20.20,
Variable.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:
Copy code
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:
Copy code
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 API
c
@Marvin: Pylance doesn't appear to like
var_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:
Copy code
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)}")
m
thought for 16.9 seconds
You’re right:
hasattr
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:
Copy code
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:
Copy code
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