|
33 | 33 | "\n",
|
34 | 34 | "import dotenv\n",
|
35 | 35 | "import jsonschema\n",
|
36 |
| - "from fs.base import FS\n", |
37 |
| - "from fs.osfs import OSFS\n", |
| 36 | + "from fsspec.spec import AbstractFileSystem\n", |
| 37 | + "from fsspec.implementations.local import LocalFileSystem\n", |
| 38 | + "from fsspec.implementations.dirfs import DirFileSystem\n", |
38 | 39 | "from jsonschema import ValidationError, validate\n",
|
| 40 | + "from io import StringIO\n", |
39 | 41 | "\n",
|
40 | 42 | "logger = logging.getLogger(__name__)"
|
41 | 43 | ]
|
|
241 | 243 | " # this is a risky value: it gets reused across instances;\n",
|
242 | 244 | " # the idea is to maybe set it once and use it multiple times.\n",
|
243 | 245 | " # but in testing this smells bad\n",
|
244 |
| - " DEFAULT_STORAGE_DRIVER: FS = None # defaults to OSFS\n", |
| 246 | + " DEFAULT_STORAGE_DRIVER: AbstractFileSystem = None # defaults to DirFileSystem\n", |
245 | 247 | " \n",
|
246 | 248 | " CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME = 'CONFIG_VALIDATOR_JSON_SCHEMA'\n",
|
247 | 249 | " \n",
|
248 | 250 | " @classmethod\n",
|
249 | 251 | " def get_default_storage_driver(cls):\n",
|
250 | 252 | " if cls.DEFAULT_STORAGE_DRIVER is None:\n",
|
251 |
| - " cls.DEFAULT_STORAGE_DRIVER = OSFS(os.getcwd())\n", |
| 253 | + " cls.DEFAULT_STORAGE_DRIVER = DirFileSystem(os.getcwd())\n", |
252 | 254 | " return cls.DEFAULT_STORAGE_DRIVER\n",
|
253 | 255 | "\n",
|
254 | 256 | " \n",
|
255 | 257 | " @classmethod\n",
|
256 | 258 | " def _get_maybe_abspath_driver(cls, maybe_abspath: str):\n",
|
257 | 259 | " if os.path.isabs(maybe_abspath): # special case\n",
|
258 |
| - " return OSFS('/')\n", |
| 260 | + " return DirFileSystem('/')\n", |
259 | 261 | " else:\n",
|
260 | 262 | " return cls.get_default_storage_driver()\n",
|
261 | 263 | " \n",
|
262 | 264 | " @classmethod\n",
|
263 |
| - " def load_json(cls, json_source: Union[str, dict]=None, storage_driver: FS = None) -> dict:\n", |
| 265 | + " def load_json(cls, json_source: Union[str, dict]=None, storage_driver: AbstractFileSystem = None) -> dict:\n", |
264 | 266 | " \"\"\"\n",
|
265 | 267 | " Convenience method to return a dictionary from either a file path or an already-loaded dictionary.\n",
|
266 | 268 | "\n",
|
267 | 269 | " Args:\n",
|
268 | 270 | " - `json_source` (Union[str, dict], optional): The JSON source to load.\n",
|
269 | 271 | " This can be a file path (str) \n",
|
270 | 272 | " or an already loaded dictionary (dict). \n",
|
271 |
| - " - `storage_driver` (FS, optional): An instance of the storage driver used to load the JSON file. \n", |
272 |
| - " If not provided, OSFS from the current working dir is used.\n", |
| 273 | + " - `storage_driver` (AbstractFileSystem, optional): An instance of the storage driver used to\n", |
| 274 | + " load the JSON file. If not provided, DirFileSystem from the current\n", |
| 275 | + " working dir is used.\n", |
273 | 276 | "\n",
|
274 | 277 | " Returns:\n",
|
275 | 278 | " dict: A dictionary that was loaded from the provided `json_source`.\n",
|
|
283 | 286 | " return json.load(ifile)\n",
|
284 | 287 | "\n",
|
285 | 288 | " @classmethod\n",
|
286 |
| - " def get_default_json_schema(cls, storage_driver: FS = None) -> dict:\n", |
| 289 | + " def get_default_json_schema(cls, storage_driver: AbstractFileSystem = None) -> dict:\n", |
287 | 290 | " if cls.CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME in os.environ:\n",
|
288 | 291 | " expected_json_schema_path = \\\n",
|
289 | 292 | " os.environ[cls.CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME]\n",
|
290 | 293 | " return cls.load_json(expected_json_schema_path, storage_driver)\n",
|
291 | 294 | " return None\n",
|
292 | 295 | "\n",
|
293 |
| - " def __init__(self, json_schema: Union[str, dict]=None, storage_driver: FS=None):\n", |
| 296 | + " def __init__(self, json_schema: Union[str, dict]=None, storage_driver: AbstractFileSystem=None):\n", |
294 | 297 | " \"\"\"\n",
|
295 | 298 | " Initialize the instance with a JSON schema and a storage driver.\n",
|
296 | 299 | "\n",
|
|
299 | 302 | " If no value is provided, it will fall back to looking for an environment \n",
|
300 | 303 | " variable corresponding to the class variable \n",
|
301 | 304 | " `CONFIG_VALIDATOR_JSON_SCHEMA_ENVVAR_NAME` to find a JSON schema file.\n",
|
302 |
| - " - `storage_driver` (FS, optional): The storage driver to use. If no value is provided, \n", |
303 |
| - " `self.__class__.DEFAULT_STORAGE_DRIVER` is used.\n", |
| 305 | + " - `storage_driver` (AbstractFileSystem, optional): The storage driver to use. If no value is provided, \n", |
| 306 | + " `self.__class__.DEFAULT_STORAGE_DRIVER` is used.\n", |
304 | 307 | "\n",
|
305 | 308 | " Raises:\n",
|
306 | 309 | " Exception: An exception is raised if no valid JSON schema is provided or found.\n",
|
|
336 | 339 | " def load_dotenv(cls,\n",
|
337 | 340 | " json_schema: Union[str, dict]=None,\n",
|
338 | 341 | " dotenv_path: str=None,\n",
|
339 |
| - " storage_driver: FS=None,\n", |
| 342 | + " storage_driver: AbstractFileSystem=None,\n", |
340 | 343 | " override: bool=False,\n",
|
341 | 344 | " ):\n",
|
342 | 345 | " \"\"\"\n",
|
|
349 | 352 | " (such as an environment variable or default schema) is used.\n",
|
350 | 353 | " - `dotenv_path` (str, optional): Path to the .env file to load the variables from.\n",
|
351 | 354 | " If not provided, loads an empty dict to start.\n",
|
352 |
| - " - `storage_driver` (FS, optional): The storage driver to use for loading files. If not given,\n", |
353 |
| - " \".env\" will be attempted from the current working directory;\n", |
354 |
| - " if that does not exist, an empty dict will be used.\n", |
| 355 | + " - `storage_driver` (AbstractFileSystem, optional): The storage driver to use for loading files.\n", |
| 356 | + " If not given, \".env\" will be attempted from the current working\n", |
| 357 | + " directory; if that does not exist, an empty dict will be used.\n", |
355 | 358 | " - `override` (bool, optional): If True, variables from the .env file or schema default override existing\n",
|
356 | 359 | " `os.environ` variables.\n",
|
357 | 360 | " \"\"\"\n",
|
358 | 361 | "\n",
|
359 | 362 | " # WARN this sidesteps storage_driver!\n",
|
360 |
| - " # it will cause breakage if storage_driver != OSFS AND `.env` exists in PWD\n", |
| 363 | + " # it will cause breakage if storage_driver != DirFileSystem AND `.env` exists in PWD\n", |
361 | 364 | " if dotenv_path is None:\n",
|
362 | 365 | " maybe_dotenv_path = dotenv.find_dotenv() # '' if not exist; else abspath\n",
|
363 | 366 | " if maybe_dotenv_path:\n",
|
|
369 | 372 | " if dotenv_path:\n",
|
370 | 373 | " dotenv_storage_driver = storage_driver or cls._get_maybe_abspath_driver(dotenv_path)\n",
|
371 | 374 | " with dotenv_storage_driver.open(dotenv_path) as ifile:\n",
|
372 |
| - " config = dotenv.dotenv_values(stream=ifile)\n", |
| 375 | + " config = dotenv.dotenv_values(stream=StringIO(ifile.read().decode('utf-8')))\n", |
373 | 376 | " \n",
|
374 | 377 | " if config is None:\n",
|
375 | 378 | " dotenv_storage_driver = storage_driver or cls.get_default_storage_driver()\n",
|
376 | 379 | " if dotenv_storage_driver.exists('.env'): # unlike dotenv.find_dotenv, stay relative!\n",
|
377 | 380 | " with dotenv_storage_driver.open('.env') as ifile:\n",
|
378 |
| - " config = dotenv.dotenv_values(stream=ifile)\n", |
| 381 | + " config = dotenv.dotenv_values(stream=StringIO(ifile.read().decode('utf-8')))\n", |
379 | 382 | " \n",
|
380 | 383 | " if config is None:\n",
|
381 | 384 | " config = {}\n",
|
|
580 | 583 | "outputs": [],
|
581 | 584 | "source": [
|
582 | 585 | "#| hide\n",
|
583 |
| - "from fs.memoryfs import MemoryFS" |
| 586 | + "from morefs.memory import MemFS" |
584 | 587 | ]
|
585 | 588 | },
|
586 | 589 | {
|
|
590 | 593 | "outputs": [],
|
591 | 594 | "source": [
|
592 | 595 | "#| hide\n",
|
593 |
| - "# test ability to override the storage driver (memoryfs here)\n", |
| 596 | + "# test ability to override the storage driver (MemFS here)\n", |
594 | 597 | "\n",
|
595 |
| - "memfs = MemoryFS()\n", |
| 598 | + "memfs = MemFS()\n", |
596 | 599 | "\n",
|
597 |
| - "memfs.makedirs('extra-long-directory-place', recreate=True)\n", |
598 |
| - "with memfs.open('extra-long-directory-place/schema.json', 'w') as ofile:\n", |
| 600 | + "memfs.makedirs('extra-long-directory-place', exist_ok=True)\n", |
| 601 | + "temp_config_validator_json_schema_path = 'extra-long-directory-place/schema.json'\n", |
| 602 | + "with memfs.open(temp_config_validator_json_schema_path, 'w') as ofile:\n", |
599 | 603 | " ofile.write(json.dumps(example_properties_schema))\n",
|
600 |
| - " os.environ['CONFIG_VALIDATOR_JSON_SCHEMA'] = ofile.name\n", |
| 604 | + " os.environ['CONFIG_VALIDATOR_JSON_SCHEMA'] = temp_config_validator_json_schema_path\n", |
601 | 605 | "\n",
|
602 | 606 | "validator = ConfigValidator(storage_driver=memfs)\n",
|
603 | 607 | "validated_config = validator.load_config({\n",
|
|
615 | 619 | "\n",
|
616 | 620 | "# test loading dotenv from an arbitrary file\n",
|
617 | 621 | "\n",
|
618 |
| - "memfs.makedirs('special-bespoke-location', recreate=True)\n", |
| 622 | + "memfs.makedirs('special-bespoke-location', exist_ok=True)\n", |
619 | 623 | "with memfs.open('special-bespoke-location/my-own.env', 'w') as ofile:\n",
|
620 | 624 | " ofile.write('\\n'.join([\n",
|
621 | 625 | " 'string_value_with_enum=only',\n",
|
|
647 | 651 | "#| hide\n",
|
648 | 652 | "# test non-os FS with fallback .env path (=$PWD/.env)\n",
|
649 | 653 | "\n",
|
650 |
| - "memfs_fallback = MemoryFS()\n", |
| 654 | + "memfs_fallback = MemFS()\n", |
651 | 655 | "\n",
|
652 | 656 | "with memfs_fallback.open('schema.json', 'w') as ofile:\n",
|
653 | 657 | " ofile.write(json.dumps(example_properties_schema))\n",
|
|
714 | 718 | " }\n",
|
715 | 719 | " }))\n",
|
716 | 720 | " \n",
|
717 |
| - "memfs.makedirs('precedence-test', recreate=True)\n", |
| 721 | + "memfs.makedirs('precedence-test', exist_ok=True)\n", |
718 | 722 | "with memfs.open('precedence-test/.env', 'w') as ofile:\n",
|
719 | 723 | " ofile.write('\\n'.join([\n",
|
720 | 724 | " 'A_VALUE_TO_OVERRIDE=in dotenv',\n",
|
|
0 commit comments