Coverage for src/deepdraw/script/config.py: 86%
88 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-11-30 15:00 +0100
« prev ^ index » next coverage.py v7.3.1, created at 2023-11-30 15:00 +0100
1# SPDX-FileCopyrightText: Copyright © 2023 Idiap Research Institute <contact@idiap.ch>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
5from __future__ import annotations
7import importlib.metadata
8import inspect
9import typing
11import click
13from clapper.click import AliasedGroup, verbosity_option
14from clapper.logging import setup
16logger = setup(__name__.split(".")[0], format="%(levelname)s: %(message)s")
19def _retrieve_entry_points(
20 group: str,
21) -> typing.Iterable[importlib.metadata.EntryPoint]:
22 """Wraps various entry-point retrieval mechanisms.
24 For Python 3.9 and 3.10,
25 :py:func:`importlib.metadata.entry_points()`
26 returns a dictionary keyed by entry-point group names. From Python
27 3.10
28 onwards, one may pass the ``group`` keyword to that function to
29 enable
30 pre-filtering, or use the ``select()`` method on the returned value,
31 which
32 is no longer a dictionary.
34 For anything before Python 3.8, you must use the backported library
35 ``importlib_metadata``.
36 """
37 import sys
39 if sys.version_info[:2] < (3, 10):
40 all_entry_points = importlib.metadata.entry_points()
41 return all_entry_points.get(group, []) # Python 3.9
43 # Python 3.10 and above
44 return importlib.metadata.entry_points().select(group=group)
47@click.group(cls=AliasedGroup)
48def config():
49 """Commands for listing, describing and copying configuration resources."""
50 pass
53@config.command(
54 epilog="""Examples:
56\b
57 1. Lists all configuration resources (type: deepdraw.config) installed:
59 .. code:: sh
61 deepdraw config list
64\b
65 2. Lists all configuration resources and their descriptions (notice this may
66 be slow as it needs to load all modules once):
68 .. code:: sh
70 deepdraw config list -v
71"""
72)
73@verbosity_option(logger=logger)
74def list(verbose) -> None:
75 """Lists configuration files installed."""
76 entry_points = _retrieve_entry_points("deepdraw.config")
77 entry_point_dict = {k.name: k for k in entry_points}
79 # all modules with configuration resources
80 modules = {k.module.rsplit(".", 1)[0] for k in entry_point_dict.values()}
81 keep_modules: set[str] = set()
82 for k in sorted(modules):
83 if k not in keep_modules and not any(
84 k.startswith(element) for element in keep_modules
85 ):
86 keep_modules.add(k)
87 modules = keep_modules
89 # sort data entries by originating module
90 entry_points_by_module: dict[str, dict[str, typing.Any]] = {}
91 for k in modules:
92 entry_points_by_module[k] = {}
93 for name, ep in entry_point_dict.items():
94 if ep.module.startswith(k):
95 entry_points_by_module[k][name] = ep
97 for config_type in sorted(entry_points_by_module):
98 # calculates the longest config name so we offset the printing
99 longest_name_length = max(
100 len(k) for k in entry_points_by_module[config_type].keys()
101 )
103 # set-up printing options
104 print_string = " %%-%ds %%s" % (longest_name_length,)
105 # 79 - 4 spaces = 75 (see string above)
106 description_leftover = 75 - longest_name_length
108 print(f"module: {config_type}")
109 for name in sorted(entry_points_by_module[config_type]):
110 ep = entry_point_dict[name]
112 if verbose >= 1:
113 module = ep.load()
114 doc = inspect.getdoc(module)
115 if doc is not None:
116 summary = doc.split("\n\n")[0]
117 else:
118 summary = "<DOCSTRING NOT AVAILABLE>"
119 else:
120 summary = ""
122 summary = (
123 (summary[: (description_leftover - 3)] + "...")
124 if len(summary) > (description_leftover - 3)
125 else summary
126 )
128 print(print_string % (name, summary))
131@config.command(
132 epilog="""Examples:
134\b
135 1. Describes the Montgomery dataset configuration:
137 .. code:: sh
139 deepdraw config describe montgomery
142\b
143 2. Describes the Montgomery dataset configuration and lists its
144 contents:
146 .. code:: sh
148 deepdraw config describe montgomery -v
150"""
151)
152@click.argument(
153 "name",
154 required=True,
155 nargs=-1,
156)
157@verbosity_option(logger=logger)
158def describe(name, verbose) -> None:
159 """Describes a specific configuration file."""
160 entry_points = _retrieve_entry_points("deepdraw.config")
161 entry_point_dict = {k.name: k for k in entry_points}
163 for k in name:
164 if k not in entry_point_dict:
165 logger.error("Cannot find configuration resource '%s'", k)
166 continue
167 ep = entry_point_dict[k]
168 print(f"Configuration: {ep.name}")
169 print(f"Python Module: {ep.module}")
170 print("")
171 mod = ep.load()
173 if verbose >= 1:
174 fname = inspect.getfile(mod)
175 print("Contents:")
176 with open(fname) as f:
177 print(f.read())
178 else: # only output documentation
179 print("Documentation:")
180 print(inspect.getdoc(mod))
183@config.command(
184 epilog="""Examples:
186\b
187 1. Makes a copy of one of the stock configuration files locally, so it can be
188 adapted:
190 .. code:: sh
192 $ deepdraw config copy montgomery -vvv newdataset.py
194"""
195)
196@click.argument(
197 "source",
198 required=True,
199 nargs=1,
200)
201@click.argument(
202 "destination",
203 required=True,
204 nargs=1,
205)
206@verbosity_option(logger=logger, expose_value=False)
207def copy(source, destination) -> None:
208 """Copy a specific configuration resource so it can be modified locally."""
209 import shutil
211 entry_points = _retrieve_entry_points("deepdraw.config")
212 entry_point_dict = {k.name: k for k in entry_points}
214 if source not in entry_point_dict:
215 logger.error("Cannot find configuration resource '%s'", source)
216 return
218 ep = entry_point_dict[source]
219 mod = ep.load()
220 src_name = inspect.getfile(mod)
221 logger.info(f"cp {src_name} -> {destination}")
222 shutil.copyfile(src_name, destination)