diff --git a/README.md b/README.md index 6a3278e..b6d3d99 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,9 @@ For example, `${oc.env:USER,me}` would resolve to the environment variable USER with a default value "me". Similarly, `${oc.select:path}` will resolve to another configuration value. +Additional resolvers are added to read file contents. +These are the same as type casts: read_text, read_strip, read_bytes. + The select is used to build multiple templates for configurations by providing base configurations. An argument `--select key=template` is a shortcut for diff --git a/alphaconf/__init__.py b/alphaconf/__init__.py index 38b6690..0e3cc3b 100644 --- a/alphaconf/__init__.py +++ b/alphaconf/__init__.py @@ -1,6 +1,7 @@ import contextlib import contextvars import logging +import re import sys from contextvars import ContextVar from typing import Any, Callable, Dict, Union, cast @@ -9,7 +10,7 @@ from .application import Application, _log from .arg_parser import ArgumentError, ExitApplication -from .arg_type import convert_to_type +from .type_resolvers import convert_to_type __doc__ = """AlphaConf @@ -24,10 +25,15 @@ """ +"""A list of functions which given a key indicate whether it's a secret""" +SECRET_MASKS = [ + # mask if contains a kind of secret and it's not in a file + re.compile(r'.*(key|password|secret)s?(?!_file)(_|$)').match, +] + ####################################### # APPLICATION CONTEXT - """The application context""" application: ContextVar[Application] = ContextVar('application') """The current configuration""" diff --git a/alphaconf/application.py b/alphaconf/application.py index 8a3b6a0..e0124e1 100644 --- a/alphaconf/application.py +++ b/alphaconf/application.py @@ -1,6 +1,5 @@ import logging import os -import re import sys import uuid from typing import Any, Dict, Iterable, List, Optional, Tuple, Union, cast @@ -16,12 +15,6 @@ _log = logging.getLogger(__name__) -"""A list of functions which given a key indicate whether it's a secret""" -SECRET_MASKS = [ - # mask if contains a kind of secret and it's not in a file - re.compile(r'.*(password|secret|key)(?!_file)(_|$)').match, -] - class Application: """An application description @@ -298,6 +291,8 @@ def yaml_configuration(self, mask_base: bool = True, mask_secrets: bool = True) @staticmethod def __mask_secrets(configuration): + from . import SECRET_MASKS + for key in list(configuration): if isinstance(key, str) and any(mask(key) for mask in SECRET_MASKS): configuration[key] = '*****' diff --git a/alphaconf/arg_type.py b/alphaconf/arg_type.py deleted file mode 100644 index 19103b3..0000000 --- a/alphaconf/arg_type.py +++ /dev/null @@ -1,22 +0,0 @@ -import datetime -from pathlib import Path - -TYPE_CONVERTER = { - datetime.datetime: datetime.datetime.fromisoformat, - datetime.date: lambda s: datetime.datetime.strptime(s, '%Y-%m-%d').date(), - Path: lambda s: Path(s).expanduser(), - 'read_text': lambda s: Path(s).expanduser().read_text(), - 'read_strip': lambda s: Path(s).expanduser().read_text().strip(), - 'read_bytes': lambda s: Path(s).expanduser().read_bytes(), -} - - -def convert_to_type(value, type): - """Converts a value to the given type. - - :param value: Any value - :param type: A class or a callable used to convert the value - :return: Result of the callable - """ - type = TYPE_CONVERTER.get(type, type) - return type(value) diff --git a/alphaconf/type_resolvers.py b/alphaconf/type_resolvers.py new file mode 100644 index 0000000..71fe4a2 --- /dev/null +++ b/alphaconf/type_resolvers.py @@ -0,0 +1,42 @@ +import datetime +from pathlib import Path + +from omegaconf import OmegaConf + +__doc__ = """Resolves types when reading values from the configuration. + +You can add values to TYPE_CONVERTER which is used in `alphaconf.get()`. +This way, you can load values from an external source. +By the way, you could register new resolvers in OmegaConf. +""" + + +def read_text(value): + return Path(value).expanduser().read_text() + + +TYPE_CONVERTER = { + datetime.datetime: datetime.datetime.fromisoformat, + datetime.date: lambda s: datetime.datetime.strptime(s, '%Y-%m-%d').date(), + datetime.time: datetime.time.fromisoformat, + Path: lambda s: Path(s).expanduser(), + 'read_text': read_text, + 'read_strip': lambda s: read_text(s).strip(), + 'read_bytes': lambda s: Path(s).expanduser().read_bytes(), +} + +# register resolved from strings +for _name, _function in TYPE_CONVERTER.items(): + if isinstance(_name, str): + OmegaConf.register_new_resolver(_name, _function) # type: ignore + + +def convert_to_type(value, type): + """Converts a value to the given type. + + :param value: Any value + :param type: A class or a callable used to convert the value + :return: Result of the callable + """ + type = TYPE_CONVERTER.get(type, type) + return type(value) diff --git a/example-simple.py b/example-simple.py index 6dfa154..308cb64 100755 --- a/example-simple.py +++ b/example-simple.py @@ -12,8 +12,14 @@ url: http://default user: ${oc.env:USER} home: "~" +show: false +exception: false """, - {"server": "Arguments for the demo"}, + { + "server": "Arguments for the demo", + "show": "The name of the selection to show", + "exception": "If set, raise an exception", + }, ) @@ -27,6 +33,10 @@ def main(): # shortcut version to get a configuration value print('server.user:', alphaconf.get('server.user')) print('server.home', alphaconf.get('server.home', Path)) + # show configuration + value = alphaconf.get('show') + if value and (value := alphaconf.get(value)): + print(value) # log an exception if we have it in the configuration if alphaconf.get('exception'): try: