2
0
Fork 0

feat: add AppHandler methods, get_distribution() and get_version()

This commit is contained in:
Lance Edgar 2024-08-05 21:48:45 -05:00
parent 0a46dddf3f
commit 2a21e70ff1
2 changed files with 167 additions and 0 deletions

View file

@ -152,6 +152,90 @@ class AppHandler:
return self.config.get(f'{self.appname}.app_title', return self.config.get(f'{self.appname}.app_title',
default=default or self.default_app_title) default=default or self.default_app_title)
def get_distribution(self, obj=None):
"""
Returns the appropriate Python distribution name.
If ``obj`` is specified, this will attempt to locate the
distribution based on the top-level module which contains the
object's type/class.
If ``obj`` is *not* specified, this behaves a bit differently.
It first will look for a :term:`config setting` named
``wutta.app_dist`` (or similar, dpending on :attr:`appname`).
If there is such a config value, it is returned. Otherwise
the "auto-locate" logic described above happens, but using
``self`` instead of ``obj``.
In other words by default this returns the distribution to
which the running :term:`app handler` belongs.
See also :meth:`get_version()`.
:param obj: Any object which may be used as a clue to locate
the appropriate distribution.
:returns: string, or ``None``
Also note that a *distribution* name is different from a
*package* name. The distribution name is how things appear on
PyPI for instance.
If you want to override the default distribution name (and
skip the auto-locate based on app handler) then you can define
it in config:
.. code-block:: ini
[wutta]
app_dist = My-Poser-Dist
"""
if obj is None:
print(self.appname)
dist = self.config.get(f'{self.appname}.app_dist')
if dist:
return dist
# TODO: do we need a config setting for app_package ?
#modpath = self.config.get(f'{self.appname}.app_package')
modpath = None
if not modpath:
modpath = type(obj if obj is not None else self).__module__
pkgname = modpath.split('.')[0]
try:
from importlib.metadata import packages_distributions
except ImportError: # python < 3.10
from importlib_metadata import packages_distributions
pkgmap = packages_distributions()
if pkgname in pkgmap:
dist = pkgmap[pkgname][0]
return dist
# fall back to configured dist, if obj lookup failed
if obj is not None:
return self.config.get(f'{self.appname}.app_dist')
def get_version(self, dist=None, obj=None):
"""
Returns the version of a given Python distribution.
If ``dist`` is not specified, calls :meth:`get_distribution()`
to get it. (It passes ``obj`` along for this).
So by default this will return the version of whichever
distribution owns the running :term:`app handler`.
:returns: Version as string.
"""
from importlib.metadata import version
if not dist:
dist = self.get_distribution(obj=obj)
if dist:
return version(dist)
def get_model(self): def get_model(self):
""" """
Returns the :term:`app model` module. Returns the :term:`app model` module.

View file

@ -2,6 +2,7 @@
import os import os
import shutil import shutil
import sys
import tempfile import tempfile
import warnings import warnings
from unittest import TestCase from unittest import TestCase
@ -116,6 +117,88 @@ class TestAppHandler(TestCase):
def test_get_title(self): def test_get_title(self):
self.assertEqual(self.app.get_title(), 'WuttJamaican') self.assertEqual(self.app.get_title(), 'WuttJamaican')
def test_get_distribution(self):
# default should always be WuttJamaican (right..?)
dist = self.app.get_distribution()
self.assertEqual(dist, 'WuttJamaican')
# also works with "non-native" objects
from config import Configuration
config = Configuration({})
dist = self.app.get_distribution(config)
self.assertEqual(dist, 'python-configuration')
# can override dist via config
self.config.setdefault('wuttatest.app_dist', 'importlib_metadata')
dist = self.app.get_distribution()
self.assertEqual(dist, 'importlib_metadata')
# but the provided object takes precedence
dist = self.app.get_distribution(config)
self.assertEqual(dist, 'python-configuration')
def test_get_distribution_pre_python_3_10(self):
# the goal here is to get coverage for code which would only
# run on python 3,9 and older, but we only need that coverage
# if we are currently testing python 3.10+
if sys.version_info.major == 3 and sys.version_info.minor < 10:
pytest.skip("this test is not relevant before python 3.10")
importlib_metadata = MagicMock()
importlib_metadata.packages_distributions = MagicMock(
return_value={
'wuttjamaican': ['WuttJamaican'],
'config': ['python-configuration'],
})
orig_import = __import__
def mock_import(name, *args, **kwargs):
if name == 'importlib.metadata':
raise ImportError
if name == 'importlib_metadata':
return importlib_metadata
return orig_import(name, *args, **kwargs)
with patch('builtins.__import__', side_effect=mock_import):
# default should always be WuttJamaican (right..?)
dist = self.app.get_distribution()
self.assertEqual(dist, 'WuttJamaican')
# also works with "non-native" objects
from config import Configuration
config = Configuration({})
dist = self.app.get_distribution(config)
self.assertEqual(dist, 'python-configuration')
# hacky sort of test, just in case we can't deduce the
# package dist based on the obj - easy enough since we
# have limited the packages_distributions() above
dist = self.app.get_distribution(42)
self.assertIsNone(dist)
# can override dist via config
self.config.setdefault('wuttatest.app_dist', 'importlib_metadata')
dist = self.app.get_distribution()
self.assertEqual(dist, 'importlib_metadata')
# but the provided object takes precedence
dist = self.app.get_distribution(config)
self.assertEqual(dist, 'python-configuration')
# hacky test again, this time config override should win
dist = self.app.get_distribution(42)
self.assertEqual(dist, 'importlib_metadata')
def test_get_version(self):
from importlib.metadata import version
# default should always be for WuttJamaican (right..?)
self.assertEqual(self.app.get_version(), version('WuttJamaican'))
def test_make_title(self): def test_make_title(self):
text = self.app.make_title('foo_bar') text = self.app.make_title('foo_bar')
self.assertEqual(text, "Foo Bar") self.assertEqual(text, "Foo Bar")