Entry Points
fleche is extensible through Python entry points (the standard
importlib.metadata plugin mechanism): any installed package can
register digest hooks for its types in the fleche entry point group under
the name digest. Installing such a package is all it takes — no imports,
no calls to add_hook(). The first time
digest() encounters a value it does not know how to
handle, it loads all registered entry points and retries.
Use entry points whenever digest support for a type should “just work” after
a pip install:
as a plugin package that adds
flechesupport for a third-party library (see fleche-ase: ASE Support below), oras a library author shipping
flechesupport for your own types alongside your library.
fleche-ase: ASE Support
fleche-ase is the canonical entry point plugin. It registers digest hooks for the core types of the Atomic Simulation Environment (ASE):
ase.Atoms— digested from the cell, the periodic boundary conditions and all per-atom arrays (positions, atomic numbers, …).ase.vibrations.VibrationsData— digested from the underlying atoms, the vibrational indices and the 2D Hessian.ase.calculators.calculator.Calculator— digested from the calculator class name and itstodict()parameters; matches all calculator subclasses.
With it installed, @fleche()-decorated functions can accept and return
ASE objects with no further setup:
pip install fleche-ase
from ase.build import bulk
from fleche import fleche
@fleche()
def relax(structure): # an ase.Atoms — digested via fleche-ase
...
relax(bulk("Al"))
How Hooks Are Loaded
Entry points are loaded lazily: to avoid import overhead, nothing happens
until digest() raises
Indigestible for a value. At that point fleche:
loads all entry points registered under the group
flechewith the namedigest, andretries the digestion.
Hooks match with isinstance, so a hook registered for a base class (like
fleche-ase’s Calculator hook) also covers its subclasses.
Precedence rules:
Hooks registered manually with
add_hook()always take priority over entry point hooks; anINFOmessage is logged when one shadows the other.If multiple entry points provide hooks for the same type, the first one encountered wins.
An entry point that fails to load is logged as an
ERROR(with traceback) and skipped — it never breaks digestion of other values.
Registering Your Own Entry Point
Declare the entry point in the fleche group with the name digest in
your pyproject.toml (or equivalent):
[project.entry-points."fleche"]
digest = "my_package.hooks:digest_hooks"
The entry point must resolve to one of:
a single
Hookobject,a tuple of
(Type, Callable), ora list containing any combination of the above.
These must be module-level objects, not callables that return hooks.
fleche loads the entry point with importlib.metadata.EntryPoint.load(),
which returns the object at the given path directly — it does not call it.
>>> from fleche.digest import digest, Digest, Hook
>>> class TypeA:
... def __init__(self, field1, field2):
... self.field1 = field1
... self.field2 = field2
>>> class TypeB:
... def __init__(self, relevant_field):
... self.relevant_field = relevant_field
>>> def type_a_digest(obj: TypeA) -> Digest:
... return digest((type(obj).__name__, obj.field1, obj.field2))
>>> def type_b_digest(obj: TypeB) -> Digest:
... return digest((type(obj).__name__, obj.relevant_field))
>>> digest_hooks = [
... Hook(TypeA, type_a_digest),
... (TypeB, type_b_digest),
... ]
For guidance on writing the digest functions themselves (which fields to include, avoiding collisions), see Customizing Digests.