3
0
Fork 0

Compare commits

..

No commits in common. "197e8369589ecb02b8c34a6917be548c7548c584" and "17efdb95729873ed14921531243d3a0a547f7203" have entirely different histories.

6 changed files with 15 additions and 81 deletions

View file

@ -5,12 +5,6 @@ All notable changes to WuttJamaican will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## v0.28.10 (2026-03-17)
### Fix
- add `lists` param for `load_entry_points()` function
## v0.28.9 (2026-03-04) ## v0.28.9 (2026-03-04)
### Fix ### Fix

View file

@ -42,7 +42,6 @@ intersphinx_mapping = {
"rattail": ("https://docs.wuttaproject.org/rattail/", None), "rattail": ("https://docs.wuttaproject.org/rattail/", None),
"rattail-manual": ("https://docs.wuttaproject.org/rattail-manual/", None), "rattail-manual": ("https://docs.wuttaproject.org/rattail-manual/", None),
"rich": ("https://rich.readthedocs.io/en/latest/", None), "rich": ("https://rich.readthedocs.io/en/latest/", None),
"setuptools": ("https://setuptools.pypa.io/en/latest/", None),
"sqlalchemy": ("http://docs.sqlalchemy.org/en/latest/", None), "sqlalchemy": ("http://docs.sqlalchemy.org/en/latest/", None),
"wutta-continuum": ("https://docs.wuttaproject.org/wutta-continuum/", None), "wutta-continuum": ("https://docs.wuttaproject.org/wutta-continuum/", None),
"wuttasync": ("https://docs.wuttaproject.org/wuttasync/", None), "wuttasync": ("https://docs.wuttaproject.org/wuttasync/", None),

View file

@ -222,9 +222,13 @@ Glossary
entry point entry point
This refers to a "setuptools-style" entry point specifically, This refers to a "setuptools-style" entry point specifically,
which is a mechanism used to register "plugins" and the like. which is a mechanism used to register "plugins" and the like.
This lets the app / config discover features dynamically. This lets the app / config discover features dynamically. Most
notably used to register :term:`commands<command>` and
:term:`subcommands<subcommand>`.
For more info see :doc:`setuptools:userguide/entry_point` in the setuptools docs. For more info see the `Python Packaging User Guide`_.
.. _Python Packaging User Guide: https://packaging.python.org/en/latest/specifications/entry-points/
handler handler
Similar to a "plugin" concept but only *one* handler may be used Similar to a "plugin" concept but only *one* handler may be used

View file

@ -6,7 +6,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "WuttJamaican" name = "WuttJamaican"
version = "0.28.10" version = "0.28.9"
description = "Base package for Wutta Framework" description = "Base package for Wutta Framework"
readme = "README.md" readme = "README.md"
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}] authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# WuttJamaican -- Base package for Wutta Framework # WuttJamaican -- Base package for Wutta Framework
# Copyright © 2023-2026 Lance Edgar # Copyright © 2023-2025 Lance Edgar
# #
# This file is part of Wutta Framework. # This file is part of Wutta Framework.
# #
@ -110,36 +110,22 @@ def get_value(obj, key):
return getattr(obj, key) return getattr(obj, key)
def load_entry_points(group, lists=False, ignore_errors=False): def load_entry_points(group, ignore_errors=False):
""" """
Load a set of ``setuptools``-style :term:`entry points <entry Load a set of ``setuptools``-style entry points.
point>`.
This is used to locate "plugins" and similar things, e.g. discover This is used to locate "plugins" and similar things, e.g. the set
which batch handlers are installed. of subcommands which belong to a main command.
Logic will inspect the registered entry points and return a dict
whose keys are the entry point names. By default the dict values
will be the loaded objects as referenced by each entry point.
In some cases (notably, import handlers for wuttasync) the keys
may not always be unique. This allows multiple projects to define
entry points for the same key. If you specify ``lists=True`` then
the dict values will each be *lists* of loaded objects instead.
(Otherwise some entry points would be discarded when duplicate
keys are found.)
:param group: The group (string name) of entry points to be :param group: The group (string name) of entry points to be
loaded, e.g. ``'wutta.commands'``. loaded, e.g. ``'wutta.commands'``.
:param lists: Whether to return lists instead of single object
values.
:param ignore_errors: If false (the default), any errors will be :param ignore_errors: If false (the default), any errors will be
raised normally. If true, errors will be logged but not raised normally. If true, errors will be logged but not
raised. raised.
:returns: A dict of entry points, as described above. :returns: A dictionary whose keys are the entry point names, and
values are the loaded entry points.
""" """
entry_points = {} entry_points = {}
@ -164,16 +150,7 @@ def load_entry_points(group, lists=False, ignore_errors=False):
raise raise
log.warning("failed to load entry point: %s", entry_point, exc_info=True) log.warning("failed to load entry point: %s", entry_point, exc_info=True)
else: else:
if lists: entry_points[entry_point.name] = ep
entry_points.setdefault(entry_point.name, []).append(ep)
else:
if entry_point.name in entry_points:
log.warning(
"overwriting existing key '%s' with entry point: %s",
entry_point.name,
ep,
)
entry_points[entry_point.name] = ep
return entry_points return entry_points

View file

@ -184,46 +184,6 @@ class TestLoadEntryPoints(TestCase):
entry_points.select.assert_called_once_with(group="wuttatest.thingers") entry_points.select.assert_called_once_with(group="wuttatest.thingers")
entry_point.load.assert_called_once_with() entry_point.load.assert_called_once_with()
def test_duplicate_key(self):
# two entry points, with same name
ep1 = MagicMock()
ep2 = MagicMock()
ep1.name = "not-quite-unique-key"
ep2.name = "not-quite-unique-key"
ep1.load.return_value = ep1
ep2.load.return_value = ep2
# they both are included for "all" entry points
entry_points = MagicMock()
entry_points.select.return_value = [ep1, ep2]
importlib = MagicMock()
importlib.metadata.entry_points.return_value = entry_points
with patch.dict("sys.modules", **{"importlib": importlib}):
# but only the 2nd entry point is returned
result = mod.load_entry_points("console_scripts")
self.assertIsInstance(result, dict)
self.assertGreaterEqual(len(result), 1)
self.assertIn("not-quite-unique-key", result)
self.assertIs(result["not-quite-unique-key"], ep2)
def test_lists(self):
# classic behvaior
result = mod.load_entry_points("console_scripts", lists=False)
self.assertIsInstance(result, dict)
self.assertIn("pip", result)
self.assertTrue(callable(result["pip"]))
# lists behavior
result = mod.load_entry_points("console_scripts", lists=True)
self.assertIsInstance(result, dict)
self.assertIn("pip", result)
self.assertIsInstance(result["pip"], list)
self.assertEqual(len(result["pip"]), 1)
self.assertTrue(callable(result["pip"][0]))
class TestLoadObject(TestCase): class TestLoadObject(TestCase):