Coverage for .tox/coverage/lib/python3.11/site-packages/wuttjamaican/util.py: 100%
62 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-19 20:49 -0600
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-19 20:49 -0600
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# WuttJamaican -- Base package for Wutta Framework
5# Copyright © 2023 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24WuttJamaican - utilities
25"""
27import importlib
28import logging
29import shlex
32log = logging.getLogger(__name__)
35# nb. this is used as default kwarg value in some places, to
36# distinguish passing a ``None`` value, vs. *no* value at all
37UNSPECIFIED = object()
40def load_entry_points(group, ignore_errors=False):
41 """
42 Load a set of ``setuptools``-style entry points.
44 This is used to locate "plugins" and similar things, e.g. the set
45 of subcommands which belong to a main command.
47 :param group: The group (string name) of entry points to be
48 loaded, e.g. ``'wutta.commands'``.
50 :param ignore_errors: If false (the default), any errors will be
51 raised normally. If true, errors will be logged but not
52 raised.
54 :returns: A dictionary whose keys are the entry point names, and
55 values are the loaded entry points.
56 """
57 entry_points = {}
59 try:
60 # nb. this package was added in python 3.8
61 import importlib.metadata
63 except ImportError:
64 # older setup, must use pkg_resources
65 # TODO: remove this section once we require python 3.8
66 from pkg_resources import iter_entry_points
68 for entry_point in iter_entry_points(group):
69 try:
70 ep = entry_point.load()
71 except:
72 if not ignore_errors:
73 raise
74 log.warning("failed to load entry point: %s", entry_point,
75 exc_info=True)
76 else:
77 entry_points[entry_point.name] = ep
79 else:
80 # newer setup (python >= 3.8); can use importlib, but the
81 # details may vary
82 eps = importlib.metadata.entry_points()
83 if not hasattr(eps, 'select'):
84 # python < 3.10
85 eps = eps.get(group, [])
86 else:
87 # python >= 3.10
88 eps = eps.select(group=group)
89 for entry_point in eps:
90 try:
91 ep = entry_point.load()
92 except:
93 if not ignore_errors:
94 raise
95 log.warning("failed to load entry point: %s", entry_point,
96 exc_info=True)
97 else:
98 entry_points[entry_point.name] = ep
100 return entry_points
103def load_object(spec):
104 """
105 Load an arbitrary object from a module, according to the spec.
107 The spec string should contain a dotted path to an importable module,
108 followed by a colon (``':'``), followed by the name of the object to be
109 loaded. For example:
111 .. code-block:: none
113 wuttjamaican.util:parse_bool
115 You'll notice from this example that "object" in this context refers to any
116 valid Python object, i.e. not necessarily a class instance. The name may
117 refer to a class, function, variable etc. Once the module is imported, the
118 ``getattr()`` function is used to obtain a reference to the named object;
119 therefore anything supported by that approach should work.
121 :param spec: Spec string.
123 :returns: The specified object.
124 """
125 if not spec:
126 raise ValueError("no object spec provided")
128 module_path, name = spec.split(':')
129 module = importlib.import_module(module_path)
130 return getattr(module, name)
133def parse_bool(value):
134 """
135 Derive a boolean from the given string value.
136 """
137 if value is None:
138 return None
139 if isinstance(value, bool):
140 return value
141 if str(value).lower() in ('true', 'yes', 'y', 'on', '1'):
142 return True
143 return False
146def parse_list(value):
147 """
148 Parse a configuration value, splitting by whitespace and/or commas
149 and taking quoting into account etc., yielding a list of strings.
150 """
151 if value is None:
152 return []
153 if isinstance(value, list):
154 return value
155 parser = shlex.shlex(value)
156 parser.whitespace += ','
157 parser.whitespace_split = True
158 values = list(parser)
159 for i, value in enumerate(values):
160 if value.startswith('"') and value.endswith('"'):
161 values[i] = value[1:-1]
162 elif value.startswith("'") and value.endswith("'"):
163 values[i] = value[1:-1]
164 return values