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 to var_type.

Resolution order: os.environ -> context.config.raw (if context is given) -> default -> EnvVarError (if required).

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 the env profile, and attach it to context.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: object

Read-only, deeply immutable resolved configuration.

base_url: str = ''
browser: str = ''
credentials: Mapping[str, str]
env: str
classmethod from_toml(path: str | Path, *, env: str | None = None, overrides: dict[str, str] | None = None) KitConfig[source]

Load a KitConfig from a TOML file.

env defaults to behave_kit.env if omitted. CLI --set overrides are dotted keys flattened to top-level strings.

Raises:

ConfigError – if the file is missing, unreadable, malformed, or the requested profile does not exist.

get(key: str, default: Any = None) Any[source]

Return the raw configuration value for key or default.

raw: Mapping[str, Any]
timeouts: Mapping[str, int]

Environment variable snapshot

behave_kit.env.snapshot.env_snapshot() Iterator[None][source]

Snapshot os.environ and 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_name has 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_dict with CLI --set key=value overrides 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"