Skip to content

Commit e2d5599

Browse files
committed
Put modified hbutils script into guide verbatim
1 parent 6930718 commit e2d5599

File tree

1 file changed

+52
-16
lines changed

1 file changed

+52
-16
lines changed

source/guides/handling-missing-extras-at-runtime.rst

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,59 @@ Using ``importlib.metadata`` and ``packaging``
5151
As a safer alternative that does check whether the optional dependencies are
5252
installed at the correct versions, :py:mod:`importlib.metadata` and
5353
:ref:`packaging` can be used to iterate through the extra's requirements
54-
recursively and check whether all are installed in the current environment.
55-
56-
This process is currently quite involved. An implementation can be found in
57-
`packaging-problems #664 <packaging-problems-664_>`_, which is also made
58-
available in the `hbutils <https://pypi.org/project/hbutils/>`_ package as
59-
``hbutils.system.check_reqs``.
60-
The possibility of offering a similar helper function in ``importlib.metadata``
61-
or ``packaging`` themselves is still being discussed
62-
(`packaging-problems #317 <packaging-problems-317_>`_).
63-
64-
With ``check_reqs`` included in your codebase or imported from ``hbutils``,
65-
usage is as simple as:
54+
recursively and check whether all are installed in the current environment
55+
(based on `code <hbutils-snippet_>`_ from the `hbutils`_ library):
6656

6757
.. code-block:: python
6858
59+
# TODO Unless we get special permission, this snippet is Apache-2-licensed:
60+
# https://github.com/HansBug/hbutils/blob/927b0757449a781ce8e30132f26b06089a24cd71/LICENSE
61+
62+
from collections.abc import Iterable
63+
from importlib.metadata import PackageNotFoundError, distribution, metadata
64+
65+
from packaging.metadata import Metadata
66+
from packaging.requirements import Requirement
67+
68+
def check_reqs(req_strs: Iterable[str]) -> bool:
69+
return all(
70+
_check_req_recursive(req)
71+
for req_str in req_strs
72+
if not (req := Requirement(req_str)).marker or req.marker.evaluate()
73+
)
74+
75+
def _check_req_recursive(req: Requirement) -> bool:
76+
try:
77+
version = distribution(req.name).version
78+
except PackageNotFoundError:
79+
return False # req not installed
80+
81+
if not req.specifier.contains(version):
82+
return False # req version does not match
83+
84+
req_metadata = Metadata.from_raw(metadata(req.name).json, validate=False)
85+
for child_req in req_metadata.requires_dist or []:
86+
for extra in req.extras:
87+
if child_req.marker and child_req.marker.evaluate({"extra": extra}):
88+
if not _check_req_recursive(child_req):
89+
return False
90+
break
91+
92+
return True
93+
94+
95+
# Perform check, e.g.:
6996
extra_installed = check_reqs(["your-package[your-extra]"])
7097
71-
In contrast to the method above, this is typically done in :term:`LBYL` style
72-
prior to importing the modules in question.
98+
TODO Either point out that this snippet doesn't actually check everything
99+
(https://github.com/HansBug/hbutils/issues/109) or fix it.
100+
101+
The possibility of offering a helper function similar to ``check_reqs`` in
102+
``importlib.metadata`` or ``packaging`` themselves is still being discussed
103+
(`packaging-problems #317 <packaging-problems-317_>`_).
104+
105+
In contrast to the method above, this check is typically done in :term:`LBYL`
106+
style prior to importing the modules in question.
73107
In principle, it could also be done after the imports succeeded just to check
74108
the version, in which case the imports themselves would have to be wrapped in a
75109
``try``-``except`` block to handle the possibility of not being installed at
@@ -262,10 +296,12 @@ TODO mention that you might want to provide a way for users to check
262296

263297
------------------
264298

299+
.. _hbutils-snippet: https://github.com/HansBug/hbutils/blob/927b0757449a781ce8e30132f26b06089a24cd71/hbutils/system/python/package.py#L171-L242
300+
301+
.. _hbutils: https://pypi.org/project/hbutils/
302+
265303
.. _pkg_resources: https://setuptools.pypa.io/en/latest/pkg_resources.html
266304

267305
.. _packaging-problems-317: https://github.com/pypa/packaging-problems/issues/317
268306

269-
.. _packaging-problems-664: https://github.com/pypa/packaging-problems/issues/664
270-
271307
.. _generalimport: https://github.com/ManderaGeneral/generalimport

0 commit comments

Comments
 (0)