Codectionary / Developer documentation / Python

Reading package resources

Read bundled data through importlib.resources instead of assuming a path relative to the current working directory.

How it works

A package resource is data distributed with an importable package, such as a template or small configuration file. importlib.resources.files returns a traversable resource container rather than promising an ordinary filesystem directory.

Packages may be loaded from different locations or supported archives. Reading through the resource interface avoids assuming that the user launched Python from your source directory. These Python 3.11 examples create a tiny temporary package so they can run independently.

Limitations and practical choices

Ensure the packaging configuration actually includes your data files. A working source checkout does not prove the built distribution contains them.

Use as_file only when another API needs a real path, and use that path inside its context. Do not write user preferences back into installed package resources.

Try it yourself

Create a package containing a greeting resource, then read it from a different working directory. Explain why a bare relative open call would not identify the package reliably.

Syntax

from importlib.resources import files
text = files(package).joinpath("message.txt").read_text(encoding="utf-8")

Examples

Read bundled text

Create a small demonstration package and read its text resource.

import importlib
import sys
from importlib.resources import files
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as folder:
    package = Path(folder) / "lesson_assets"
    package.mkdir()
    (package / "__init__.py").write_text("", encoding="utf-8")
    (package / "message.txt").write_text("Welcome", encoding="utf-8")
    sys.path.insert(0, folder)
    try:
        importlib.invalidate_caches()
        import lesson_assets
        resource = files(lesson_assets).joinpath("message.txt")
        print(resource.read_text(encoding="utf-8"))
    finally:
        sys.path.remove(folder)
        sys.modules.pop("lesson_assets", None)
  1. The setup creates __init__.py to make a package and writes message.txt beside it. A temporary sys.path entry makes that package importable for this isolated demonstration.
  2. files uses the imported package as its anchor. joinpath locates the resource and read_text decodes it explicitly as UTF-8, independent of the working directory.
  3. In an installed application the package already exists, so use just the import and resource-reading lines. The finally block removes this example’s temporary import state.
  4. Expected output: Welcome

Check and read bytes

Inspect a resource and choose binary access deliberately.

import importlib
import sys
from importlib.resources import files
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as folder:
    package = Path(folder) / "lesson_assets"
    package.mkdir()
    (package / "__init__.py").write_text("", encoding="utf-8")
    (package / "message.txt").write_text("Welcome", encoding="utf-8")
    sys.path.insert(0, folder)
    try:
        importlib.invalidate_caches()
        import lesson_assets
        resource = files(lesson_assets).joinpath("message.txt")
        print(resource.is_file())
        print(resource.read_bytes())
    finally:
        sys.path.remove(folder)
        sys.modules.pop("lesson_assets", None)
  1. The same temporary setup supplies a known resource. is_file confirms it is a file, then read_bytes returns its raw content.
  2. The b-prefixed output is a bytes representation rather than decoded text. Choose binary access for images or formats with their own decoding process.
  3. An existence check is not a substitute for handling a later read failure in real deployments. The resource interface avoids assuming that storage is a plain local directory.
  4. Expected output: True b'Welcome'

Provide a temporary real path

Bridge to an API that requires a filesystem path.

import importlib
import sys
from importlib.resources import files
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as folder:
    package = Path(folder) / "lesson_assets"
    package.mkdir()
    (package / "__init__.py").write_text("", encoding="utf-8")
    (package / "message.txt").write_text("Welcome", encoding="utf-8")
    sys.path.insert(0, folder)
    try:
        importlib.invalidate_caches()
        import lesson_assets
        from importlib.resources import as_file
        resource = files(lesson_assets).joinpath("message.txt")
        with as_file(resource) as path:
            print(path.read_text(encoding="utf-8"))
    finally:
        sys.path.remove(folder)
        sys.modules.pop("lesson_assets", None)
  1. as_file supplies a concrete Path for the file resource. When necessary, an implementation may extract a temporary file.
  2. The path is used inside the nested with block so its lifetime is respected. The example does not assume it remains valid after that context ends.
  3. Use this bridge for libraries that insist on a filename; prefer direct read_text or read_bytes when no actual path is needed.
  4. Expected output: Welcome

Best practices

  • Ensure the packaging configuration actually includes your data files. A working source checkout does not prove the built distribution contains them.
  • Use as_file only when another API needs a real path, and use that path inside its context. Do not write user preferences back into installed package resources.

Runtime & versions

Python interpreter. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

Python language reference

Specifications & further reading