Source code for wuttjamaican.util
# -*- coding: utf-8; -*-
################################################################################
#
# WuttJamaican -- Base package for Wutta Framework
# Copyright © 2023 Lance Edgar
#
# This file is part of Wutta Framework.
#
# Wutta Framework is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# Wutta Framework is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
WuttJamaican - utilities
"""
import importlib
import logging
import shlex
log = logging.getLogger(__name__)
# nb. this is used as default kwarg value in some places, to
# distinguish passing a ``None`` value, vs. *no* value at all
UNSPECIFIED = object()
[docs]
def load_entry_points(group, ignore_errors=False):
"""
Load a set of ``setuptools``-style entry points.
This is used to locate "plugins" and similar things, e.g. the set
of subcommands which belong to a main command.
:param group: The group (string name) of entry points to be
loaded, e.g. ``'wutta.commands'``.
:param ignore_errors: If false (the default), any errors will be
raised normally. If true, errors will be logged but not
raised.
:returns: A dictionary whose keys are the entry point names, and
values are the loaded entry points.
"""
entry_points = {}
try:
# nb. this package was added in python 3.8
import importlib.metadata
except ImportError:
# older setup, must use pkg_resources
# TODO: remove this section once we require python 3.8
from pkg_resources import iter_entry_points
for entry_point in iter_entry_points(group):
try:
ep = entry_point.load()
except:
if not ignore_errors:
raise
log.warning("failed to load entry point: %s", entry_point,
exc_info=True)
else:
entry_points[entry_point.name] = ep
else:
# newer setup (python >= 3.8); can use importlib, but the
# details may vary
eps = importlib.metadata.entry_points()
if not hasattr(eps, 'select'):
# python < 3.10
eps = eps.get(group, [])
else:
# python >= 3.10
eps = eps.select(group=group)
for entry_point in eps:
try:
ep = entry_point.load()
except:
if not ignore_errors:
raise
log.warning("failed to load entry point: %s", entry_point,
exc_info=True)
else:
entry_points[entry_point.name] = ep
return entry_points
[docs]
def load_object(spec):
"""
Load an arbitrary object from a module, according to the spec.
The spec string should contain a dotted path to an importable module,
followed by a colon (``':'``), followed by the name of the object to be
loaded. For example:
.. code-block:: none
wuttjamaican.util:parse_bool
You'll notice from this example that "object" in this context refers to any
valid Python object, i.e. not necessarily a class instance. The name may
refer to a class, function, variable etc. Once the module is imported, the
``getattr()`` function is used to obtain a reference to the named object;
therefore anything supported by that approach should work.
:param spec: Spec string.
:returns: The specified object.
"""
if not spec:
raise ValueError("no object spec provided")
module_path, name = spec.split(':')
module = importlib.import_module(module_path)
return getattr(module, name)
[docs]
def parse_bool(value):
"""
Derive a boolean from the given string value.
"""
if value is None:
return None
if isinstance(value, bool):
return value
if str(value).lower() in ('true', 'yes', 'y', 'on', '1'):
return True
return False
[docs]
def parse_list(value):
"""
Parse a configuration value, splitting by whitespace and/or commas
and taking quoting into account etc., yielding a list of strings.
"""
if value is None:
return []
if isinstance(value, list):
return value
parser = shlex.shlex(value)
parser.whitespace += ','
parser.whitespace_split = True
values = list(parser)
for i, value in enumerate(values):
if value.startswith('"') and value.endswith('"'):
values[i] = value[1:-1]
elif value.startswith("'") and value.endswith("'"):
values[i] = value[1:-1]
return values