feat: add util.resource_path()
function
need that now that we have configurable mako template paths
This commit is contained in:
parent
94868bbaa9
commit
b401fac04f
|
@ -27,6 +27,7 @@ classifiers = [
|
|||
requires-python = ">= 3.8"
|
||||
dependencies = [
|
||||
'importlib-metadata; python_version < "3.10"',
|
||||
"importlib_resources ; python_version < '3.9'",
|
||||
"progress",
|
||||
"python-configuration",
|
||||
]
|
||||
|
|
|
@ -26,6 +26,7 @@ WuttJamaican - utilities
|
|||
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
import shlex
|
||||
from uuid import uuid1
|
||||
|
||||
|
@ -264,3 +265,45 @@ def progress_loop(func, items, factory, message=None):
|
|||
|
||||
if progress:
|
||||
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
|
||||
|
|
|
@ -277,3 +277,43 @@ class TestProgressLoop(TestCase):
|
|||
# without progress
|
||||
mod.progress_loop(act, [1, 2, 3], None,
|
||||
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')
|
||||
|
|
Loading…
Reference in a new issue