Quickstart ========== Installation ------------ .. code-block:: bash pip install fwirl fwirl requires a running `RabbitMQ `_ instance only when using the :ref:`server mode ` or the remote :mod:`fwirl.api` / :ref:`CLI `. Stand-alone scripted usage (calling :meth:`~fwirl.AssetGraph.build` and :meth:`~fwirl.AssetGraph.refresh` directly) works without RabbitMQ. Defining assets --------------- Subclass :class:`~fwirl.Asset` and implement two async methods: * :meth:`~fwirl.Asset.timestamp` — return when the asset was last produced, or :attr:`~fwirl.AssetStatus.Unavailable` if it does not exist yet. * :meth:`~fwirl.Asset.build` — produce (or re-produce) the asset. .. code-block:: python import fwirl import pendulum as plm class MyAsset(fwirl.Asset): """A simple in-memory asset.""" def __init__(self, key, dependencies): self._built = False super().__init__(key, dependencies) async def timestamp(self): if self._built: return self._ts return fwirl.AssetStatus.Unavailable async def build(self): # ... do the actual work here ... self._built = True self._ts = plm.now() Building a graph ---------------- Create an :class:`~fwirl.AssetGraph`, add assets, and call :meth:`~fwirl.AssetGraph.build`: .. code-block:: python g = fwirl.AssetGraph("my_graph") root = MyAsset("root", []) child = MyAsset("child", [root]) leaf = MyAsset("leaf", [child]) g.add_assets([leaf]) # dependencies are discovered automatically g.build() # builds root → child → leaf in order You can also call :meth:`~fwirl.AssetGraph.refresh` first to check which assets are stale without rebuilding them, then call :meth:`~fwirl.AssetGraph.build` to rebuild only those that need it. Scheduling ---------- Attach recurring schedules with :meth:`~fwirl.AssetGraph.schedule`: .. code-block:: python # Refresh all asset statuses every minute, then build stale ones every hour g.schedule("refresh_minutely", "refresh", "* * * * *") g.schedule("build_hourly", "build", "0 * * * *") .. _server-mode: Server mode ----------- Call :meth:`~fwirl.AssetGraph.run` to start the graph as a long-running server that listens for commands on RabbitMQ: .. code-block:: python g.run() # blocks; use Ctrl-C or fwirl shutdown to stop While the server is running you can control it from another process using the :mod:`fwirl.api` module or the :ref:`fwirl CLI `. External assets --------------- Use :class:`~fwirl.ExternalAsset` for data that can change outside fwirl's control (e.g. a file written by a third party). fwirl polls the value at a configurable interval and marks the asset stale when the value changes: .. code-block:: python import pendulum as plm class FileExternalAsset(fwirl.ExternalAsset): def __init__(self, key, path, dependencies): self._path = path super().__init__(key, dependencies, min_polling_interval=plm.duration(minutes=5)) async def get(self): with open(self._path) as f: return f.read() def diff(self, val): return val != self._cached_val Managed resources ----------------- Attach shared resources (database connections, hardware locks, …) to assets via the ``resources`` constructor argument. fwirl calls :meth:`~fwirl.Resource.init` before a build pass and :meth:`~fwirl.Resource.close` afterwards: .. code-block:: python class DBConnection(fwirl.Resource): def init(self): self.conn = connect_to_db() def close(self): self.conn.close() db = DBConnection("main_db") asset = MyDBAsset("db_asset", [], resources=[db]) Notifications ------------- Pass a ``notifiers`` mapping to :class:`~fwirl.AssetGraph` to receive alerts through any service supported by the `notifiers `_ library: .. code-block:: python g = fwirl.AssetGraph("my_graph", notifiers={ "slack": { "params": {"webhook_url": "https://hooks.slack.com/..."}, "level": "ERROR", } })