2
0
Fork 0

feat: add util.resource_path() function

need that now that we have configurable mako template paths
This commit is contained in:
Lance Edgar 2024-08-26 10:12:52 -05:00
parent 94868bbaa9
commit b401fac04f
3 changed files with 84 additions and 0 deletions

View file

@ -27,6 +27,7 @@ classifiers = [
requires-python = ">= 3.8" requires-python = ">= 3.8"
dependencies = [ dependencies = [
'importlib-metadata; python_version < "3.10"', 'importlib-metadata; python_version < "3.10"',
"importlib_resources ; python_version < '3.9'",
"progress", "progress",
"python-configuration", "python-configuration",
] ]

View file

@ -26,6 +26,7 @@ WuttJamaican - utilities
import importlib import importlib
import logging import logging
import os
import shlex import shlex
from uuid import uuid1 from uuid import uuid1
@ -264,3 +265,45 @@ def progress_loop(func, items, factory, message=None):
if progress: if progress:
progress.finish() progress.finish()
def resource_path(path):
"""
Returns the absolute file path for the given resource path.
A "resource path" is one which designates a python package name,
plus some path under that. For instance:
.. code-block:: none
wuttjamaican.email:templates
Assuming such a path should exist, the question is "where?"
So this function uses :mod:`python:importlib.resources` to locate
the path, possibly extracting the file(s) from a zipped package,
and returning the final path on disk.
It only does this if it detects it is needed, based on the given
``path`` argument. If that is already an absolute path then it
will be returned as-is.
:param path: Either a package resource specifier as shown above,
or regular file path.
:returns: Absolute file path to the resource.
"""
if not os.path.isabs(path) and ':' in path:
try:
# nb. these were added in python 3.9
from importlib.resources import files, as_file
except ImportError: # python < 3.9
from importlib_resources import files, as_file
package, filename = path.split(':')
ref = files(package) / filename
with as_file(ref) as path:
return str(path)
return path

View file

@ -277,3 +277,43 @@ class TestProgressLoop(TestCase):
# without progress # without progress
mod.progress_loop(act, [1, 2, 3], None, mod.progress_loop(act, [1, 2, 3], None,
message="whatever") message="whatever")
class TestResourcePath(TestCase):
def test_basic(self):
# package spec is resolved to path
path = mod.resource_path('wuttjamaican:util.py')
self.assertTrue(path.endswith('wuttjamaican/util.py'))
# absolute path returned as-is
self.assertEqual(mod.resource_path('/tmp/doesnotexist.txt'), '/tmp/doesnotexist.txt')
def test_basic_pre_python_3_9(self):
# the goal here is to get coverage for code which would only
# run on python 3.8 and older, but we only need that coverage
# if we are currently testing python 3.9+
if sys.version_info.major == 3 and sys.version_info.minor < 9:
pytest.skip("this test is not relevant before python 3.9")
from importlib.resources import files, as_file
orig_import = __import__
def mock_import(name, globals=None, locals=None, fromlist=(), level=0):
if name == 'importlib.resources':
raise ImportError
if name == 'importlib_resources':
return MagicMock(files=files, as_file=as_file)
return orig_import(name, globals, locals, fromlist, level)
with patch('builtins.__import__', side_effect=mock_import):
# package spec is resolved to path
path = mod.resource_path('wuttjamaican:util.py')
self.assertTrue(path.endswith('wuttjamaican/util.py'))
# absolute path returned as-is
self.assertEqual(mod.resource_path('/tmp/doesnotexist.txt'), '/tmp/doesnotexist.txt')