diff --git a/docs/how-to-guides/editable-installs.rst b/docs/how-to-guides/editable-installs.rst index fc62370c1..54bfb3645 100644 --- a/docs/how-to-guides/editable-installs.rst +++ b/docs/how-to-guides/editable-installs.rst @@ -108,6 +108,61 @@ An alternative build directory can be specified using the :option:`build-dir` config setting. +Data files +---------- + +It is relatively common to install data files needed at runtime +alongside the package's Python code or extension modules. For a Python +package named ``package`` this would look like this: + +.. TODO the :force: option should be removed once the pygments meson lexer is + updated to fix https://github.com/pygments/pygments/issues/2918 + +.. literalinclude:: ../../tests/packages/install-data/meson.build + :language: meson + :force: + :lines: 5- + +In most circumstances, these files can be accessed deriving their +filesystem path from the filesystem path of the Python module next to +them via the ``__file__`` special variable. For example, within the +package ``__init__.py``: + +.. code-block:: python + + import pathlib + + data = pathlib.Path(__file__).parent.joinpath('data.txt').read_text() + uuid = pathlib.Path(__file__).parent.joinpath('uuid.txt').read_text() # WRONG! + +However, this does not work when modules are not loaded from a package +installed in the Python library path in the filesystem but with a +special module loader, as used to implement editable installs in +``meson-python``. In the example above, the second read would fail +when the package is installed in editable mode. For this reason, data +files need to be accessed using :mod:`importlib.resources`. The code +above should be replaced with: + +.. literalinclude:: ../../tests/packages/install-data/__init__.py + :lines: 5- + +:mod:`importlib.resources` implements a virtual filesystem that allows +to access individual files as if they were in their install location. +However, there is no way to expose this file structure outside of the +python runtime. In the example above, it is not possible to make the +``data.txt`` and ``uuid.txt`` files appear in the same fileystem +directory. + +.. warning:: + + The :mod:`importlib.resources` appeared in Python 3.7 but it did not work + correctly for this use until Python 3.10. The `importlib-resources`_ + backport version 5.10 or later can be used if support for earlier Python + versions is desired. + +.. _importlib-resources: https://importlib-resources.readthedocs.io/en/latest/index.html + + .. _how-to-guides-editable-installs-verbose: Verbose mode diff --git a/pyproject.toml b/pyproject.toml index da9caf8ef..ce44e0606 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,6 +109,11 @@ known-first-party = [ 'mesonpy', ] +[tool.ruff.lint.per-file-ignores] +# this file is included literally in the documentation and the double +# empty lines forced by the import sorting style look odd there +'tests/packages/install-data/__init__.py' = ['I001'] + [tool.coverage.run] disable_warnings = [ diff --git a/tests/packages/install-data/__init__.py b/tests/packages/install-data/__init__.py new file mode 100644 index 000000000..2f3b7364f --- /dev/null +++ b/tests/packages/install-data/__init__.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2025 The meson-python developers +# +# SPDX-License-Identifier: MIT + +import importlib.resources + +data = importlib.resources.files(__package__).joinpath('data.txt').read_text() +uuid = importlib.resources.files(__package__).joinpath('uuid.txt').read_text() diff --git a/tests/packages/install-data/data.txt b/tests/packages/install-data/data.txt new file mode 100644 index 000000000..76848c82c --- /dev/null +++ b/tests/packages/install-data/data.txt @@ -0,0 +1 @@ +DATA diff --git a/tests/packages/install-data/data.txt.license b/tests/packages/install-data/data.txt.license new file mode 100644 index 000000000..443ef1738 --- /dev/null +++ b/tests/packages/install-data/data.txt.license @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2025 The meson-python developers +# +# SPDX-License-Identifier: MIT diff --git a/tests/packages/install-data/meson.build b/tests/packages/install-data/meson.build new file mode 100644 index 000000000..3f58466db --- /dev/null +++ b/tests/packages/install-data/meson.build @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2025 The meson-python developers +# +# SPDX-License-Identifier: MIT + +project('package', version: '1.0.0') + +py = import('python').find_installation() + +py.install_sources( + '__init__.py', + subdir: 'package', +) + +install_data( + 'data.txt', + install_dir: py.get_install_dir() / 'package', +) + +custom_target( + output: 'uuid.txt', + command: [py, '-m', 'uuid'], + capture: true, + install: true, + install_dir: py.get_install_dir() / 'package', +) diff --git a/tests/packages/install-data/pyproject.toml b/tests/packages/install-data/pyproject.toml new file mode 100644 index 000000000..79b6ea07f --- /dev/null +++ b/tests/packages/install-data/pyproject.toml @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The meson-python developers +# +# SPDX-License-Identifier: MIT + +[build-system] +build-backend = 'mesonpy' +requires = ['meson-python'] diff --git a/tests/test_editable.py b/tests/test_editable.py index cd69f0089..92656ec63 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -344,3 +344,9 @@ def test_editable_rebuild_error(package_purelib_and_platlib, tmp_path, verbose): del sys.meta_path[0] sys.modules.pop('pure', None) path.write_text(code) + + +@pytest.mark.skipif(sys.version_info < (3, 10), reason='importlib.resources is unusable on Python 3.9') +def test_install_data(venv, editable_install_data, tmp_path): + venv.pip('install', os.fspath(editable_install_data)) + venv.python('-c', 'import package')