How to Use required_env for Environment Variable Management¶
TL;DR¶
Use the required_env field on connections to declare which environment variables your integration needs. oaa-runner validates them before running and auto-loads them from .env — no more calling load_dotenv() in every module.
Prerequisites¶
An integration configured with
config.yamlA
.envfile containing your secrets (or environment variables set in your shell)
Why required_env?¶
Without required_env, every integration module must:
Call
load_dotenv()at the top of the file and again insidefetch()Use
params_or_env()oros.getenv()to read each secretHope the developer remembers to set the right variables
With required_env:
Validation fails fast — missing variables are caught before any data is fetched
No
load_dotenv()needed — oaa-runner loads.envfrom the config directory automaticallyAuto-injection — variables are injected into
connection.params, so you access them directlySelf-documenting — the config file explicitly declares which secrets each connection needs
Steps¶
Step 1: Declare required variables in config.yaml¶
Add a required_env list under each connection that needs secrets:
connections:
my_api:
connection_type: custom
custom_module: modules.myapp
required_env:
- MY_API_BASE_URL
- MY_API_KEY
params:
timeout: 30
VEZA_URL and VEZA_API_KEY are always implicitly required — you do not need to list them in required_env.
Step 2: Add variables to your .env file¶
Place the .env file in the same directory as your config.yaml:
# .env (never commit this file!)
VEZA_URL=https://myorg.vezacloud.com
VEZA_API_KEY=veza-api-key-here
MY_API_BASE_URL=https://api.example.com
MY_API_KEY=sk-abc123def456
Add .env to .gitignore:
echo ".env" >> .gitignore
Step 3: Access variables in your module¶
With required_env, variables are auto-injected into connection.params. No load_dotenv() call is needed:
Before (without required_env):
from dotenv import load_dotenv
from oaa.modules.params_or_env import params_or_env
load_dotenv()
class MyAPI:
def __init__(self, connection, source):
self.base_url = params_or_env(connection, source, 'MY_API_BASE_URL')
self.api_key = params_or_env(connection, source, 'MY_API_KEY')
After (with required_env):
# No load_dotenv() needed — oaa-runner handles it
class MyAPI:
def __init__(self, connection, source):
self.base_url = connection.params['MY_API_BASE_URL']
self.api_key = connection.params['MY_API_KEY']
Step 4: Validate your configuration¶
Before running, validate that all required variables are present:
oaa app validate --config=config.yaml
You will see output like:
==================================================
STEP 2: Validating required environment variables...
==================================================
✅ All required environment variables are set (4 checked)
If a variable is missing:
==================================================
STEP 2: Validating required environment variables...
==================================================
❌ Required environment variable "MY_API_KEY" is not set.
Declared in connection "my_api" required_env.
Set it in your .env file or shell environment.
❌ VALIDATION FAILED - Please review errors above
How It Works¶
Validation¶
Before each run, oaa-runner checks that every variable listed in required_env is present in the environment. VEZA_URL and VEZA_API_KEY are always checked regardless. If any variable is missing, the run fails immediately with a clear error message naming the variable and the connection that declared it.
Auto-loading¶
When required_env is present, oaa-runner loads .env from the same directory as the config.yaml file. This replaces the need for individual modules to call load_dotenv().
Injection¶
After loading, values from the environment are injected into connection.params. Given this config:
connections:
my_api:
connection_type: custom
custom_module: modules.myapp
required_env:
- MY_API_KEY
params:
base_url: https://api.example.com
And this .env:
MY_API_KEY=sk-abc123
The connection.params dict inside fetch() contains:
{
"base_url": "https://api.example.com", # from config
"MY_API_KEY": "sk-abc123", # auto-loaded from .env
}
Existing params values are preserved. Environment values override params values when the same key appears in both.
Connection Types¶
required_env works on all connection types:
Custom connections¶
connections:
my_api:
connection_type: custom
custom_module: modules.myapp
required_env:
- MY_API_KEY
- MY_API_SECRET
params:
base_url: https://api.example.com
API connections¶
connections:
external_api:
connection_type: api
uri: https://api.example.com
fetch_module: api_fetch
required_env:
- API_TOKEN
DB connections¶
connections:
my_db:
connection_type: db
DB_DIALECT: POSTGRES
DB_SERVER: db.example.com
DB_NAME: mydb
DB_USER: sa
DB_PASSWORD: "!env"
required_env:
- DB_PASSWORD
- DB_SERVER
Note: DB connections already support the
!envsyntax for individual fields. Userequired_envwhen you want pre-flight validation or need to reference the same variable from your module code.
CSV and Veza API connections¶
connections:
my_csv:
connection_type: csv
required_env:
- CSV_ENCRYPTION_KEY
Multiple Connections¶
Each connection declares its own requirements:
connections:
hr_api:
connection_type: custom
custom_module: modules.hr
required_env:
- HR_API_KEY
params:
base_url: https://hr.example.com
iam_api:
connection_type: custom
custom_module: modules.iam
required_env:
- IAM_CLIENT_ID
- IAM_CLIENT_SECRET
params:
base_url: https://iam.example.com
All variables across all connections are validated before the run starts.
Migration Guide¶
To migrate an existing integration from load_dotenv() to required_env:
Add
required_envtoconfig.yamlfor each connection that uses secretsRemove
load_dotenv()calls from your module filesReplace
params_or_env()calls with directconnection.params[...]accessRemove
from dotenv import load_dotenvimportsValidate with
oaa app validate --config=config.yaml
Before¶
# modules/myapp.py
from dotenv import load_dotenv
from oaa.modules.params_or_env import params_or_env
load_dotenv()
class MyAppAPI:
def __init__(self, connection, source):
self.base_url = params_or_env(connection, source, 'MYAPP_API_BASE_URL')
self.api_key = params_or_env(connection, source, 'MYAPP_API_KEY')
# config.yaml
connections:
myapp:
connection_type: custom
custom_module: modules.myapp
params:
base_url: https://api.myapp.com
After¶
# modules/myapp.py — no load_dotenv needed
class MyAppAPI:
def __init__(self, connection, source):
self.base_url = connection.params['MYAPP_API_BASE_URL']
self.api_key = connection.params['MYAPP_API_KEY']
# config.yaml
connections:
myapp:
connection_type: custom
custom_module: modules.myapp
required_env:
- MYAPP_API_BASE_URL
- MYAPP_API_KEY
params:
base_url: https://api.myapp.com
Best Practices¶
1. Use load_dotenv() without override parameter¶
In your module’s fetch() function, always use load_dotenv() without the override=True parameter. This allows externally-set environment variables (e.g., from test runners or CI/CD) to take precedence over .env file values:
Incorrect (overrides external env vars):
from dotenv import load_dotenv
def fetch(**kwargs):
load_dotenv(override=True) # ❌ Overwrites MOCK_RESULTS=true prefix, VEZA_URL, etc.
...
Correct (respects external env vars):
from dotenv import load_dotenv
def fetch(**kwargs):
load_dotenv() # ✓ External env vars take precedence
...
2. Never include testing flags in .env files¶
Testing flags like MOCK_RESULTS and VEZA_URL should not be in .env. Instead, test via environment variable prefix:
Incorrect .env:
MOCK_RESULTS=false # ❌ Breaks test prefix pattern
VEZA_URL=https://dummy # ❌ Interferes with real credentials
Correct .env:
MYAPP_API_KEY=xxxxx
MYAPP_BASE_URL=https://api.myapp.com
Test command:
MOCK_RESULTS=true oaa-run ... run
3. VEZA_URL and VEZA_API_KEY are implicitly required¶
Even without explicit required_env configuration, these two variables are always validated at startup. Use --no-push=True in dry-run mode to skip Veza credential validation:
oaa app run --config=config.yaml --no-push=True
Troubleshooting¶
Error: Required environment variable "MY_API_KEY" is not set
Verify
.envfile exists in the same directory asconfig.yamlCheck that the variable name matches exactly (case-sensitive)
Ensure no extra whitespace in
.envvalues
Error: Required environment variable "VEZA_URL" is not set
VEZA_URLandVEZA_API_KEYare always required, even withoutrequired_envAdd them to your
.envfile or set them in your shell environment
Variable injected but empty string
Check that your
.envfile has the value after the=signEmpty strings pass validation (only missing variables fail)
Module still calling load_dotenv()
Safe to leave
load_dotenv()calls in existing modules for backward compatibilityThe call is idempotent — calling it twice has no negative effect
Next Steps¶
How to Authenticate with API Keys - For API key authentication patterns
How to Authenticate with Client Credentials - For OAuth 2.0 flows
Configuration Schema Reference - Full config.yaml reference