Skip to main content

Rhylthyme Importers

Import plugins for converting external data sources into Rhylthyme programs.

Installation

pip install rhylthyme-importers      # TheMealDB, Spoonacular, recipe sites, CookLang, protocols.io, Opentrons, Benchling, Ontime

Python 3.12 or newer. From a checkout: pip install -e ./rhylthyme-importers.

Importers read a URL, an id, or text you pass them. import_from_url() refuses a local path unless the importer was built with allow_local_files=True; the command-line tools do that, a server should not.

Quickstart: a recipe page to a picture and a live timeline

pip install rhylthyme            # this package plus the rhylthyme command and the renderer

rhylthyme import https://www.bbcgoodfood.com/recipes/classic-lasagne
#   Importing with recipe-scrapers…
#   Saved easy_classic_lasagne.json

rhylthyme render easy_classic_lasagne.json -o lasagne.png --style web --palette vivid --color-by task    # needs Node.js
rhylthyme publish easy_classic_lasagne.json --open                                                    # live timeline with timers
rhylthyme import https://www.bbcgoodfood.com/recipes/classic-lasagne --review    # a model checks the import against the page (needs rhylthyme login)

The same from Python:

from rhylthyme_importers import ImporterRegistry
from rhylthyme_timeline import render

url = "https://www.bbcgoodfood.com/recipes/classic-lasagne"
result = ImporterRegistry.find_for_url(url).import_from_url(url)
assert result.success, result.error
program = result.program                       # Rhylthyme program JSON

svg = render(program, style="web", colorBy="task")
open("lasagne.svg", "w").write(svg)

Run rhylthyme validate (or the validate_program MCP tool) on anything an importer produces before you rely on the timings: an importer reads what the source says, and sources are vague about time. --review goes further: a model reads the program against the source and reports the durations that are defaults rather than facts, steps the parser dropped, and steps chained that could overlap. It changes nothing; the findings are yours to apply.

What is here

Each importer turns one kind of source into a Rhylthyme program: parallel tracks of timed steps with shared-equipment limits, ready for rhylthyme validate, rhylthyme publish or the web app. An importer reads structure, not meaning, so what it produces is faithful to the source and mostly sequential; see Enrichment for how tracks and cross-track triggers are added afterwards.

Importer Source Input Needs
themealdb TheMealDB, a free recipe API meal id or URL nothing
spoonacular Spoonacular recipe API recipe id or URL SPOONACULAR_API_KEY
recipe-scrapers several hundred recipe websites recipe page URL nothing
cooklang CookLang .cook files raw or GitHub URL, or a file nothing
protocolsio protocols.io protocol URL or id PROTOCOLS_IO_TOKEN
opentrons Opentrons Protocol API v2 .py file or pasted source nothing
benchling Benchling protocols, workflow tasks, notebook entries protocol URL or id a Benchling API token
slidedeck PowerPoint .pptx file nothing
ontime Ontime run of show project .json, .xlsx, CSV/TSV or a pasted table nothing

rhylthyme importers prints the registered importers; rhylthyme import <url> picks one by URL, validates the program, writes <programId>.json and with --publish prints a live-timeline URL; rhylthyme search <query> -i <importer> finds things to import. (pip install rhylthyme gives you that command with this package.)

TheMealDB

Recipes from TheMealDB. No key needed.

rhylthyme search "chicken curry" -i themealdb
rhylthyme import https://www.themealdb.com/meal/52772 -o curry.json
rhylthyme import 52772 -i themealdb --publish
from rhylthyme_importers import TheMealDBImporter
result = TheMealDBImporter().import_from_url("52772")

Spoonacular

Recipes from Spoonacular, with structured ingredients, equipment and nutrition. Set SPOONACULAR_API_KEY (free tier available); without it, search returns nothing and imports fail with a message saying so.

export SPOONACULAR_API_KEY=...
rhylthyme search "pad thai" -i spoonacular
rhylthyme import https://spoonacular.com/recipes/pad-thai-716429 -o pad_thai.json
from rhylthyme_importers import SpoonacularImporter
SpoonacularImporter(api_key="...").import_from_url("716429")

Recipe sites (recipe-scrapers)

Any of the several hundred recipe websites that recipe-scrapers understands (Serious Eats, BBC Good Food, AllRecipes, NYT Cooking, ...). Pass the page URL; the importer is chosen automatically when the host is supported.

rhylthyme import https://www.seriouseats.com/the-best-chili-recipe -o chili.json

rhylthyme-sweep-supported-sites checks which of those sites still parse (for catalogue maintenance).

CookLang

Individual CookLang .cook files, from a raw URL, a GitHub blob URL (converted to raw automatically) or a local file.

rhylthyme import "https://github.com/cooklang/cookcli/blob/main/seed/Neapolitan%20Pizza.cook" -o pizza.json
rhylthyme import "Neapolitan Pizza.cook" -i cooklang     # a .cook file on disk
from rhylthyme_importers import CooklangImporter
CooklangImporter().import_from_url("https://raw.githubusercontent.com/cooklang/cookcli/main/seed/Neapolitan%20Pizza.cook")
CooklangImporter(allow_local_files=True).import_from_url("Pizza.cook")
CooklangImporter().import_from_content(open("Pizza.cook").read(), source_name="Pizza")

Timers (~{10%minutes}) become step durations; ingredients and cookware land in metadata.

CookLang Federation (bulk)

Scrape every recipe in the Cooklang Federation, about 76 GitHub repositories, and filter out placeholders. Reproducible and idempotent on re-run.

rhylthyme-import-cooklang-federation --discover                       # 1. list source repos
rhylthyme-import-cooklang-federation --import-all --staged-out /tmp/staged.json   # 2. scrape + filter
rhylthyme-import-cooklang-federation --review /tmp/staged.json        # 3. what passed, what was dropped
rhylthyme-import-cooklang-federation --refilter /tmp/staged.json      # 4. re-apply edited rules, no re-download

The staged file is a list of programs; --upload sends it to a Rhylthyme catalogue.

The filter rejects placeholder names (recipe, untitled, template, test, Copy of ...), hash-like or all-digit names, and programs with no ingredients or no steps. Short real names (Roux, Filo, Dahl, Toum) are kept.

protocols.io

Laboratory protocols from protocols.io. Needs PROTOCOLS_IO_TOKEN (from https://www.protocols.io/developers). Each protocol step keeps its source sentence on the step, so enrichment can say which words a step came from.

export PROTOCOLS_IO_TOKEN=...
rhylthyme search "western blot" -i protocolsio
rhylthyme import "https://www.protocols.io/view/western-blot-..." -o blot.json
from rhylthyme_importers import ProtocolsIOImporter
ProtocolsIOImporter(access_token="...").import_from_url("https://www.protocols.io/view/...")

Opentrons

An Opentrons Protocol API v2 .py file (OT-2 or Flex) becomes a program with one track per pipette mount or module; durations come from a static lookup table. Nothing to configure. Full feature matrix in docs/opentrons.md.

rhylthyme import https://raw.githubusercontent.com/Opentrons/Protocols/develop/protocols/007992/rna_isolation.ot2.apiv2.py
rhylthyme import path/to/protocol.py -o program.json
cat protocol.py | rhylthyme import - -i opentrons --stdout     # pasted source
from rhylthyme_importers import OpentronsImporter
OpentronsImporter().import_from_source(open("protocol.py").read())

Covers OT-2 and Flex pipettes (1-, 8- and 96-channel), the Flex gripper, and the heater-shaker, magnetic, temperature, thermocycler and absorbance modules. transfer, distribute and consolidate expand into the same low-level events the Opentrons run log emits. A protocol the stubbed simulator cannot execute falls back to a static AST parse and carries a WARNING event, which the web app shows as a banner.

Benchling

A Benchling Protocol, Workflow Task or Notebook Entry becomes a program with an instrument-aware track split. Live import needs a Benchling API token for the tenant; the web app stores one per user (encrypted) after a one-time connect, and the MCP server's import_from_source with source: "benchling" searches your library and imports by id or URL. Details, field mapping and the roadmap: docs/benchling.md, docs/benchling-field-mapping.md.

Benchling limits API access to enterprise tenants, and we don't currently have one to test with. If you have an enterprise account, please get in touch: support@rhylthyme.com. See where this stands.

from rhylthyme_importers.benchling import BenchlingImporter
importer = BenchlingImporter()
importer.search_live(tenant="acme", token="...", query="PCR")
importer.import_from_url("https://acme.benchling.com/acme/protocols/prt_abc123", token="...")

Offline, a saved Benchling API response converts too, which is how the importer is tested:

BenchlingImporter().import_from_file("protocol_response.json", tenant="acme")

Slide decks

A PowerPoint .pptx becomes a presentation timeline: one step per slide, timed from speaker-notes cues where present.

from rhylthyme_importers import SlideDeckImporter
SlideDeckImporter().import_from_url("talk.pptx")

Ontime

An Ontime rundown becomes a run of show: each group a track, each event a step at its clock time, linked events chained with afterStep, gaps and overlaps kept. Milestones become alerts on the next step. Cues, notes, end actions, timer types, colours and custom fields are kept in each step's metadata.ontime. It reads:

  • the project file Ontime saves (.json, version 4 or 3);
  • the rundown as a table: Ontime's .xlsx export or import template, a Google Sheet (its share link, from the web app or MCP), or the same table as CSV, TSV or Markdown, with Ontime's column names (time start, time end, duration, cue, title, note, link start, skip, timer type).
cue,title,time start,time end,duration,link start,timer type,note,presenter
,Keynote,,,,,group,,
2,Welcome,18:30:00,18:35:00,00:05:00,,,Walk-in music fades,Host
3,Product demo,18:35:00,19:05:00,00:30:00,x,,Two clickers on stage,CEO
,Mics live in the house,,,,,milestone,,
4,Audience Q&A,19:05:00,19:20:00,00:15:00,x,,Two roaming mics,Host
rhylthyme import show.xlsx -i ontime
from rhylthyme_importers import OntimeImporter
OntimeImporter(allow_local_files=True).import_from_url("show.json")
OntimeImporter().import_from_source(csv_text, title="Product launch")

A project with several rundowns imports the first one that has events in it; pass rundown= (an id or a title) to pick another. A full example is tests/fixtures/ontime/product-launch.csv.

Other commands

The remaining rhylthyme-* commands (rhylthyme-backfill-taxonomy, rhylthyme-clean-titles, rhylthyme-backfill-language, rhylthyme-embed-recipes, rhylthyme-detect-text-thumbs, rhylthyme-replace-broken-thumbs, rhylthyme-import-recipe-scrapers, rhylthyme-sweep-supported-sites) maintain the public catalogue on rhylthyme.com and are not needed to import anything. Their extra dependencies are the catalog extra.

Enrichment is not part of BaseImporter

An importer reads structure, not meaning, so it produces one track of steps chained head-to-tail. Splitting that into parallel tracks with cross-track triggers takes a language model, and that lives in rhylthyme-server, not here: rhylthyme_server._prompting.enrich_program runs turn 4 of the plan_schedule prompt over the program an importer has already returned, exposed as enrich: true on POST /api/import and on the MCP import_from_source tool.

Nothing in this package changes for it. BaseImporter has no enrich hook, no Anthropic dependency and no network requirement beyond the source API it already talks to; importers stay deterministic, offline- testable and free to run without an ANTHROPIC_API_KEY. Enrichment is a post-processor over ImportResult.program, opt-in per request and rate-limited, and a failed enrichment returns the importer's program unchanged.

Two conventions make an importer enrich well:

  • Keep the source sentence on the step. enrich reads step["description"] (falling back to step["notes"]) as the step's metadata.sourceSpan.quote, so the enriched program can say which words each step came from. protocols.io's importer already does this.
  • Keep stepIds stable. Enrichment preserves every imported stepId, and marks any step it adds with metadata.inferred = true; the diff between the two is how the UI tells read-from-source apart from inferred.

Python API

from rhylthyme_importers import TheMealDBImporter, ProtocolsIOImporter, ImporterRegistry

# TheMealDB
mealdb = TheMealDBImporter()
results = mealdb.search("pasta")
result = mealdb.import_from_url("52771")

if result.success:
    program = result.program
    print(f"Imported: {program['name']}")

# Protocols.io
protocolsio = ProtocolsIOImporter(access_token="your_token")
result = protocolsio.import_from_url("https://www.protocols.io/view/...")

# Auto-detect importer from URL
importer = ImporterRegistry.find_for_url("https://www.themealdb.com/meal/52771")
if importer:
    result = importer.import_from_url("https://www.themealdb.com/meal/52771")

Creating Custom Importers

Extend the BaseImporter class to create new importers:

from rhylthyme_importers import BaseImporter, ImportResult, ImporterRegistry

class MyCustomImporter(BaseImporter):
    name = "myimporter"
    description = "Import from my custom source"
    supported_domains = ["example.com"]

    def can_import(self, url_or_query: str) -> bool:
        return "example.com" in url_or_query

    def search(self, query: str) -> list:
        # Return list of {name, url, description}
        return []

    def import_from_url(self, url: str) -> ImportResult:
        # Fetch data and convert to Rhylthyme program
        program = self.create_base_program(
            name="My Program",
            description="Description",
            environment_type="kitchen",
            source_url=url,
            source_type="myimporter"
        )
        # Add tracks, steps, etc.
        return ImportResult(success=True, program=program)

# Register the importer
ImporterRegistry.register(MyCustomImporter())

Releasing

From a clean, up-to-date main, with a PyPI token in ~/.pypirc:

make release                 # next patch version
make release VERSION=0.5.0   # a specific version
DRY_RUN=1 make release       # tests, build and package check only

This runs the tests, builds and checks the package, and asks once before publishing. It then commits the version, pushes the vX.Y.Z tag, uploads to PyPI and opens the rhylthyme-server PR that requires the new version.

License

Apache License 2.0

Metadata

Release files for rhylthyme-importers 0.4.8

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rhylthyme-importers 0.4.8
File Size Uploaded Provenance Actions
rhylthyme_importers-0.4.8.tar.gz 218.5 kB Without provenance Details for rhylthyme_importers-0.4.8.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for rhylthyme-importers 0.4.8
File Interpreter ABI Platform Actions
rhylthyme_importers-0.4.8-py3-none-any.whl Python 3 none any Details for rhylthyme_importers-0.4.8-py3-none-any.whl

Total release size: 414.3 kB

Release files / rhylthyme_importers-0.4.8.tar.gz

Download URL rhylthyme_importers-0.4.8.tar.gz
Size 218.5 kB
Tags Source
SHA-256 checksum
How to use checksums
e75613b8bdff4073cefee82b6df77770d3ff09955601be724d17e4c72a977d0b
BLAKE2b-256 checksum
How to use checksums
7889d9c5b9d6de791daef0864dc1441641882a5123ad7ed4b69b2b1a7ea1c3b5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.8

Release files / rhylthyme_importers-0.4.8-py3-none-any.whl

Download URL rhylthyme_importers-0.4.8-py3-none-any.whl
Size 195.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
75ffeebed86c1697a108ffd387988fbeb263a19133c9e47d6bb1f69be912bd60
BLAKE2b-256 checksum
How to use checksums
d1520b88bec9cadb29c81e874980a1ac4e080d3cb356e090d1e1e4a44de920e1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.8

Security for rhylthyme-importers 0.4.8

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page