Source code for fastrag.ingestion.steps.fetching.base
"""The ``Fetcher`` interface: ``docs/specification/subclasses.md``, verbatim."""
from __future__ import annotations
from abc import ABC
from typing import Any, Self
from fastrag.exceptions import ConfigurationError
from fastrag.ingestion.filters import Filter
from fastrag.ingestion.models import Source
from fastrag.ingestion.steps.base import SourceStep
[docs]
class Fetcher(SourceStep, ABC):
"""Base class for leaf-level data fetchers that extract raw content from a single protocol/medium.
Every event a fetcher emits is labelled ``"Fetching"`` — restated here
rather than only inherited from :class:`~fastrag.ingestion.steps.base.SourceStep`
so the family's own module states the stage it belongs to.
Fetchers that discover URLs (crawlers) can delegate specific ones to other
fetchers with :meth:`route`, e.g. YouTube links to a ``YouTubeFetcher``.
"""
event_name = "Fetching"
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize the fetcher with no routes registered."""
super().__init__(*args, **kwargs)
self.routes: list[tuple[Filter[Source], Fetcher]] = []
[docs]
def route(self, filter_rule: Filter[Source], fetcher: Fetcher) -> Self:
"""Delegate the URLs matching ``filter_rule`` to ``fetcher``.
The filter sees a content-less ``Source`` carrying only the URL, so
URL-based filters such as :class:`~fastrag.ingestion.filters.URLFilter`
apply. Routes are tried in registration order, and the first match
wins.
Args:
filter_rule: Predicate over the URL about to be fetched.
fetcher: Fetcher implementing :meth:`fetch`, used on match.
Returns:
Self, for fluent chaining.
"""
self.routes.append((filter_rule, fetcher))
return self
[docs]
def route_for(self, uri: str) -> Fetcher | None:
"""The fetcher routed to ``uri``, or ``None`` to fetch it normally."""
probe = Source(id=uri, uri=uri, content=b"", content_type="", content_hash="")
for filter_rule, fetcher in self.routes:
if filter_rule.matches(probe):
return fetcher
return None
[docs]
def canonical_uri(self, uri: str) -> str:
"""Identity under which ``uri`` is fetched, to deduplicate equivalent spellings."""
return uri
[docs]
async def fetch(self, uri: str) -> list[Source]:
"""Fetch one URL in-process, uncached. Needed to be a :meth:`route` target.
Raises:
fastrag.exceptions.ConfigurationError: This fetcher cannot fetch
an arbitrary URL.
"""
raise ConfigurationError(
f"{type(self).__name__} cannot be used as a route target"
)
[docs]
def routes_config(self) -> list[dict[str, Any]]:
"""Routed fetchers' fingerprints, for inclusion in a crawler's ``config()``."""
return [
{"fetcher": type(fetcher).__name__, "config": dict(fetcher.config())}
for _, fetcher in self.routes
]