diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index db46db8c6449..836b69a5f7f0 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -59,11 +59,10 @@ if TYPE_CHECKING: from galaxy.model import User -try: - from importlib.resources import files # type: ignore[attr-defined] -except ImportError: - # Python < 3.9 - from importlib_resources import files # type: ignore[no-redef] +if sys.version_info >= (3, 9): + from importlib.resources import files +else: + from importlib_resources import files log = logging.getLogger(__name__) diff --git a/lib/galaxy/config/templates.py b/lib/galaxy/config/templates.py index e3d438b846a3..fc4b473ce7cc 100644 --- a/lib/galaxy/config/templates.py +++ b/lib/galaxy/config/templates.py @@ -10,14 +10,17 @@ from jinja2 import Environment -from galaxy.util.resources import resource_path +from galaxy.util.resources import ( + resource_path, + Traversable, +) TEMPLATE_SEP = ">>>>>>" # Used to split templates into doc/body sections def render(template_path: str, context: dict, custom_templates_dir: str) -> str: """Read and return templated content as string.""" - with open(_get_template_path(template_path, custom_templates_dir)) as f: + with _get_template_path(template_path, custom_templates_dir).open() as f: template_str = _get_template_body(f.read()) tmpl = Environment().from_string(template_str) return tmpl.render(**context) @@ -28,7 +31,7 @@ def _get_template_body(template: str) -> str: return template.split(TEMPLATE_SEP, 1)[-1].split("\n", 1)[1] -def _get_template_path(relpath: str, custom_templates_dir: str) -> Path: +def _get_template_path(relpath: str, custom_templates_dir: str) -> Traversable: """Return template file path.""" default_path = resource_path("galaxy.config", "templates") / relpath custom_path = Path(custom_templates_dir) / relpath diff --git a/lib/galaxy/util/resources.py b/lib/galaxy/util/resources.py index a16a78f7202b..071921a68b77 100644 --- a/lib/galaxy/util/resources.py +++ b/lib/galaxy/util/resources.py @@ -1,24 +1,38 @@ """Provide a consistent interface into and utilities for importlib file resources. """ -try: - from importlib.abc import Traversable # type: ignore[attr-defined] - from importlib.resources import files # type: ignore[attr-defined] -except ImportError: - # Python < 3.9 - from importlib_resources import files # type: ignore[no-redef] - from importlib_resources.abc import Traversable # type: ignore[no-redef] - - -def resource_path(package_or_requirement, resource_name): +import sys + +if sys.version_info >= (3, 12): + from importlib.resources import ( + Anchor, + as_file, + files, + ) + from importlib.resources.abc import Traversable +elif sys.version_info >= (3, 9): + from importlib.abc import Traversable + from importlib.resources import ( + as_file, + files, + Package as Anchor, + ) +else: + from importlib_resources import ( + as_file, + files, + Package as Anchor, + ) + from importlib_resources.abc import Traversable + + +def resource_path(package_or_requirement: Anchor, resource_name: str) -> Traversable: """ - Return specified resource as a string. - - Replacement function for pkg_resources.resource_string, but returns unicode string instead of bytestring. + Return specified resource as a Traversable. """ return files(package_or_requirement).joinpath(resource_name) -def resource_string(package_or_requirement, resource_name) -> str: +def resource_string(package_or_requirement: Anchor, resource_name: str) -> str: """ Return specified resource as a string. @@ -28,6 +42,7 @@ def resource_string(package_or_requirement, resource_name) -> str: __all__ = ( + "as_file", "files", "resource_string", "resource_path", diff --git a/packages/util/setup.cfg b/packages/util/setup.cfg index d9059545d977..39708d11975a 100644 --- a/packages/util/setup.cfg +++ b/packages/util/setup.cfg @@ -35,7 +35,7 @@ install_requires = bleach boltons docutils!=0.17,!=0.17.1 - importlib_resources + importlib-resources;python_version<'3.9' packaging pyparsing PyYAML diff --git a/packages/web_apps/setup.cfg b/packages/web_apps/setup.cfg index 98476be2fb28..472f09cc1c85 100644 --- a/packages/web_apps/setup.cfg +++ b/packages/web_apps/setup.cfg @@ -46,7 +46,7 @@ install_requires = fastapi-utils gunicorn gxformat2 - importlib_resources + importlib-resources;python_version<'3.9' Mako MarkupSafe mercurial diff --git a/pyproject.toml b/pyproject.toml index befdd176d346..c6c3184f2337 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ gxformat2 = "*" h5grove = ">=1.2.1" h5py = "*" importlib-metadata = "<5" # Work around https://github.com/celery/kombu/issues/1600 -importlib-resources = "*" +importlib-resources = { version = "*", python = "<3.9" } # for importlib.{abc.Traversable,resources.{files, Package}} isa-rwval = ">=0.10.10" kombu = "*" lagom = "*" @@ -167,6 +167,7 @@ watchdog = "*" [tool.poetry.group.typecheck.dependencies] mypy = "*" +lxml-stubs = "*" pydantic = "<2" # for pydantic.mypy plugin types-bleach = "*" types-boto = "*" diff --git a/test/unit/data/model/migrations/test_migrations.py b/test/unit/data/model/migrations/test_migrations.py index 20665cd1fdd5..51cba45265ab 100644 --- a/test/unit/data/model/migrations/test_migrations.py +++ b/test/unit/data/model/migrations/test_migrations.py @@ -43,7 +43,10 @@ drop_existing_database, url_factory, ) -from galaxy.util.resources import resource_path +from galaxy.util.resources import ( + as_file, + resource_path, +) # Revision numbers from test versions directories GXY_REVISION_0 = "62695fac6cc0" # oldest/base @@ -1128,10 +1131,13 @@ def db_state6_gxy_state3_tsi_no_sam(url_factory, metadata_state6_gxy_state3_tsi_ @pytest.fixture(autouse=True) def legacy_manage_db(monkeypatch): def get_alembic_cfg(): - path = resource_path("galaxy.model.migrations", "alembic.ini") - config = Config(path) - path1, path2 = _get_paths_to_version_locations() - config.set_main_option("version_locations", f"{path1};{path2}") + traversable = resource_path("galaxy.model.migrations", "alembic.ini") + with as_file(traversable) as path: + config = Config(path) + path1, path2 = _get_paths_to_version_locations() + config.set_main_option("version_locations", f"{path1};{path2}") + # config.set_main_option() calls config.file_config() which memoizes + # the temporary file path from the as_file() contextmanager return config monkeypatch.setattr(galaxy.model.migrations.scripts, "get_alembic_cfg", get_alembic_cfg) diff --git a/test/unit/tool_util/data/test_tool_data_bundles.py b/test/unit/tool_util/data/test_tool_data_bundles.py index 2e1a1f271740..9c6afddc5735 100644 --- a/test/unit/tool_util/data/test_tool_data_bundles.py +++ b/test/unit/tool_util/data/test_tool_data_bundles.py @@ -15,7 +15,10 @@ galaxy_directory, parse_xml, ) -from galaxy.util.resources import resource_path +from galaxy.util.resources import ( + as_file, + resource_path, +) TOOLS_DIRECTORY = os.path.abspath(os.path.join(galaxy_directory(), "test/functional/tools/")) @@ -51,8 +54,8 @@ def test_xml_parsing() -> None: def test_parsing_manual() -> None: - path = resource_path(__package__, "example_data_managers/manual.xml") - tree = parse_xml(path) + with as_file(resource_path(__package__, "example_data_managers/manual.xml")) as path: + tree = parse_xml(path) data_managers_el = tree.getroot() data_manager_el = data_managers_el.find("data_manager") description = convert_data_tables_xml(data_manager_el) @@ -61,8 +64,8 @@ def test_parsing_manual() -> None: def test_parsing_mothur() -> None: - path = resource_path(__package__, "example_data_managers/mothur.xml") - tree = parse_xml(path) + with as_file(resource_path(__package__, "example_data_managers/mothur.xml")) as path: + tree = parse_xml(path) data_managers_el = tree.getroot() data_manager_el = data_managers_el.find("data_manager") description = convert_data_tables_xml(data_manager_el)