Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
67f376f
Tests passing need to work on other checks.
BatuhanSA Aug 15, 2025
0ff308a
Worked on linting and typing.
BatuhanSA Aug 16, 2025
b6148cf
Changed the Mapping import.
BatuhanSA Aug 16, 2025
d4556b9
Added an option to specify the config file name.
BatuhanSA Aug 16, 2025
cca8ea6
Reordered some checks.
BatuhanSA Aug 16, 2025
588f452
Generalizes the use case to edq.
BatuhanSA Aug 16, 2025
f34c44f
Revised unittest.
BatuhanSA Aug 17, 2025
bf98083
Revised config and config tests.
BatuhanSA Aug 17, 2025
03a7d64
resolved conflicts.
BatuhanSA Aug 17, 2025
e550e8b
pointed to defualt config file name in tests.
BatuhanSA Aug 17, 2025
aa14f7f
Revised the first pass.
BatuhanSA Aug 18, 2025
3b22855
Passed in the msg for unittesting.
BatuhanSA Aug 18, 2025
9f0be06
Reviewd the PR, need to work on testing comments.
BatuhanSA Aug 18, 2025
a9efa0b
Reviewed testing.
BatuhanSA Aug 20, 2025
769527c
Resolved conflicts.
BatuhanSA Aug 20, 2025
ee3d21b
Revised questions in mind and added missing config directory.
BatuhanSA Aug 20, 2025
c06b6fc
Testing Windows permission error on directories when loading JSON.
BatuhanSA Aug 20, 2025
0f61041
Changed the way equals returns for ConfigSource object.
BatuhanSA Aug 21, 2025
4cc295b
Tested all combinations of 4 config labels.
BatuhanSA Aug 21, 2025
9d25595
Revised the structure of tests, need to go over them.
BatuhanSA Aug 21, 2025
bcaa609
Revised testcases and added file tree creation when testing.
BatuhanSA Aug 21, 2025
05b7966
Removing files that need to be in the next PR.
BatuhanSA Aug 21, 2025
24f240e
Reviewed previous PR comments.
BatuhanSA Aug 21, 2025
cdeee74
Revised the second pass, need to work on testing and README.
BatuhanSA Aug 22, 2025
67634ee
Changed the order of the test structure. Made a single temp_dir for a…
BatuhanSA Aug 22, 2025
f2ef098
Added config on README
BatuhanSA Aug 22, 2025
707a351
Got rid of points for Global config.
BatuhanSA Aug 22, 2025
683140b
Polished of the README.
BatuhanSA Aug 22, 2025
97d16d9
Fixed the url for platformdir.
BatuhanSA Aug 22, 2025
cf20ac6
Fixed the url for platformdir.
BatuhanSA Aug 22, 2025
5edb54a
Made the local config description better.
BatuhanSA Aug 22, 2025
aed3ffb
Made the local config description better.
BatuhanSA Aug 22, 2025
7c905b5
Revised for 3rd pass
BatuhanSA Aug 24, 2025
078eaee
Corrected inconsistencies.
BatuhanSA Aug 24, 2025
c6c7a0a
Revised 4th pass.
BatuhanSA Aug 28, 2025
53d8bf4
Revised the README, converted the table format.
BatuhanSA Aug 28, 2025
22797db
Got rid of a inconsistency.
BatuhanSA Aug 29, 2025
c753729
2nd revision of README
BatuhanSA Aug 29, 2025
629f110
Revised it again.
BatuhanSA Aug 29, 2025
f223a92
Added error description.
BatuhanSA Aug 30, 2025
418b6cb
Added examples to the README.
BatuhanSA Aug 31, 2025
869d963
Made clarifications.
BatuhanSA Aug 31, 2025
033d119
Revised the overriding on skip keys.
BatuhanSA Aug 31, 2025
51c139b
Moved config from util to core.
BatuhanSA Sep 2, 2025
b15cbd0
Revised the README with 5th pass.
BatuhanSA Sep 2, 2025
568830f
Added 'only' to local and global config description.
BatuhanSA Sep 2, 2025
1aed3d7
Adding CLI for config list.
BatuhanSA Sep 3, 2025
f9155d6
Merge branch 'main' into cli
BatuhanSA Sep 5, 2025
7e73dd7
Renamed the functions for list cli.
BatuhanSA Sep 6, 2025
6273d49
Adding cli tests.
BatuhanSA Sep 6, 2025
2ecf78b
Fixed import order.
BatuhanSA Sep 6, 2025
1428e59
Added --config flag functionality to get_tierd_config().
BatuhanSA Sep 6, 2025
e57b662
Added more CLI tests for config list.
BatuhanSA Sep 6, 2025
39dabdb
Revised for PR.
BatuhanSA Sep 7, 2025
815f904
Revised it one last time before the PR.
BatuhanSA Sep 7, 2025
e07f887
Corrected an inaccruacy on a comment.
BatuhanSA Sep 7, 2025
77476bf
Made CLI flags descriptions better.
BatuhanSA Sep 8, 2025
8257ca8
Reviewed 1st pass.
BatuhanSA Sep 10, 2025
d4495bb
Reviwed the 1st pass one last time.
BatuhanSA Sep 10, 2025
cdf293c
Changed the name of the post parsing function for config.
BatuhanSA Sep 10, 2025
61d69de
Revised 2nd pass.
BatuhanSA Sep 10, 2025
04636f7
Added more test cases.
BatuhanSA Sep 10, 2025
bf08974
Moved config list test under list directory.
BatuhanSA Sep 10, 2025
8af6f7e
Moved some test from cli test to config test side.
BatuhanSA Sep 10, 2025
f8986f8
Made password test case consisten on cli side.
BatuhanSA Sep 10, 2025
648b4be
Added ignore-config flag.
BatuhanSA Sep 24, 2025
4cdc050
Fixed issues with helpand error messages.
BatuhanSA Sep 26, 2025
fdb1aba
Embedded global config path in to cli arguments.
BatuhanSA Sep 26, 2025
9303bf1
Added tests and updated the help message for ignore config.
BatuhanSA Sep 27, 2025
3052836
Added non-existing key test for ignore config.
BatuhanSA Sep 27, 2025
3901239
Got rid of extra space.
BatuhanSA Sep 27, 2025
234d4a6
Increaed the max module lines for format.
BatuhanSA Sep 27, 2025
a00427a
Done with 3rd pass.
BatuhanSA Sep 28, 2025
2241faa
Simplified str conversion, chnaged the numeric value test.
BatuhanSA Sep 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ disable=bad-inline-option,
too-many-arguments,
too-many-branches,
too-many-instance-attributes,
too-many-lines,
too-many-locals,
too-many-positional-arguments,
too-many-public-methods,
Expand Down
Empty file added edq/cli/config/__init__.py
Empty file.
65 changes: 65 additions & 0 deletions edq/cli/config/list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""
List current configuration options.
"""

import argparse
import sys

import edq.core.argparser

CONFIG_FIELD_SEPARATOR: str = "\t"

def run_cli(args: argparse.Namespace) -> int:
""" Run the CLI. """

rows = []

for (key, value) in args._config.items():
row = [key, str(value)]
if (args.show_origin):
config_source_obj = args._config_sources.get(key)

origin = config_source_obj.path
if (origin is None):
origin = config_source_obj.label

row.append(origin)

rows.append(CONFIG_FIELD_SEPARATOR.join(row))

rows.sort()

if (not args.skip_header):
header = ["Key", "Value"]
if (args.show_origin):
header.append("Origin")

rows.insert(0, (CONFIG_FIELD_SEPARATOR.join(header)))

print("\n".join(rows))
return 0

def main() -> int:
""" Get a parser, parse the args, and call run. """

return run_cli(_get_parser().parse_args())

def _get_parser() -> edq.core.argparser.Parser:
""" Get a parser and add addition flags. """

parser = edq.core.argparser.get_default_parser(__doc__.strip())

parser.add_argument("--show-origin", dest = 'show_origin',
action = 'store_true',
help = "Display where each configuration's value was obtained from.",
)

parser.add_argument("--skip-header", dest = 'skip_header',
action = 'store_true',
help = 'Skip headers when displaying configs.',
)

return parser

if (__name__ == '__main__'):
sys.exit(main())
1 change: 1 addition & 0 deletions edq/cli/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def run_cli(args: argparse.Namespace) -> int:

def main() -> int:
""" Get a parser, parse the args, and call run. """

return run_cli(_get_parser().parse_args())

def _get_parser() -> edq.core.argparser.Parser:
Expand Down
2 changes: 2 additions & 0 deletions edq/core/argparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import argparse
import typing

import edq.core.config
import edq.core.log

@typing.runtime_checkable
Expand Down Expand Up @@ -108,5 +109,6 @@ def get_default_parser(description: str) -> Parser:
parser = Parser(description = description)

parser.register_callbacks('log', edq.core.log.set_cli_args, edq.core.log.init_from_args)
parser.register_callbacks('config', edq.core.config.set_cli_args, edq.core.config.load_config_into_args)

return parser
108 changes: 87 additions & 21 deletions edq/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@

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_SOURCE_CLI_FILE: str = "<cli config file>"
CONFIG_SOURCE_CLI: str = "<cli argument>"

CONFIG_PATHS_KEY: str = 'config_paths'
CONFIGS_KEY: str = 'configs'
GLOBAL_CONFIG_KEY: str = 'global_config_path'
IGNORE_CONFIGS_KEY: str = 'ignore_configs'
DEFAULT_CONFIG_FILENAME: str = "edq-config.json"
DEFAULT_GLOBAL_CONFIG_PATH: str = platformdirs.user_config_dir(DEFAULT_CONFIG_FILENAME)

class ConfigSource:
""" A class for storing config source information. """
Expand All @@ -23,7 +27,7 @@ def __init__(self, label: str, path: typing.Union[str, None] = None) -> None:
""" The label identifying the config (see CONFIG_SOURCE_* constants). """

self.path = path
""" The path of where the config was soruced from. """
""" The path of where the config was sourced from. """

def __eq__(self, other: object) -> bool:
if (not isinstance(other, ConfigSource)):
Expand All @@ -37,8 +41,6 @@ def __str__(self) -> str:
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]]:
Expand All @@ -47,12 +49,6 @@ def get_tiered_config(
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 = {}

Expand All @@ -63,6 +59,8 @@ def get_tiered_config(
if (isinstance(cli_arguments, argparse.Namespace)):
cli_arguments = vars(cli_arguments)

global_config_path = cli_arguments.get(GLOBAL_CONFIG_KEY, platformdirs.user_config_dir(config_file_name))

# Check the global user config file.
if (os.path.isfile(global_config_path)):
_load_config_file(global_config_path, config, sources, CONFIG_SOURCE_GLOBAL)
Expand All @@ -79,20 +77,32 @@ def get_tiered_config(

# 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)
for path in config_paths:
_load_config_file(path, config, sources, CONFIG_SOURCE_CLI_FILE)

# Finally, any command-line options.
for (key, value) in cli_arguments.items():
if (key in skip_keys):
continue
# Check the command-line config options.
cli_configs = cli_arguments.get(CONFIGS_KEY, [])
for cli_config in cli_configs:
if ("=" not in cli_config):
raise ValueError(
f"Invalid configuration option '{cli_config}'."
+ " Configuration options must be provided in the format `<key>=<value>` when passed via the CLI."
)

if ((value is None) or (value == '')):
continue
(key, value) = cli_config.split("=", maxsplit = 1)

key = key.strip()
if (key == ""):
raise ValueError(f"Found an empty configuration option key associated with the value '{value}'.")

config[key] = value
sources[key] = ConfigSource(label = CONFIG_SOURCE_CLI_BARE)
sources[key] = ConfigSource(label = CONFIG_SOURCE_CLI)

# Finally, ignore any configs that is specified from CLI command.
cli_ignore_configs = cli_arguments.get(IGNORE_CONFIGS_KEY, [])
for ignore_config in cli_ignore_configs:
config.pop(ignore_config, None)
sources.pop(ignore_config, None)

return config, sources

Expand All @@ -106,6 +116,10 @@ def _load_config_file(

config_path = os.path.abspath(config_path)
for (key, value) in edq.util.json.load_path(config_path).items():
key = key.strip()
if (key == ""):
raise ValueError(f"Found an empty configuration option key associated with the value '{value}'.")

config[key] = value
sources[key] = ConfigSource(label = source_label, path = config_path)

Expand Down Expand Up @@ -175,3 +189,55 @@ def _get_ancestor_config_file_path(
current_directory = parent_dir

return None

def set_cli_args(parser: argparse.ArgumentParser, extra_state: typing.Dict[str, typing.Any]) -> None:
"""
Set common CLI arguments for configuration.
"""

parser.add_argument('--config-global', dest = GLOBAL_CONFIG_KEY,
action = 'store', type = str, default = DEFAULT_GLOBAL_CONFIG_PATH,
help = 'Set the default global config file path (default: %(default)s).',
)

parser.add_argument('--config-file', dest = CONFIG_PATHS_KEY,
action = 'append', type = str, default = [],
help = ('Load config options from a JSON file.'
+ ' This flag can be specified multiple times.'
+ ' Files are applied in the order provided and later files override earlier ones.'
+ ' Will override options form both global and local config files.')
)

parser.add_argument('--config', dest = CONFIGS_KEY,
action = 'append', type = str, default = [],
help = ('Set a configuration option from the command-line.'
+ ' Specify options as <key>=<value> pairs.'
+ ' This flag can be specified multiple times.'
+ ' The options are applied in the order provided and later options override earlier ones.'
+ ' Will override options form all config files.')
)

parser.add_argument('--ignore-config-option', dest = IGNORE_CONFIGS_KEY,
action = 'append', type = str, default = [],
help = ('Ignore any config option with the specified key.'
+ ' The system-provided default value will be used for that option if one exists.'
+ ' This flag can be specified multiple times.'
+ ' Ignored options are processed last.')
)

def load_config_into_args(
parser: argparse.ArgumentParser,
args: argparse.Namespace,
extra_state: typing.Dict[str, typing.Any],
) -> None:
"""
Take in args from a parser that was passed to set_cli_args(),
and get the tired configuration with the appropriate parameters, and attache it to args.
"""

(config_dict, sources_dict) = get_tiered_config(
cli_arguments = args,
)

setattr(args, "_config", config_dict)
setattr(args, "_config_sources", sources_dict)
Loading