Skip to content

Commit 4f71e17

Browse files
committed
chg: add "override" load_dotenv() arg: set os.environ keys
current `load_dotenv()` functionality purely returns a validated dict; this commit adds an `override` argument that functions like `python-dotenv` to set the keys in `os.environ`
1 parent 3b35907 commit 4f71e17

File tree

4 files changed

+125
-7
lines changed

4 files changed

+125
-7
lines changed

nbs/00_core.ipynb

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"#| export\n",
2828
"from nbdev.showdoc import *\n",
2929
"from fastcore.test import *\n",
30+
"from unittest.mock import patch\n",
3031
"\n",
3132
"import dotenv\n",
3233
"import json\n",
@@ -280,7 +281,18 @@
280281
" return cls.load_validated_config(json_schema, dict(os.environ), **kwargs)\n",
281282
" \n",
282283
" @classmethod\n",
283-
" def load_dotenv(cls, json_schema: Union[str, dict]=None, dotenv_path: str=None, storage_driver: FS=None):\n",
284+
" def load_dotenv(cls,\n",
285+
" json_schema: Union[str, dict]=None,\n",
286+
" dotenv_path: str=None,\n",
287+
" storage_driver: FS=None,\n",
288+
" override: bool=False,\n",
289+
" ):\n",
290+
" '''\n",
291+
" :param override: set variables into os.environ where applicable; i.e.\n",
292+
" - if set in os.environ already and valid, leave alone\n",
293+
" - if not set in os.environ already, read from .env or schema default\n",
294+
" '''\n",
295+
" \n",
284296
" storage_driver = storage_driver or cls.DEFAULT_STORAGE_DRIVER\n",
285297
" if dotenv_path is None:\n",
286298
" maybe_dotenv_path = dotenv.find_dotenv() # '' if not exist\n",
@@ -301,9 +313,17 @@
301313
" if key in config and config[key] != os.environ[key]:\n",
302314
" logger.debug(f'os.environ key \"{key}\" overriding value present in {dotenv_path}')\n",
303315
" config[key] = os.environ[key]\n",
304-
" return cls.load_validated_config(\n",
316+
" validated_config = cls.load_validated_config(\n",
305317
" json_schema or cls.get_default_json_schema(storage_driver=storage_driver),\n",
306-
" config, storage_driver=storage_driver)"
318+
" config, storage_driver=storage_driver)\n",
319+
" \n",
320+
" if override:\n",
321+
" for key, value in validated_config.items():\n",
322+
" if key in os.environ:\n",
323+
" continue\n",
324+
" os.environ[key] = value\n",
325+
" \n",
326+
" return validated_config"
307327
]
308328
},
309329
{
@@ -675,6 +695,84 @@
675695
"})"
676696
]
677697
},
698+
{
699+
"cell_type": "code",
700+
"execution_count": null,
701+
"metadata": {},
702+
"outputs": [],
703+
"source": [
704+
"#| hide\n",
705+
"# test propagating values into os.environ depending on flag\n",
706+
"\n",
707+
"def test_propagate_values_into_os_environ():\n",
708+
" \n",
709+
" with memfs.open('flag.schema.json', 'w') as ofile:\n",
710+
" ofile.write(json.dumps({\n",
711+
" 'type': 'object',\n",
712+
" 'properties': {\n",
713+
" 'UNINVITED_GUEST': { 'type': 'string', 'default': 'from schema' },\n",
714+
" }\n",
715+
" }))\n",
716+
" \n",
717+
" with memfs.open('.flag-env1', 'w') as ofile:\n",
718+
" ofile.write('')\n",
719+
" \n",
720+
" with memfs.open('.flag-env2', 'w') as ofile:\n",
721+
" ofile.write('\\n'.join([\n",
722+
" 'UNINVITED_GUEST=from dotenv',\n",
723+
" ]))\n",
724+
" \n",
725+
" with memfs.open('.flag-env3', 'w') as ofile:\n",
726+
" ofile.write('\\n'.join([\n",
727+
" 'UNINVITED_GUEST=I should be ignored!',\n",
728+
" ]))\n",
729+
"\n",
730+
" mock_env = {\"already_here\": \"no touch me\"}\n",
731+
"\n",
732+
" with patch.dict('os.environ', mock_env):\n",
733+
" \n",
734+
" # don't update os.environ\n",
735+
" validated_config1 = ConfigValidator.load_dotenv(\n",
736+
" json_schema='flag.schema.json',\n",
737+
" dotenv_path='.flag-env1',\n",
738+
" storage_driver=memfs,\n",
739+
" )\n",
740+
" test_eq(os.environ.get(\"already_here\"), \"no touch me\")\n",
741+
" test_eq(os.environ.get(\"UNINVITED_GUEST\"), None)\n",
742+
" test_eq(validated_config1.get(\"UNINVITED_GUEST\"), \"from schema\")\n",
743+
" \n",
744+
" # update os.environ, loading from schema\n",
745+
" validated_config2 = ConfigValidator.load_dotenv(\n",
746+
" json_schema='flag.schema.json',\n",
747+
" dotenv_path='.flag-env1',\n",
748+
" storage_driver=memfs,\n",
749+
" override=True\n",
750+
" )\n",
751+
" test_eq(validated_config1, validated_config2)\n",
752+
" test_eq(os.environ.get(\"UNINVITED_GUEST\"), \"from schema\")\n",
753+
" \n",
754+
" os.environ.pop('UNINVITED_GUEST')\n",
755+
" # update os.environ, loading from dotenv\n",
756+
" validated_config3 = ConfigValidator.load_dotenv(\n",
757+
" json_schema='flag.schema.json',\n",
758+
" dotenv_path='.flag-env2',\n",
759+
" storage_driver=memfs,\n",
760+
" override=True\n",
761+
" )\n",
762+
" test_eq(os.environ.get(\"UNINVITED_GUEST\"), \"from dotenv\")\n",
763+
" \n",
764+
" # os.environ is set; takes precedence\n",
765+
" validated_config3 = ConfigValidator.load_dotenv(\n",
766+
" json_schema='flag.schema.json',\n",
767+
" dotenv_path='.flag-env3',\n",
768+
" storage_driver=memfs,\n",
769+
" override=True\n",
770+
" )\n",
771+
" test_eq(os.environ.get(\"UNINVITED_GUEST\"), \"from dotenv\")\n",
772+
" \n",
773+
"test_propagate_values_into_os_environ()"
774+
]
775+
},
678776
{
679777
"cell_type": "code",
680778
"execution_count": null,

schematized_config/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.6"
1+
__version__ = "0.0.7"

schematized_config/core.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# %% ../nbs/00_core.ipynb 2
77
from nbdev.showdoc import *
88
from fastcore.test import *
9+
from unittest.mock import patch
910

1011
import dotenv
1112
import json
@@ -157,7 +158,18 @@ def load_validated_environment(cls, json_schema: Union[str, dict]=None, **kwargs
157158
return cls.load_validated_config(json_schema, dict(os.environ), **kwargs)
158159

159160
@classmethod
160-
def load_dotenv(cls, json_schema: Union[str, dict]=None, dotenv_path: str=None, storage_driver: FS=None):
161+
def load_dotenv(cls,
162+
json_schema: Union[str, dict]=None,
163+
dotenv_path: str=None,
164+
storage_driver: FS=None,
165+
override: bool=False,
166+
):
167+
'''
168+
:param override: set variables into os.environ where applicable; i.e.
169+
- if set in os.environ already and valid, leave alone
170+
- if not set in os.environ already, read from .env or schema default
171+
'''
172+
161173
storage_driver = storage_driver or cls.DEFAULT_STORAGE_DRIVER
162174
if dotenv_path is None:
163175
maybe_dotenv_path = dotenv.find_dotenv() # '' if not exist
@@ -178,6 +190,14 @@ def load_dotenv(cls, json_schema: Union[str, dict]=None, dotenv_path: str=None,
178190
if key in config and config[key] != os.environ[key]:
179191
logger.debug(f'os.environ key "{key}" overriding value present in {dotenv_path}')
180192
config[key] = os.environ[key]
181-
return cls.load_validated_config(
193+
validated_config = cls.load_validated_config(
182194
json_schema or cls.get_default_json_schema(storage_driver=storage_driver),
183195
config, storage_driver=storage_driver)
196+
197+
if override:
198+
for key, value in validated_config.items():
199+
if key in os.environ:
200+
continue
201+
os.environ[key] = value
202+
203+
return validated_config

settings.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[DEFAULT]
22
repo = python-schematized-config
33
lib_name = python-schematized-config
4-
version = 0.0.6
4+
version = 0.0.7
55
min_python = 3.7
66
license = apache2
77
black_formatting = False

0 commit comments

Comments
 (0)