edq.core.argparser
A place to handle common CLI arguments. "parsers" in this file are always assumed to be argparse parsers.
The general idea is that callers can register callbacks to be called before and after parsing CLI arguments. Pre-callbacks are generally intended to add arguments to the parser, while post-callbacks are generally intended to act on the results of parsing.
1""" 2A place to handle common CLI arguments. 3"parsers" in this file are always assumed to be argparse parsers. 4 5The general idea is that callers can register callbacks to be called before and after parsing CLI arguments. 6Pre-callbacks are generally intended to add arguments to the parser, 7while post-callbacks are generally intended to act on the results of parsing. 8""" 9 10import argparse 11import typing 12 13import edq.core.log 14 15@typing.runtime_checkable 16class PreParseFunction(typing.Protocol): 17 """ 18 A function that can be called before parsing arguments. 19 """ 20 21 def __call__(self, parser: argparse.ArgumentParser, extra_state: typing.Dict[str, typing.Any]) -> None: 22 """ 23 Prepare a parser for parsing. 24 This is generally used for adding your module's arguments to the parser, 25 for example a logging module may add arguments to set a logging level. 26 27 The extra state is shared between all pre-parse functions 28 and will be placed in the final parsed output under `_pre_extra_state_`. 29 """ 30 31@typing.runtime_checkable 32class PostParseFunction(typing.Protocol): 33 """ 34 A function that can be called after parsing arguments. 35 """ 36 37 def __call__(self, 38 parser: argparse.ArgumentParser, 39 args: argparse.Namespace, 40 extra_state: typing.Dict[str, typing.Any]) -> None: 41 """ 42 Take actions after arguments are parsed. 43 This is generally used for initializing your module with options, 44 for example a logging module may set a logging level. 45 46 The extra state is shared between all post-parse functions 47 and will be placed in the final parsed output under `_post_extra_state_`. 48 """ 49 50class Parser(argparse.ArgumentParser): 51 """ 52 Extend an argparse parser to call the pre and post functions. 53 """ 54 55 def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: 56 super().__init__(*args, **kwargs) 57 58 self._pre_parse_callbacks: typing.Dict[str, PreParseFunction] = {} 59 self._post_parse_callbacks: typing.Dict[str, PostParseFunction] = {} 60 61 def register_callbacks(self, 62 key: str, 63 pre_parse_callback: typing.Union[PreParseFunction, None] = None, 64 post_parse_callback: typing.Union[PostParseFunction, None] = None, 65 ) -> None: 66 """ 67 Register callback functions to run before/after argument parsing. 68 Any existing callbacks under the specified key will be replaced. 69 """ 70 71 if (pre_parse_callback is not None): 72 self._pre_parse_callbacks[key] = pre_parse_callback 73 74 if (post_parse_callback is not None): 75 self._post_parse_callbacks[key] = post_parse_callback 76 77 def parse_args(self, # type: ignore[override] 78 *args: typing.Any, 79 skip_keys: typing.Union[typing.List[str], None] = None, 80 **kwargs: typing.Any) -> argparse.Namespace: 81 if (skip_keys is None): 82 skip_keys = [] 83 84 # Call pre-parse callbacks. 85 pre_extra_state: typing.Dict[str, typing.Any] = {} 86 for (key, pre_parse_callback) in self._pre_parse_callbacks.items(): 87 if (key not in skip_keys): 88 pre_parse_callback(self, pre_extra_state) 89 90 # Parse the args. 91 parsed_args = super().parse_args(*args, **kwargs) 92 93 # Call post-parse callbacks. 94 post_extra_state: typing.Dict[str, typing.Any] = {} 95 for (key, post_parse_callback) in self._post_parse_callbacks.items(): 96 if (key not in skip_keys): 97 post_parse_callback(self, parsed_args, post_extra_state) 98 99 # Attach the additional state to the args. 100 setattr(parsed_args, '_pre_extra_state_', pre_extra_state) 101 setattr(parsed_args, '_post_extra_state_', post_extra_state) 102 103 return parsed_args # type: ignore[no-any-return] 104 105def get_default_parser(description: str) -> Parser: 106 """ Get a parser with the default callbacks already attached. """ 107 108 parser = Parser(description = description) 109 110 parser.register_callbacks('log', edq.core.log.set_cli_args, edq.core.log.init_from_args) 111 112 return parser
@typing.runtime_checkable
class
PreParseFunction16@typing.runtime_checkable 17class PreParseFunction(typing.Protocol): 18 """ 19 A function that can be called before parsing arguments. 20 """ 21 22 def __call__(self, parser: argparse.ArgumentParser, extra_state: typing.Dict[str, typing.Any]) -> None: 23 """ 24 Prepare a parser for parsing. 25 This is generally used for adding your module's arguments to the parser, 26 for example a logging module may add arguments to set a logging level. 27 28 The extra state is shared between all pre-parse functions 29 and will be placed in the final parsed output under `_pre_extra_state_`. 30 """
A function that can be called before parsing arguments.
PreParseFunction(*args, **kwargs)
1953def _no_init_or_replace_init(self, *args, **kwargs): 1954 cls = type(self) 1955 1956 if cls._is_protocol: 1957 raise TypeError('Protocols cannot be instantiated') 1958 1959 # Already using a custom `__init__`. No need to calculate correct 1960 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1961 if cls.__init__ is not _no_init_or_replace_init: 1962 return 1963 1964 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1965 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1966 # searches for a proper new `__init__` in the MRO. The new `__init__` 1967 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1968 # instantiation of the protocol subclass will thus use the new 1969 # `__init__` and no longer call `_no_init_or_replace_init`. 1970 for base in cls.__mro__: 1971 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1972 if init is not _no_init_or_replace_init: 1973 cls.__init__ = init 1974 break 1975 else: 1976 # should not happen 1977 cls.__init__ = object.__init__ 1978 1979 cls.__init__(self, *args, **kwargs)
@typing.runtime_checkable
class
PostParseFunction32@typing.runtime_checkable 33class PostParseFunction(typing.Protocol): 34 """ 35 A function that can be called after parsing arguments. 36 """ 37 38 def __call__(self, 39 parser: argparse.ArgumentParser, 40 args: argparse.Namespace, 41 extra_state: typing.Dict[str, typing.Any]) -> None: 42 """ 43 Take actions after arguments are parsed. 44 This is generally used for initializing your module with options, 45 for example a logging module may set a logging level. 46 47 The extra state is shared between all post-parse functions 48 and will be placed in the final parsed output under `_post_extra_state_`. 49 """
A function that can be called after parsing arguments.
PostParseFunction(*args, **kwargs)
1953def _no_init_or_replace_init(self, *args, **kwargs): 1954 cls = type(self) 1955 1956 if cls._is_protocol: 1957 raise TypeError('Protocols cannot be instantiated') 1958 1959 # Already using a custom `__init__`. No need to calculate correct 1960 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1961 if cls.__init__ is not _no_init_or_replace_init: 1962 return 1963 1964 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1965 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1966 # searches for a proper new `__init__` in the MRO. The new `__init__` 1967 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1968 # instantiation of the protocol subclass will thus use the new 1969 # `__init__` and no longer call `_no_init_or_replace_init`. 1970 for base in cls.__mro__: 1971 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1972 if init is not _no_init_or_replace_init: 1973 cls.__init__ = init 1974 break 1975 else: 1976 # should not happen 1977 cls.__init__ = object.__init__ 1978 1979 cls.__init__(self, *args, **kwargs)
class
Parser(argparse.ArgumentParser):
51class Parser(argparse.ArgumentParser): 52 """ 53 Extend an argparse parser to call the pre and post functions. 54 """ 55 56 def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: 57 super().__init__(*args, **kwargs) 58 59 self._pre_parse_callbacks: typing.Dict[str, PreParseFunction] = {} 60 self._post_parse_callbacks: typing.Dict[str, PostParseFunction] = {} 61 62 def register_callbacks(self, 63 key: str, 64 pre_parse_callback: typing.Union[PreParseFunction, None] = None, 65 post_parse_callback: typing.Union[PostParseFunction, None] = None, 66 ) -> None: 67 """ 68 Register callback functions to run before/after argument parsing. 69 Any existing callbacks under the specified key will be replaced. 70 """ 71 72 if (pre_parse_callback is not None): 73 self._pre_parse_callbacks[key] = pre_parse_callback 74 75 if (post_parse_callback is not None): 76 self._post_parse_callbacks[key] = post_parse_callback 77 78 def parse_args(self, # type: ignore[override] 79 *args: typing.Any, 80 skip_keys: typing.Union[typing.List[str], None] = None, 81 **kwargs: typing.Any) -> argparse.Namespace: 82 if (skip_keys is None): 83 skip_keys = [] 84 85 # Call pre-parse callbacks. 86 pre_extra_state: typing.Dict[str, typing.Any] = {} 87 for (key, pre_parse_callback) in self._pre_parse_callbacks.items(): 88 if (key not in skip_keys): 89 pre_parse_callback(self, pre_extra_state) 90 91 # Parse the args. 92 parsed_args = super().parse_args(*args, **kwargs) 93 94 # Call post-parse callbacks. 95 post_extra_state: typing.Dict[str, typing.Any] = {} 96 for (key, post_parse_callback) in self._post_parse_callbacks.items(): 97 if (key not in skip_keys): 98 post_parse_callback(self, parsed_args, post_extra_state) 99 100 # Attach the additional state to the args. 101 setattr(parsed_args, '_pre_extra_state_', pre_extra_state) 102 setattr(parsed_args, '_post_extra_state_', post_extra_state) 103 104 return parsed_args # type: ignore[no-any-return]
Extend an argparse parser to call the pre and post functions.
def
register_callbacks( self, key: str, pre_parse_callback: Optional[PreParseFunction] = None, post_parse_callback: Optional[PostParseFunction] = None) -> None:
62 def register_callbacks(self, 63 key: str, 64 pre_parse_callback: typing.Union[PreParseFunction, None] = None, 65 post_parse_callback: typing.Union[PostParseFunction, None] = None, 66 ) -> None: 67 """ 68 Register callback functions to run before/after argument parsing. 69 Any existing callbacks under the specified key will be replaced. 70 """ 71 72 if (pre_parse_callback is not None): 73 self._pre_parse_callbacks[key] = pre_parse_callback 74 75 if (post_parse_callback is not None): 76 self._post_parse_callbacks[key] = post_parse_callback
Register callback functions to run before/after argument parsing. Any existing callbacks under the specified key will be replaced.
def
parse_args( self, *args: Any, skip_keys: Optional[List[str]] = None, **kwargs: Any) -> argparse.Namespace:
78 def parse_args(self, # type: ignore[override] 79 *args: typing.Any, 80 skip_keys: typing.Union[typing.List[str], None] = None, 81 **kwargs: typing.Any) -> argparse.Namespace: 82 if (skip_keys is None): 83 skip_keys = [] 84 85 # Call pre-parse callbacks. 86 pre_extra_state: typing.Dict[str, typing.Any] = {} 87 for (key, pre_parse_callback) in self._pre_parse_callbacks.items(): 88 if (key not in skip_keys): 89 pre_parse_callback(self, pre_extra_state) 90 91 # Parse the args. 92 parsed_args = super().parse_args(*args, **kwargs) 93 94 # Call post-parse callbacks. 95 post_extra_state: typing.Dict[str, typing.Any] = {} 96 for (key, post_parse_callback) in self._post_parse_callbacks.items(): 97 if (key not in skip_keys): 98 post_parse_callback(self, parsed_args, post_extra_state) 99 100 # Attach the additional state to the args. 101 setattr(parsed_args, '_pre_extra_state_', pre_extra_state) 102 setattr(parsed_args, '_post_extra_state_', post_extra_state) 103 104 return parsed_args # type: ignore[no-any-return]
106def get_default_parser(description: str) -> Parser: 107 """ Get a parser with the default callbacks already attached. """ 108 109 parser = Parser(description = description) 110 111 parser.register_callbacks('log', edq.core.log.set_cli_args, edq.core.log.init_from_args) 112 113 return parser
Get a parser with the default callbacks already attached.