Environment Variables¶
Typed environment variable reads with defaults, validation, and config file fallback.
The problem¶
In raw Behave, reading environment variables is verbose and error-prone:
import os
api_key = os.environ.get("API_KEY")
if api_key is None:
raise ValueError("API_KEY is not set")
timeout_str = os.environ.get("TIMEOUT", "30")
try:
timeout = int(timeout_str)
except ValueError:
raise ValueError(f"TIMEOUT must be an integer, got '{timeout_str}'")
debug = os.environ.get("DEBUG", "false").lower() in ("true", "1", "yes")
The solution¶
from behave_kit import env
api_key = env("API_KEY", required=True) # str, raises if missing
timeout = env("TIMEOUT", var_type=int, default=30) # int with default
debug = env("DEBUG", var_type=bool, default=False) # bool conversion
API reference¶
- behave_kit.env.variables.env(key: str, *, required: Literal[True] = True, var_type: type[T] = <class 'str'>, default: Any = None, context: Context | None = None) T[source]¶
- behave_kit.env.variables.env(key: str, *, required: ~typing.Literal[False], var_type: type[~behave_kit.env.variables.T] = <class 'str'>, default: ~typing.Any = None, context: ~behave.runner.Context | None = None) T | None
Read environment variable
key, converted tovar_type.Resolution order:
os.environ->context.config.raw(ifcontextis given) ->default-> EnvVarError (ifrequired).
Examples¶
Required variables¶
from behave_kit import env
@given("I have an API key")
def step(context):
context.api_key = env("API_KEY", required=True)
# Raises EnvVarError if API_KEY is not set
With defaults¶
@given("I have a timeout setting")
def step(context):
context.timeout = env("TIMEOUT", var_type=int, default=30)
Type conversion¶
# String (default)
api_key = env("API_KEY")
# Integer
port = env("PORT", var_type=int, default=8080)
# Boolean — accepts: true/false, 1/0, yes/no, on/off (case-insensitive)
debug = env("DEBUG", var_type=bool, default=False)
# Float
threshold = env("THRESHOLD", var_type=float, default=0.5)
Reading from context config¶
When setup() is called with env="staging", the env() function also
checks context.config for values defined in behave.toml:
# behave.toml
[env.default]
base_url = "http://localhost:8000"
[env.staging]
base_url = "https://staging.example.com"
# steps.py
@given("I have the base URL")
def step(context):
context.base_url = env("base_url", default="http://localhost:8000")
# Reads from context.config when env var is not set
dotenv support¶
When the dotenv extra is installed (pip install behave-kit[dotenv]),
env() automatically loads variables from a .env file in the project root:
pip install "behave-kit[dotenv]"
# .env
API_KEY=secret-key
DEBUG=true
Error handling¶
from behave_kit import env
from behave_kit._core.errors import EnvVarError
try:
value = env("MISSING_VAR", required=True)
except EnvVarError as exc:
print(f"Variable not set: {exc}")
# "Variable not set: Environment variable 'MISSING_VAR' is not set"
Configuration files¶
- behave_kit.env.config.load_env_config(context: Context, env: str, config_file: str | Path = 'behave.toml', *, overrides: dict[str, str] | None = None) KitConfig[source]¶
Load
config_file, resolve theenvprofile, and attach it tocontext.config.
- class behave_kit._core.config.KitConfig(env: str, base_url: str = '', browser: str = '', timeouts: Mapping[str, int]=<factory>, credentials: Mapping[str, str]=<factory>, raw: Mapping[str, ~typing.Any]=<factory>)[source]¶
Bases:
objectRead-only, deeply immutable resolved configuration.
- classmethod from_toml(path: str | Path, *, env: str | None = None, overrides: dict[str, str] | None = None) KitConfig[source]¶
Load a
KitConfigfrom a TOML file.envdefaults tobehave_kit.envif omitted. CLI--setoverrides are dotted keys flattened to top-level strings.- Raises:
ConfigError – if the file is missing, unreadable, malformed, or the requested profile does not exist.
Environment variable snapshot¶
- behave_kit.env.snapshot.env_snapshot() Iterator[None][source]¶
Snapshot
os.environand restore it on exit.Any additions, modifications, or deletions made to environment variables inside the block are reverted when the block exits — even if an exception is raised.
The env_snapshot context manager saves the current state of
os.environ on entry and restores it on exit, including variables that
were added or deleted inside the block.
This is useful for tests that modify environment variables and need to ensure no state leaks between scenarios:
from behave_kit import env_snapshot
@when("I set a temporary env var")
def step(context):
with env_snapshot():
os.environ["TEMP_TOKEN"] = "abc123"
# ... test logic ...
# TEMP_TOKEN is gone and all other vars are restored
Restoration also happens on exceptions:
with env_snapshot():
os.environ["API_KEY"] = "test"
raise RuntimeError("something went wrong")
# os.environ is fully restored even after the exception
Profile selection¶
- behave_kit.env.profiles.select_profile(toml_data: dict[str, Any], env_name: str) dict[str, Any][source]¶
Return the
[env.<env_name>]section merged over[env.default].- Raises:
ConfigError – if
env_namehas no matching section or the TOML structure is invalid.
- behave_kit.env.profiles.apply_overrides(config_dict: dict[str, Any], overrides: dict[str, str]) dict[str, Any][source]¶
Return
config_dictwith CLI--set key=valueoverrides applied on top.
Example behave.toml:
[env.default]
base_url = "http://localhost:8000"
timeout = "30"
[env.staging]
base_url = "https://staging.example.com"
timeout = "10"
[env.production]
base_url = "https://api.example.com"
timeout = "5"