1+ import re
2+ import os
13from typing import Any , Dict
24import toml
35
@@ -11,6 +13,8 @@ def is_uri(s: str) -> bool:
1113
1214
1315def _apply_config (config : Dict [str , Any ], run_name : str , kw : Dict [str , Any ]):
16+ _resolve_env (config )
17+
1418 # Load config
1519 databases = config .pop ("database" , {})
1620 runs = config .pop ("run" , {})
@@ -77,6 +81,32 @@ def _apply_config(config: Dict[str, Any], run_name: str, kw: Dict[str, Any]):
7781 return new_kw
7882
7983
84+ # There are no strict requirements for the environment variable name format.
85+ # But most shells only allow alphanumeric characters and underscores.
86+ # https://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html
87+ # "Environment variable names (...) consist solely of uppercase letters, digits, and the '_' (underscore)"
88+ _ENV_VAR_PATTERN = r"\$\{([A-Za-z0-9_]+)\}"
89+
90+
91+ def _resolve_env (config : Dict [str , Any ]):
92+ """
93+ Resolve environment variables referenced as ${ENV_VAR_NAME}.
94+ Missing environment variables are replaced with an empty string.
95+ """
96+ for key , value in config .items ():
97+ if isinstance (value , dict ):
98+ _resolve_env (value )
99+ elif isinstance (value , str ):
100+ config [key ] = re .sub (_ENV_VAR_PATTERN , _replace_match , value )
101+
102+
103+ def _replace_match (match : re .Match ) -> str :
104+ # Lookup referenced variable in environment.
105+ # Replace with empty string if not found
106+ referenced_var = match .group (1 ) # group(0) is the whole string
107+ return os .environ .get (referenced_var , "" )
108+
109+
80110def apply_config_from_file (path : str , run_name : str , kw : Dict [str , Any ]):
81111 with open (path ) as f :
82112 return _apply_config (toml .load (f ), run_name , kw )
0 commit comments