|
35 | 35 | "import logging\n", |
36 | 36 | "from jsonschema import validate, ValidationError\n", |
37 | 37 | "from typing import Union\n", |
| 38 | + "from fs.base import FS\n", |
| 39 | + "from fs.osfs import OSFS\n", |
38 | 40 | "\n", |
39 | 41 | "\n", |
40 | 42 | "logger = logging.getLogger(__name__)" |
41 | 43 | ] |
42 | 44 | }, |
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 | | - }, |
62 | 45 | { |
63 | 46 | "cell_type": "code", |
64 | 47 | "execution_count": null, |
|
232 | 215 | "\n", |
233 | 216 | "\n", |
234 | 217 | "class ConfigValidator(object):\n", |
| 218 | + "\n", |
| 219 | + " DEFAULT_STORAGE_DRIVER: FS = OSFS('.')\n", |
235 | 220 | " \n", |
236 | 221 | " 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", |
238 | 235 | " @classmethod\n", |
239 | 236 | " def get_default_json_schema(cls):\n", |
240 | 237 | " if cls.CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME in os.environ:\n", |
241 | 238 | " expected_json_schema_path = \\\n", |
242 | 239 | " 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", |
244 | 241 | " return json.load(ifile)\n", |
245 | 242 | " return None\n", |
246 | 243 | "\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", |
248 | 245 | " '''\n", |
249 | 246 | " :param json_schema: a str path to a json schema file, or a schema in dict form\n", |
250 | 247 | " \n", |
|
253 | 250 | " `CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME`\n", |
254 | 251 | " to find a JSON schema file\n", |
255 | 252 | " '''\n", |
| 253 | + " self.storage_driver = storage_driver or self.__class__.DEFAULT_STORAGE_DRIVER\n", |
256 | 254 | " 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", |
258 | 256 | " elif (default_schema := self.__class__.get_default_json_schema()):\n", |
259 | 257 | " self._json_schema = default_schema\n", |
260 | 258 | " else:\n", |
|
347 | 345 | "cell_type": "code", |
348 | 346 | "execution_count": null, |
349 | 347 | "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 | + ], |
351 | 359 | "source": [ |
352 | 360 | "#| hide\n", |
353 | 361 | "test_fail(ConfigValidator.load_validated_config, args=(example_properties_schema, {}))" |
|
395 | 403 | "cell_type": "code", |
396 | 404 | "execution_count": null, |
397 | 405 | "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 | + ], |
399 | 415 | "source": [ |
400 | 416 | "#| hide\n", |
401 | 417 | "test_fail(ConfigValidator.load_validated_config,\n", |
|
411 | 427 | "cell_type": "code", |
412 | 428 | "execution_count": null, |
413 | 429 | "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 | + ], |
415 | 439 | "source": [ |
416 | 440 | "#| hide\n", |
417 | 441 | "test_fail(ConfigValidator.load_validated_config,\n", |
|
427 | 451 | "cell_type": "code", |
428 | 452 | "execution_count": null, |
429 | 453 | "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 | + ], |
431 | 463 | "source": [ |
432 | 464 | "#| hide\n", |
433 | 465 | "test_fail(ConfigValidator.load_validated_config,\n", |
|
443 | 475 | "cell_type": "code", |
444 | 476 | "execution_count": null, |
445 | 477 | "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 | + ], |
447 | 487 | "source": [ |
448 | 488 | "#| hide\n", |
449 | 489 | "test_fail(ConfigValidator.load_validated_config,\n", |
|
455 | 495 | "}))" |
456 | 496 | ] |
457 | 497 | }, |
| 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 | + }, |
458 | 532 | { |
459 | 533 | "cell_type": "code", |
460 | 534 | "execution_count": null, |
|
0 commit comments