Skip to content

Commit 588bb11

Browse files
committed
update to allow override file system driver using py fs
the architecture is jank because it's set at the class level. should be made more pure later. also update github links + nbdev dep
1 parent 9bd276d commit 588bb11

8 files changed

Lines changed: 151 additions & 81 deletions

File tree

nbs/00_core.ipynb

Lines changed: 102 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,30 +35,13 @@
3535
"import logging\n",
3636
"from jsonschema import validate, ValidationError\n",
3737
"from typing import Union\n",
38+
"from fs.base import FS\n",
39+
"from fs.osfs import OSFS\n",
3840
"\n",
3941
"\n",
4042
"logger = logging.getLogger(__name__)"
4143
]
4244
},
43-
{
44-
"cell_type": "code",
45-
"execution_count": null,
46-
"metadata": {},
47-
"outputs": [],
48-
"source": [
49-
"#| export\n",
50-
"def load_json(json_source: Union[str, dict]=None) -> dict:\n",
51-
" '''\n",
52-
" convenience method to return a dict from either\n",
53-
" a file path or an already-loaded dict\n",
54-
" '''\n",
55-
" if isinstance(json_source, str):\n",
56-
" with open(json_source) as ifile:\n",
57-
" return json.load(ifile)\n",
58-
" elif isinstance(json_source, dict):\n",
59-
" return json_source"
60-
]
61-
},
6245
{
6346
"cell_type": "code",
6447
"execution_count": null,
@@ -232,19 +215,33 @@
232215
"\n",
233216
"\n",
234217
"class ConfigValidator(object):\n",
218+
"\n",
219+
" DEFAULT_STORAGE_DRIVER: FS = OSFS('.')\n",
235220
" \n",
236221
" CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME = 'CONFIG_VALIDATOR_JSON_SCHEMA'\n",
237-
" \n",
222+
"\n",
223+
" @classmethod\n",
224+
" def load_json(cls, json_source: Union[str, dict]=None) -> dict:\n",
225+
" '''\n",
226+
" convenience method to return a dict from either\n",
227+
" a file path or an already-loaded dict\n",
228+
" '''\n",
229+
" if isinstance(json_source, str):\n",
230+
" with cls.DEFAULT_STORAGE_DRIVER.open(json_source) as ifile:\n",
231+
" return json.load(ifile)\n",
232+
" elif isinstance(json_source, dict):\n",
233+
" return json_source\n",
234+
"\n",
238235
" @classmethod\n",
239236
" def get_default_json_schema(cls):\n",
240237
" if cls.CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME in os.environ:\n",
241238
" expected_json_schema_path = \\\n",
242239
" os.environ[cls.CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME]\n",
243-
" with open(expected_json_schema_path) as ifile:\n",
240+
" with cls.DEFAULT_STORAGE_DRIVER.open(expected_json_schema_path) as ifile:\n",
244241
" return json.load(ifile)\n",
245242
" return None\n",
246243
"\n",
247-
" def __init__(self, json_schema: Union[str, dict]=None):\n",
244+
" def __init__(self, json_schema: Union[str, dict]=None, storage_driver: FS=None):\n",
248245
" '''\n",
249246
" :param json_schema: a str path to a json schema file, or a schema in dict form\n",
250247
" \n",
@@ -253,8 +250,9 @@
253250
" `CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME`\n",
254251
" to find a JSON schema file\n",
255252
" '''\n",
253+
" self.storage_driver = storage_driver or self.__class__.DEFAULT_STORAGE_DRIVER\n",
256254
" if isinstance(json_schema, (str, dict)):\n",
257-
" self._json_schema = load_json(json_schema)\n",
255+
" self._json_schema = self.__class__.load_json(json_schema)\n",
258256
" elif (default_schema := self.__class__.get_default_json_schema()):\n",
259257
" self._json_schema = default_schema\n",
260258
" else:\n",
@@ -347,7 +345,17 @@
347345
"cell_type": "code",
348346
"execution_count": null,
349347
"metadata": {},
350-
"outputs": [],
348+
"outputs": [
349+
{
350+
"name": "stderr",
351+
"output_type": "stream",
352+
"text": [
353+
"$:\t'string_value_with_enum' is a required property\n",
354+
"$:\t'MY_INTEGER_VALUE' is a required property\n",
355+
"$:\t'A_NUMERIC_VALUE' is a required property\n"
356+
]
357+
}
358+
],
351359
"source": [
352360
"#| hide\n",
353361
"test_fail(ConfigValidator.load_validated_config, args=(example_properties_schema, {}))"
@@ -395,7 +403,15 @@
395403
"cell_type": "code",
396404
"execution_count": null,
397405
"metadata": {},
398-
"outputs": [],
406+
"outputs": [
407+
{
408+
"name": "stderr",
409+
"output_type": "stream",
410+
"text": [
411+
"$.string_value_with_enum:\t'blah-blah' is not one of ['it', 'can', 'only', 'be', 'one', 'of', 'these']\n"
412+
]
413+
}
414+
],
399415
"source": [
400416
"#| hide\n",
401417
"test_fail(ConfigValidator.load_validated_config,\n",
@@ -411,7 +427,15 @@
411427
"cell_type": "code",
412428
"execution_count": null,
413429
"metadata": {},
414-
"outputs": [],
430+
"outputs": [
431+
{
432+
"name": "stderr",
433+
"output_type": "stream",
434+
"text": [
435+
"$.MY_INTEGER_VALUE:\t'5555.999' is not of type 'integer'\n"
436+
]
437+
}
438+
],
415439
"source": [
416440
"#| hide\n",
417441
"test_fail(ConfigValidator.load_validated_config,\n",
@@ -427,7 +451,15 @@
427451
"cell_type": "code",
428452
"execution_count": null,
429453
"metadata": {},
430-
"outputs": [],
454+
"outputs": [
455+
{
456+
"name": "stderr",
457+
"output_type": "stream",
458+
"text": [
459+
"$.A_NUMERIC_VALUE:\t'WHAT???' is not of type 'number'\n"
460+
]
461+
}
462+
],
431463
"source": [
432464
"#| hide\n",
433465
"test_fail(ConfigValidator.load_validated_config,\n",
@@ -443,7 +475,15 @@
443475
"cell_type": "code",
444476
"execution_count": null,
445477
"metadata": {},
446-
"outputs": [],
478+
"outputs": [
479+
{
480+
"name": "stderr",
481+
"output_type": "stream",
482+
"text": [
483+
"$.A_NUMERIC_VALUE:\t13.0 is less than the minimum of 22\n"
484+
]
485+
}
486+
],
447487
"source": [
448488
"#| hide\n",
449489
"test_fail(ConfigValidator.load_validated_config,\n",
@@ -455,6 +495,40 @@
455495
"}))"
456496
]
457497
},
498+
{
499+
"cell_type": "code",
500+
"execution_count": null,
501+
"metadata": {},
502+
"outputs": [],
503+
"source": [
504+
"#| hide\n",
505+
"# FIXME this test clobbers the environment\n",
506+
"# FIXME this is a code smell on the class design\n",
507+
"# here we're testing the ability to override the storage driver (memoryfs here)\n",
508+
"\n",
509+
"from fs.memoryfs import MemoryFS\n",
510+
"memfs = MemoryFS()\n",
511+
"\n",
512+
"with memfs.open('schema.json', 'w') as ofile:\n",
513+
" ofile.write(json.dumps(example_properties_schema))\n",
514+
" os.environ['CONFIG_VALIDATOR_JSON_SCHEMA'] = ofile.name\n",
515+
"\n",
516+
"ConfigValidator.DEFAULT_STORAGE_DRIVER = memfs\n",
517+
"validator = ConfigValidator()\n",
518+
"validated_config = validator.load_config({\n",
519+
" 'string_value_with_enum': 'these',\n",
520+
" 'MY_INTEGER_VALUE': '-85',\n",
521+
" 'A_NUMERIC_VALUE': '1.23e4',\n",
522+
"})\n",
523+
"\n",
524+
"test_eq(validated_config, {\n",
525+
" 'string_value_with_enum': 'these',\n",
526+
" 'MY_INTEGER_VALUE': -85,\n",
527+
" 'A_NUMERIC_VALUE': 12300.0,\n",
528+
" '_____A_STRING_VALUE____with_default__': 'underscores_and spaces',\n",
529+
"})"
530+
]
531+
},
458532
{
459533
"cell_type": "code",
460534
"execution_count": null,

nbs/01_cli.ipynb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"from schematized_config.core import (\n",
3232
" ConfigValidator,\n",
3333
" ConfigValidatorException,\n",
34-
" load_json,\n",
3534
" extract_declared_items,\n",
3635
")\n",
3736
"\n",
@@ -72,7 +71,7 @@
7271
"source": [
7372
"#| export\n",
7473
"def generate_sample_dotenv(json_schema: Union[str, dict], seed_config: dict=None):\n",
75-
" schema_dict = load_json(json_schema)\n",
74+
" schema_dict = ConfigValidator.load_json(json_schema)\n",
7675
" merged_config = dict(os.environ)\n",
7776
" default_dotenv = dotenv.dotenv_values()\n",
7877
" merged_config.update(default_dotenv)\n",

nbs/nbdev.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ project:
33

44
website:
55
title: "python-schematized-config"
6-
site-url: "https://aistriketeam.github.io/python-schematized-config"
6+
site-url: "https://tutankalex.github.io/python-schematized-config"
77
description: "validate configs using json schema"
88
repo-branch: main
9-
repo-url: "https://github.com/aistriketeam/python-schematized-config"
9+
repo-url: "https://github.com/tutankalex/python-schematized-config"

schematized_config/__init__.py

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

schematized_config/_modidx.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
d = { 'settings': { 'branch': 'main',
44
'doc_baseurl': '/python-schematized-config',
5-
'doc_host': 'https://aistriketeam.github.io',
6-
'git_url': 'https://github.com/aistriketeam/python-schematized-config',
5+
'doc_host': 'https://tutankalex.github.io',
6+
'git_url': 'https://github.com/tutankalex/python-schematized-config',
77
'lib_path': 'schematized_config'},
88
'syms': { 'schematized_config.cli': { 'schematized_config.cli.generate_sample_dotenv': ( 'cli.html#generate_sample_dotenv',
99
'schematized_config/cli.py'),
@@ -19,6 +19,8 @@
1919
'schematized_config/core.py'),
2020
'schematized_config.core.ConfigValidator.load_dotenv': ( 'core.html#configvalidator.load_dotenv',
2121
'schematized_config/core.py'),
22+
'schematized_config.core.ConfigValidator.load_json': ( 'core.html#configvalidator.load_json',
23+
'schematized_config/core.py'),
2224
'schematized_config.core.ConfigValidator.load_validated_config': ( 'core.html#configvalidator.load_validated_config',
2325
'schematized_config/core.py'),
2426
'schematized_config.core.ConfigValidator.load_validated_environment': ( 'core.html#configvalidator.load_validated_environment',
@@ -30,5 +32,4 @@
3032
'schematized_config.core.coerce_primitive_values': ( 'core.html#coerce_primitive_values',
3133
'schematized_config/core.py'),
3234
'schematized_config.core.extract_declared_items': ( 'core.html#extract_declared_items',
33-
'schematized_config/core.py'),
34-
'schematized_config.core.load_json': ('core.html#load_json', 'schematized_config/core.py')}}}
35+
'schematized_config/core.py')}}}

schematized_config/cli.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from schematized_config.core import (
88
ConfigValidator,
99
ConfigValidatorException,
10-
load_json,
1110
extract_declared_items,
1211
)
1312

@@ -32,7 +31,7 @@ def validate_env(json_schema: Union[str, dict], dotenv_path: str=None):
3231

3332
# %% ../nbs/01_cli.ipynb 4
3433
def generate_sample_dotenv(json_schema: Union[str, dict], seed_config: dict=None):
35-
schema_dict = load_json(json_schema)
34+
schema_dict = ConfigValidator.load_json(json_schema)
3635
merged_config = dict(os.environ)
3736
default_dotenv = dotenv.dotenv_values()
3837
merged_config.update(default_dotenv)

schematized_config/core.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.
22

33
# %% auto 0
4-
__all__ = ['logger', 'load_json', 'coerce_primitive_values', 'extract_declared_items', 'ConfigValidatorException',
5-
'ConfigValidator']
4+
__all__ = ['logger', 'coerce_primitive_values', 'extract_declared_items', 'ConfigValidatorException', 'ConfigValidator']
65

76
# %% ../nbs/00_core.ipynb 2
87
from nbdev.showdoc import *
@@ -15,23 +14,13 @@
1514
import logging
1615
from jsonschema import validate, ValidationError
1716
from typing import Union
17+
from fs.base import FS
18+
from fs.osfs import OSFS
1819

1920

2021
logger = logging.getLogger(__name__)
2122

2223
# %% ../nbs/00_core.ipynb 3
23-
def load_json(json_source: Union[str, dict]=None) -> dict:
24-
'''
25-
convenience method to return a dict from either
26-
a file path or an already-loaded dict
27-
'''
28-
if isinstance(json_source, str):
29-
with open(json_source) as ifile:
30-
return json.load(ifile)
31-
elif isinstance(json_source, dict):
32-
return json_source
33-
34-
# %% ../nbs/00_core.ipynb 4
3524
def coerce_primitive_values(json_schema: dict, data: dict) -> dict:
3625
'''
3726
given a JSON schema dict, return a dict where the values that have
@@ -73,7 +62,7 @@ def coerce_primitive_values(json_schema: dict, data: dict) -> dict:
7362
continue
7463
return out
7564

76-
# %% ../nbs/00_core.ipynb 7
65+
# %% ../nbs/00_core.ipynb 6
7766
def extract_declared_items(json_schema: dict, data: dict) -> dict:
7867
'''
7968
given a JSON schema dict, return a dict where
@@ -94,7 +83,7 @@ def extract_declared_items(json_schema: dict, data: dict) -> dict:
9483
out[required_property] = property_schema['default']
9584
return out
9685

97-
# %% ../nbs/00_core.ipynb 9
86+
# %% ../nbs/00_core.ipynb 8
9887
class ConfigValidatorException(Exception):
9988

10089
def __init__(self, errors):
@@ -103,19 +92,33 @@ def __init__(self, errors):
10392

10493

10594
class ConfigValidator(object):
95+
96+
DEFAULT_STORAGE_DRIVER: FS = OSFS('.')
10697

10798
CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME = 'CONFIG_VALIDATOR_JSON_SCHEMA'
108-
99+
100+
@classmethod
101+
def load_json(cls, json_source: Union[str, dict]=None) -> dict:
102+
'''
103+
convenience method to return a dict from either
104+
a file path or an already-loaded dict
105+
'''
106+
if isinstance(json_source, str):
107+
with cls.DEFAULT_STORAGE_DRIVER.open(json_source) as ifile:
108+
return json.load(ifile)
109+
elif isinstance(json_source, dict):
110+
return json_source
111+
109112
@classmethod
110113
def get_default_json_schema(cls):
111114
if cls.CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME in os.environ:
112115
expected_json_schema_path = \
113116
os.environ[cls.CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME]
114-
with open(expected_json_schema_path) as ifile:
117+
with cls.DEFAULT_STORAGE_DRIVER.open(expected_json_schema_path) as ifile:
115118
return json.load(ifile)
116119
return None
117120

118-
def __init__(self, json_schema: Union[str, dict]=None):
121+
def __init__(self, json_schema: Union[str, dict]=None, storage_driver: FS=None):
119122
'''
120123
:param json_schema: a str path to a json schema file, or a schema in dict form
121124
@@ -124,8 +127,9 @@ def __init__(self, json_schema: Union[str, dict]=None):
124127
`CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME`
125128
to find a JSON schema file
126129
'''
130+
self.storage_driver = storage_driver or self.__class__.DEFAULT_STORAGE_DRIVER
127131
if isinstance(json_schema, (str, dict)):
128-
self._json_schema = load_json(json_schema)
132+
self._json_schema = self.__class__.load_json(json_schema)
129133
elif (default_schema := self.__class__.get_default_json_schema()):
130134
self._json_schema = default_schema
131135
else:

0 commit comments

Comments
 (0)