-
-
Notifications
You must be signed in to change notification settings - Fork 1
Added utility for loading tiered config from file system. #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
66 commits
Select commit
Hold shift + click to select a range
67f376f
Tests passing need to work on other checks.
BatuhanSA 0ff308a
Worked on linting and typing.
BatuhanSA b6148cf
Changed the Mapping import.
BatuhanSA d4556b9
Added an option to specify the config file name.
BatuhanSA cca8ea6
Reordered some checks.
BatuhanSA 588f452
Generalizes the use case to edq.
BatuhanSA f34c44f
Revised unittest.
BatuhanSA bf98083
Revised config and config tests.
BatuhanSA 03a7d64
resolved conflicts.
BatuhanSA e550e8b
pointed to defualt config file name in tests.
BatuhanSA aa14f7f
Revised the first pass.
BatuhanSA 3b22855
Passed in the msg for unittesting.
BatuhanSA 9f0be06
Reviewd the PR, need to work on testing comments.
BatuhanSA a9efa0b
Reviewed testing.
BatuhanSA 769527c
Resolved conflicts.
BatuhanSA ee3d21b
Revised questions in mind and added missing config directory.
BatuhanSA c06b6fc
Testing Windows permission error on directories when loading JSON.
BatuhanSA 0f61041
Changed the way equals returns for ConfigSource object.
BatuhanSA 4cc295b
Tested all combinations of 4 config labels.
BatuhanSA 9d25595
Revised the structure of tests, need to go over them.
BatuhanSA bcaa609
Revised testcases and added file tree creation when testing.
BatuhanSA 05b7966
Removing files that need to be in the next PR.
BatuhanSA 24f240e
Reviewed previous PR comments.
BatuhanSA cdeee74
Revised the second pass, need to work on testing and README.
BatuhanSA 67634ee
Changed the order of the test structure. Made a single temp_dir for a…
BatuhanSA f2ef098
Added config on README
BatuhanSA 707a351
Got rid of points for Global config.
BatuhanSA 683140b
Polished of the README.
BatuhanSA 97d16d9
Fixed the url for platformdir.
BatuhanSA cf20ac6
Fixed the url for platformdir.
BatuhanSA 5edb54a
Made the local config description better.
BatuhanSA aed3ffb
Made the local config description better.
BatuhanSA 7c905b5
Revised for 3rd pass
BatuhanSA 078eaee
Corrected inconsistencies.
BatuhanSA c6c7a0a
Revised 4th pass.
BatuhanSA 53d8bf4
Revised the README, converted the table format.
BatuhanSA 22797db
Got rid of a inconsistency.
BatuhanSA c753729
2nd revision of README
BatuhanSA 629f110
Revised it again.
BatuhanSA f223a92
Added error description.
BatuhanSA 418b6cb
Added examples to the README.
BatuhanSA 869d963
Made clarifications.
BatuhanSA 033d119
Revised the overriding on skip keys.
BatuhanSA 51c139b
Moved config from util to core.
BatuhanSA b15cbd0
Revised the README with 5th pass.
BatuhanSA 568830f
Added 'only' to local and global config description.
BatuhanSA edac1c7
Added the table for command line options.
BatuhanSA 385bad9
Revised the 6th pass.
BatuhanSA 72a07ea
Revised the CLI options table.
BatuhanSA 41c56a5
Revised 7th pass.
BatuhanSA 9c772ef
Revised the comments.
BatuhanSA fb03e78
Revised the tables and some of the examples given.
BatuhanSA 79d5b03
Table structure correction.
BatuhanSA 4f86412
Corrected the wording incosistancy on the table.
BatuhanSA c37a94a
Simplified config file description on the config source table.
BatuhanSA c1cca7d
Revised 7th pass one last time.
BatuhanSA 0b1ef27
Missing line break.
BatuhanSA e016837
Revised the 8th pass.
BatuhanSA 704fc7c
Revised the introductory sentences for each configuration section.
BatuhanSA ada5646
Revised 8th pass one last time.
BatuhanSA 4f71222
Got rid of a extra white space.
BatuhanSA 9eed911
Changed the global config usage example.
BatuhanSA 3595c91
Revised 9th pass.
BatuhanSA 9d493e8
Went over 9th pass one last time.
BatuhanSA 3b77f27
Made the changes we talked about.
BatuhanSA c3b49cd
Made the changes we talked about.
BatuhanSA File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| import argparse | ||
| import os | ||
| import typing | ||
|
|
||
| import platformdirs | ||
|
|
||
| import edq.util.dirent | ||
| import edq.util.json | ||
|
|
||
| CONFIG_SOURCE_GLOBAL: str = "<global config file>" | ||
| CONFIG_SOURCE_LOCAL: str = "<local config file>" | ||
| CONFIG_SOURCE_CLI: str = "<cli config file>" | ||
| CONFIG_SOURCE_CLI_BARE: str = "<cli argument>" | ||
|
|
||
| CONFIG_PATHS_KEY: str = 'config_paths' | ||
| DEFAULT_CONFIG_FILENAME: str = "edq-config.json" | ||
|
|
||
| class ConfigSource: | ||
| """ A class for storing config source information. """ | ||
|
|
||
| def __init__(self, label: str, path: typing.Union[str, None] = None) -> None: | ||
| self.label = label | ||
| """ The label identifying the config (see CONFIG_SOURCE_* constants). """ | ||
|
|
||
| self.path = path | ||
| """ The path of where the config was soruced from. """ | ||
|
|
||
| def __eq__(self, other: object) -> bool: | ||
| if (not isinstance(other, ConfigSource)): | ||
| return False | ||
|
|
||
| return ((self.label == other.label) and (self.path == other.path)) | ||
|
|
||
| def __str__(self) -> str: | ||
| return f"({self.label}, {self.path})" | ||
|
|
||
| def get_tiered_config( | ||
| config_file_name: str = DEFAULT_CONFIG_FILENAME, | ||
| legacy_config_file_name: typing.Union[str, None] = None, | ||
| global_config_path: typing.Union[str, None] = None, | ||
| skip_keys: typing.Union[list, None] = None, | ||
| cli_arguments: typing.Union[dict, argparse.Namespace, None] = None, | ||
| local_config_root_cutoff: typing.Union[str, None] = None, | ||
| ) -> typing.Tuple[typing.Dict[str, str], typing.Dict[str, ConfigSource]]: | ||
| """ | ||
| Load all configuration options from files and command-line arguments. | ||
| Returns a configuration dictionary with the values based on tiering rules and a source dictionary mapping each key to its origin. | ||
| """ | ||
|
|
||
| if (global_config_path is None): | ||
| global_config_path = platformdirs.user_config_dir(config_file_name) | ||
|
|
||
| if (skip_keys is None): | ||
| skip_keys = [CONFIG_PATHS_KEY] | ||
|
|
||
| if (cli_arguments is None): | ||
| cli_arguments = {} | ||
|
|
||
| config: typing.Dict[str, str] = {} | ||
| sources: typing.Dict[str, ConfigSource] = {} | ||
|
|
||
| # Ensure CLI arguments are always a dict, even if provided as argparse.Namespace. | ||
| if (isinstance(cli_arguments, argparse.Namespace)): | ||
| cli_arguments = vars(cli_arguments) | ||
|
|
||
| # Check the global user config file. | ||
| if (os.path.isfile(global_config_path)): | ||
| _load_config_file(global_config_path, config, sources, CONFIG_SOURCE_GLOBAL) | ||
|
|
||
| # Check the local user config file. | ||
| local_config_path = _get_local_config_path( | ||
| config_file_name = config_file_name, | ||
| legacy_config_file_name = legacy_config_file_name, | ||
| local_config_root_cutoff = local_config_root_cutoff, | ||
| ) | ||
|
|
||
| if (local_config_path is not None): | ||
| _load_config_file(local_config_path, config, sources, CONFIG_SOURCE_LOCAL) | ||
|
|
||
| # Check the config file specified on the command-line. | ||
| config_paths = cli_arguments.get(CONFIG_PATHS_KEY, []) | ||
| if (config_paths is not None): | ||
| for path in config_paths: | ||
| _load_config_file(path, config, sources, CONFIG_SOURCE_CLI) | ||
|
|
||
| # Finally, any command-line options. | ||
| for (key, value) in cli_arguments.items(): | ||
| if (key in skip_keys): | ||
| continue | ||
|
|
||
| if ((value is None) or (value == '')): | ||
| continue | ||
|
|
||
| config[key] = value | ||
| sources[key] = ConfigSource(label = CONFIG_SOURCE_CLI_BARE) | ||
|
|
||
| return config, sources | ||
|
|
||
| def _load_config_file( | ||
| config_path: str, | ||
| config: typing.Dict[str, str], | ||
| sources: typing.Dict[str, ConfigSource], | ||
| source_label: str, | ||
| ) -> None: | ||
| """ Loads config variables and the source from the given config JSON file. """ | ||
|
|
||
| config_path = os.path.abspath(config_path) | ||
| for (key, value) in edq.util.json.load_path(config_path).items(): | ||
| config[key] = value | ||
| sources[key] = ConfigSource(label = source_label, path = config_path) | ||
|
|
||
| def _get_local_config_path( | ||
| config_file_name: str, | ||
| legacy_config_file_name: typing.Union[str, None] = None, | ||
| local_config_root_cutoff: typing.Union[str, None] = None, | ||
| ) -> typing.Union[str, None]: | ||
| """ | ||
| Search for a config file in hierarchical order. | ||
| Begins with the provided config file name, | ||
| optionally checks the legacy config file name if specified, | ||
| then continues up the directory tree looking for the provided config file name. | ||
| Returns the path to the first config file found. | ||
|
|
||
| If no config file is found, returns None. | ||
|
|
||
| The cutoff parameter limits the search depth, preventing detection of config file in higher-level directories during testing. | ||
| """ | ||
|
|
||
| # Provided config file is in current directory. | ||
| if (os.path.isfile(config_file_name)): | ||
| return os.path.abspath(config_file_name) | ||
|
|
||
| # Provided legacy config file is in current directory. | ||
| if (legacy_config_file_name is not None): | ||
| if (os.path.isfile(legacy_config_file_name)): | ||
| return os.path.abspath(legacy_config_file_name) | ||
|
|
||
| # Provided config file is found in an ancestor directory up to the root or cutoff limit. | ||
| parent_dir = os.path.dirname(os.getcwd()) | ||
| return _get_ancestor_config_file_path( | ||
| parent_dir, | ||
| config_file_name = config_file_name, | ||
| local_config_root_cutoff = local_config_root_cutoff, | ||
| ) | ||
|
|
||
| def _get_ancestor_config_file_path( | ||
| current_directory: str, | ||
| config_file_name: str, | ||
| local_config_root_cutoff: typing.Union[str, None] = None, | ||
| ) -> typing.Union[str, None]: | ||
| """ | ||
| Search through the parent directories (until root or a given cutoff directory(inclusive)) for a config file. | ||
| Stops at the first occurrence of the specified config file along the path to root. | ||
| Returns the path if a config file is found. | ||
| Otherwise, returns None. | ||
| """ | ||
|
|
||
| if (local_config_root_cutoff is not None): | ||
| local_config_root_cutoff = os.path.abspath(local_config_root_cutoff) | ||
|
|
||
| current_directory = os.path.abspath(current_directory) | ||
| for _ in range(edq.util.dirent.DEPTH_LIMIT): | ||
| config_file_path = os.path.join(current_directory, config_file_name) | ||
| if (os.path.isfile(config_file_path)): | ||
| return config_file_path | ||
|
|
||
| # Check if current directory is root. | ||
| parent_dir = os.path.dirname(current_directory) | ||
| if (parent_dir == current_directory): | ||
| break | ||
|
|
||
| if (local_config_root_cutoff == current_directory): | ||
| break | ||
|
|
||
| current_directory = parent_dir | ||
|
|
||
| return None |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.