.. py:currentmodule:: oxidized_importer

.. _oxidized_importer_zip_finder:

======================================
``OxidizedZipFinder`` Meta Path Finder
======================================

``oxidized_importer`` contains a pure Rust implementation of a *meta path
finder* that can load Python resources from zip files. Its goal is to be
a compatible reimplementation of ``zipimport.zipimporter`` from the Python
standard library.

Usage
=====

Instances of :py:class:`OxidizedZipFinder` are bound to zip archive data.

Instances can be constructed by calling
:py:meth:`OxidizedZipFinder.from_zip_data` or
:py:meth:`OxidizedZipFinder.from_path`.

:py:class:`OxidizedZipFinder` is a *meta path finder* and instances should
be registered on ``sys.meta_path``. e.g.

.. code-block:: python

   import os
   import sys
   import oxidized_importer

   HERE = os.dirname(os.path.abspath(__file__))
   zip_path = os.path.join(HERE, "archive.zip")

   zip_importer = OxidizedZipFinder.from_path(zip_path)
   sys.meta_path.insert(0, zip_importer)

Once an instance is registered on ``sys.meta_path``, it will be consulted
when an ``import`` is serviced by Python's importing mechanism.

Behavior
========

:py:class:`OxidizedZipFinder` is similar to - but critically different from -
the standard library ``zipimport.zipimporter``.

:py:class:`OxidizedZipFinder` is a *meta path finder*, not a
*path entry finder*. This means instances are bound to ``sys.meta_path`` and not
``sys.path_hooks``. Support for enabling use as a *path hook* is planned. The
lack of ``sys.path_hooks`` support means this importer can't be used as
a replacement for ``zipimport.zipimporter``.

All I/O and zip reading in :py:class:`OxidizedZipFinder` is implemented in
Rust. Subtle differences in behavior as a result of zip parsing implementations
could occur.

:py:class:`OxidizedZipFinder` doesn't yet implement support for resource
reading (e.g. the ``importlib.abc.ResourceReader`` interface). Only loading
of ``.py`` and ``.pyc`` files is supported.

:py:class:`OxidizedZipFinder` doesn't validate the header of ``.pyc``
files. If it sees a ``.pyc`` version of a module, its bytecode will be
used as-is. (``zipimport.zipimporter`` validates that the content in
the ``.pyc`` matches expectations.)

Support for opening just sub-directories within zip files is not
yet implemented.

Performance
===========

:py:class:`OxidizedZipFinder` should perform substantially better than
``zipimport.zipimporter``.

A test importing the ~450 modules that constitute the Python standard library
yielded the following results:

+---------------------+-----------------+-------------+-----------+--------------------+
| Environment         | ``zipimporter`` | Us (memory) | Us (file) | ``OxidizedFinder`` |
+---------------------+-----------------+-------------+-----------+--------------------+
| Ryzen 5950X Linux   |       205.07 ms |   168.70 ms | 184.74 ms |          126.33 ms |
+---------------------+-----------------+-------------+-----------+--------------------+
| Ryzen 5950X Windows |       235.73 ms |   147.14 ms | 167.10 ms |          140.21 ms |
+---------------------+-----------------+-------------+-----------+--------------------+

(The exact set of modules and Python versions were different between the
environments so it isn't fair to compare numbers across environments: only
within the same environment.)

Python API
==========

See :py:class:`OxidizedZipFinder` for the Python API documentation.