11
11
"""
12
12
.. py:module::config
13
13
:synopsis: Convenience class for configuration file option
14
+
15
+ The functions :func:`configuration_callback` and :func:`configuration_option` were directly taken from the repository
16
+ https://github.com/phha/click_config_file/blob/7b93a20b4c79458987fac116418859f30a16d82a/click_config_file.py with a
17
+ minor modification to ``configuration_callback`` to add a check for unknown parameters in the configuration file and
18
+ the default provider is changed to :func:`yaml_config_file_provider`.
14
19
"""
15
- import click_config_file
20
+ from __future__ import annotations
21
+
22
+ import functools
23
+ import os
24
+ import typing as t
25
+
26
+ import click
16
27
import yaml
17
28
18
29
from .overridable import OverridableOption
@@ -25,9 +36,115 @@ def yaml_config_file_provider(handle, cmd_name): # pylint: disable=unused-argum
25
36
return yaml .safe_load (handle )
26
37
27
38
28
- class ConfigFileOption (OverridableOption ):
39
+ def configuration_callback (
40
+ cmd_name : str | None ,
41
+ option_name : str ,
42
+ config_file_name : str ,
43
+ saved_callback : t .Callable [..., t .Any ] | None ,
44
+ provider : t .Callable [..., t .Any ],
45
+ implicit : bool ,
46
+ ctx : click .Context ,
47
+ param : click .Parameter ,
48
+ value : t .Any ,
49
+ ):
50
+ """Callback for reading the config file.
51
+
52
+ Also takes care of calling user specified custom callback afterwards.
53
+
54
+ :param cmd_name: The command name. This is used to determine the configuration directory.
55
+ :param option_name: The name of the option. This is used for error messages.
56
+ :param config_file_name: The name of the configuration file.
57
+ :param saved_callback: User-specified callback to be called later.
58
+ :param provider: A callable that parses the configuration file and returns a dictionary of the configuration
59
+ parameters. Will be called as ``provider(file_path, cmd_name)``. Default: ``yaml_config_file_provider``.
60
+ :param implicit: Whether a implicit value should be applied if no configuration option value was provided.
61
+ :param ctx: ``click`` context.
62
+ :param param: ``click`` parameters.
63
+ :param value: Value passed to the parameter.
64
+ """
65
+ ctx .default_map = ctx .default_map or {}
66
+ cmd_name = cmd_name or ctx .info_name
67
+
68
+ if implicit :
69
+ default_value = os .path .join (click .get_app_dir (cmd_name ), config_file_name ) # type: ignore[arg-type]
70
+ param .default = default_value
71
+ value = value or default_value
72
+
73
+ if value :
74
+ try :
75
+ config = provider (value , cmd_name )
76
+ except Exception as exception :
77
+ raise click .BadOptionUsage (option_name , f'Error reading configuration file: { exception } ' , ctx )
78
+
79
+ valid_params = [param .name for param in ctx .command .params if param .name != option_name ]
80
+ specified_params = list (config .keys ())
81
+ unknown_params = set (specified_params ).difference (set (valid_params ))
82
+
83
+ if unknown_params :
84
+ raise click .BadParameter (
85
+ f'Invalid configuration file, the following keys are not supported: { unknown_params } ' , ctx , param
86
+ )
87
+
88
+ ctx .default_map .update (config )
89
+
90
+ return saved_callback (ctx , param , value ) if saved_callback else value
91
+
92
+
93
+ def configuration_option (* param_decls , ** attrs ):
94
+ """Adds configuration file support to a click application.
95
+
96
+ This will create an option of type ``click.File`` expecting the path to a configuration file. When specified, it
97
+ overwrites the default values for all other click arguments or options with the corresponding value from the
98
+ configuration file. The default name of the option is ``--config``. By default, the configuration will be read from
99
+ a configuration directory as determined by ``click.get_app_dir``. This decorator accepts the same arguments as
100
+ ``click.option`` and ``click.Path``. In addition, the following keyword arguments are available:
101
+
102
+ :param cmd_name: str
103
+ The command name. This is used to determine the configuration directory. Default: ``ctx.info_name``.
104
+ :param config_file_name: str
105
+ The name of the configuration file. Default: ``config``.
106
+ :param implicit: bool
107
+ If ``True`` then implicitly create a value for the configuration option using the above parameters. If a
108
+ configuration file exists in this path it will be applied even if no configuration option was suppplied as a
109
+ CLI argument or environment variable. If ``False`` only apply a configuration file that has been explicitely
110
+ specified. Default: ``False``.
111
+ :param provider: callable
112
+ A callable that parses the configuration file and returns a dictionary of the configuration parameters. Will be
113
+ called as ``provider(file_path, cmd_name)``. Default: ``yaml_config_file_provider``.
29
114
"""
30
- Wrapper around click_config_file.configuration_option that increases reusability.
115
+ param_decls = param_decls or ('--config' ,)
116
+ option_name = param_decls [0 ]
117
+
118
+ def decorator (func ):
119
+ attrs .setdefault ('is_eager' , True )
120
+ attrs .setdefault ('help' , 'Read configuration from FILE.' )
121
+ attrs .setdefault ('expose_value' , False )
122
+ implicit = attrs .pop ('implicit' , True )
123
+ cmd_name = attrs .pop ('cmd_name' , None )
124
+ config_file_name = attrs .pop ('config_file_name' , 'config' )
125
+ provider = attrs .pop ('provider' , yaml_config_file_provider )
126
+ path_default_params = {
127
+ 'exists' : False ,
128
+ 'file_okay' : True ,
129
+ 'dir_okay' : False ,
130
+ 'writable' : False ,
131
+ 'readable' : True ,
132
+ 'resolve_path' : False
133
+ }
134
+ path_params = {k : attrs .pop (k , v ) for k , v in path_default_params .items ()}
135
+ attrs ['type' ] = attrs .get ('type' , click .Path (** path_params ))
136
+ saved_callback = attrs .pop ('callback' , None )
137
+ partial_callback = functools .partial (
138
+ configuration_callback , cmd_name , option_name , config_file_name , saved_callback , provider , implicit
139
+ )
140
+ attrs ['callback' ] = partial_callback
141
+ return click .option (* param_decls , ** attrs )(func )
142
+
143
+ return decorator
144
+
145
+
146
+ class ConfigFileOption (OverridableOption ):
147
+ """Reusable option that reads a configuration file containing values for other command parameters.
31
148
32
149
Example::
33
150
@@ -49,8 +166,7 @@ def computer_setup(computer_name):
49
166
"""
50
167
51
168
def __init__ (self , * args , ** kwargs ):
52
- """
53
- Store the default args and kwargs.
169
+ """Store the default args and kwargs.
54
170
55
171
:param args: default arguments to be used for the option
56
172
:param kwargs: default keyword arguments to be used that can be overridden in the call
@@ -59,8 +175,7 @@ def __init__(self, *args, **kwargs):
59
175
super ().__init__ (* args , ** kwargs )
60
176
61
177
def __call__ (self , ** kwargs ):
62
- """
63
- Override the stored kwargs, (ignoring args as we do not allow option name changes) and return the option.
178
+ """Override the stored kwargs, (ignoring args as we do not allow option name changes) and return the option.
64
179
65
180
:param kwargs: keyword arguments that will override those set in the construction
66
181
:return: click_config_file.configuration_option constructed with args and kwargs defined during construction
@@ -69,4 +184,4 @@ def __call__(self, **kwargs):
69
184
kw_copy = self .kwargs .copy ()
70
185
kw_copy .update (kwargs )
71
186
72
- return click_config_file . configuration_option (* self .args , ** kw_copy )
187
+ return configuration_option (* self .args , ** kw_copy )
0 commit comments