@@ -406,6 +406,84 @@ metadata in locations other than the file system, subclass
406406a custom finder, return instances of this derived ``Distribution `` in the
407407``find_distributions() `` method.
408408
409+ Example
410+ -------
411+
412+ Consider for example a custom finder that loads Python
413+ modules from a database::
414+
415+ class DatabaseImporter(importlib.abc.MetaPathFinder):
416+ def __init__(self, db):
417+ self.db = db
418+
419+ def find_spec(self, fullname, target=None) -> ModuleSpec:
420+ return self.db.spec_from_name(fullname)
421+
422+ sys.meta_path.append(DatabaseImporter(connect_db(...)))
423+
424+ That importer now presumably provides importable modules from a
425+ database, but it provides no metadata or entry points. For this
426+ custom importer to provide metadata, it would also need to implement
427+ ``DistributionFinder ``::
428+
429+ from importlib.metadata import DistributionFinder
430+
431+ class DatabaseImporter(DistributionFinder):
432+ ...
433+
434+ def find_distributions(self, context=DistributionFinder.Context()):
435+ query = dict(name=context.name) if context.name else {}
436+ for dist_record in self.db.query_distributions(query):
437+ yield DatabaseDistribution(dist_record)
438+
439+ In this way, ``query_distributions `` would return records for
440+ each distribution served by the database matching the query. For
441+ example, if ``requests-1.0 `` is in the database, ``find_distributions ``
442+ would yield a ``DatabaseDistribution `` for ``Context(name='requests') ``
443+ or ``Context(name=None) ``.
444+
445+ For the sake of simplicity, this example ignores ``context.path ``\. The
446+ ``path `` attribute defaults to ``sys.path `` and is the set of import paths to
447+ be considered in the search. A ``DatabaseImporter `` could potentially function
448+ without any concern for a search path. Assuming the importer does no
449+ partitioning, the "path" would be irrelevant. In order to illustrate the
450+ purpose of ``path ``, the example would need to illustrate a more complex
451+ ``DatabaseImporter `` whose behavior varied depending on
452+ ``sys.path ``/``PYTHONPATH ``. In that case, the ``find_distributions `` should
453+ honor the ``context.path `` and only yield ``Distribution ``\ s pertinent to that
454+ path.
455+
456+ ``DatabaseDistribution ``, then, would look something like::
457+
458+ class DatabaseDistribution(importlib.metadata.Distributon):
459+ def __init__(self, record):
460+ self.record = record
461+
462+ def read_text(self, filename):
463+ """
464+ Read a file like "METADATA" for the current distribution.
465+ """
466+ if filename == "METADATA":
467+ return f"""Name: {self.record.name}
468+ Version: {self.record.version}
469+ """
470+ if filename == "entry_points.txt":
471+ return "\n".join(
472+ f"""[{ep.group}]\n{ep.name}={ep.value}"""
473+ for ep in self.record.entry_points)
474+
475+ def locate_file(self, path):
476+ raise RuntimeError("This distribution has no file system")
477+
478+ This basic implementation should provide metadata and entry points for
479+ packages served by the ``DatabaseImporter ``, assuming that the
480+ ``record `` supplies suitable ``.name ``, ``.version ``, and
481+ ``.entry_points `` attributes.
482+
483+ The ``DatabaseDistribution `` may also provide other metadata files, like
484+ ``RECORD `` (required for ``Distribution.files ``) or override the
485+ implementation of ``Distribution.files ``. See the source for more inspiration.
486+
409487
410488.. _`entry point API` : https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
411489.. _`metadata API` : https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api
0 commit comments