A composable scraper pipeline built around the medallion architecture (bronze / silver / gold).
Project description
Medallion
A scraper platform running hundreds of processing steps in Python mandates minimizing maintenance cost for updating and deploying processing steps, and guaranteeing availability of the output data for internal users in 0.5 seconds or less, especially in the case of a thundering herd causing an explosion in the amount of data to process at any point in time.
Medallion is a Python library that reduces the code developers need to change to update processing steps to the mere business logic. It deploys scrapers as microservices, isolating deployments and allowing for seamless re-routing of data traffic after deployment. It exposes the output of each processing step directly as a stream of data that internal users can opt–in for near-instant data availability. The output of each step is also written to disk for asynchronous inspection. Medallion manages thundering herds by design, because the processing–step microservices are inherently stateless, allowing you to scale horizontally as needed.
Why Medallion
Building a scraper platform that stays cheap to maintain and serves data in 0.5 seconds or less usually means gluing together a message broker, a worker framework, an archival sink, a schema layer, a local emulator, and a pile of deployment YAML. Medallion collapses that stack into a single config-driven library so the only thing you actually write is your business logic.
-
Only the business logic changes. Updating a processing step means editing one
extract()ortransform()method. Queues, storage, wiring, schemas, and deployment artifacts are derived fromconfig.yml— there is no plumbing to touch when you ship a change. -
Sub-second availability of raw source data. Every step exposes its output as a live stream that internal users can subscribe to directly. Internal users don't have to wait for the entire pipeline to finish to get access to the data, if they prefer instant availability instead of more complex queries on the final output. This also means that if a downstream step is slow or broken, the raw source data is still available to internal users without having to wait for the entire pipeline to be fixed.
-
Thundering herds handled by design. Processing-step microservices are stateless by construction, so a sudden explosion in input volume is absorbed by scaling steps horizontally rather than by tuning or babysitting the system.
-
Every intermediate stream is archived automatically. A Store is attached to each queue with no code on your part, writing output to disk (or a bucket) for asynchronous inspection and replay — without requiring a replay-capable broker. This doubles as a precise replay tool: a developer who changes one step can re-process exactly the same archived input without re-running the entire pipeline — avoiding the risk of costly re-processing or that upstream changes or non-determinism produce different input.
-
Typed pipelines, end to end. Queues are typed and every processor's
In/Outmust match the queues it reads from and writes to, so topology mistakes are caught as early as possible. -
Isolated, independently deployable steps. Each extractor and transformer runs as its own microservice, so deployments are isolated and data traffic can be re-routed seamlessly after a deploy.
-
One config, local and prod parity. From a single
config.yml, Medallion generates a runnable Docker Compose cluster, debug configurations for VS Code, and a Google Cloud Run fleet of services with Pub/Sub as broker and storage bucket — the same topology you run locally is the one you deploy. If you prefer a different cloud provider than GCP, you can easily swap it, you only need to implement a handful of functions for the Queue and BlobStore interfaces for your provider of choice.
In total, Medallion replaces what is typically 8–10 separate components plus their glue code.
Installation
pip install medallion-pipeline
The distribution is published as medallion-pipeline; the Python import name and CLI tool name is medallion. Python 3.12+ required.
Core concepts
- Extractor — produces the initial data and sends to a
Queue. You implement a class that inherits fromBaseExtractor[Out]and implementextract(self) -> Iterable[Out]. - Transformer — consumes the previous step's output from a
Queueand sends transformed output to a different queue. SubclassBaseTransformer[In, Out]and implementtransform(self, data: Iterable[In]) -> Iterable[Out]. If you need streaming, you need to inherit fromBaseStreamingTransformer[In, Out]and implementtransform_one(self, data: In) -> Outinstead. - Queues – Queues are typed and processors (Extractor or Transformer) must match the queue's type in their corresponding
Inand/orOuttypes. The library supports a mock-in-memory queue and GCP Pub/Sub. To roll your own, implement theQueueinterface. - Store – is attached to each queue automatically – you don't need to write any code to do that. A Store reads from a queue and stores the data. The library has built-in support for storage on a local disc or GCP Storage Bucket. To roll your own, implement the
BlobStoreinterface. - Orchestration – you tie everything together in a
config.ymlfile. Here you define data types, queues, extractors and transformers, and which queues the processors read and write to.
Benchmark results
All benchmarks were run on an M4 Max with 64 GB unified memory on macOS 26.5 using Python 3.13.8. Workloads, configs, and scripts to reproduce these results live in
benchmarks/.
What we measured
| Dimension | What it tells you | How important for the user (0 not important to 10 very important) | Story points |
|---|---|---|---|
| Lines of code to a working pipeline | Maintenance and onboarding cost for scraper development | 10 | ? |
| End-to-end latency (extract → consumer) | Availability SLA under normal load | 10 | ? |
| Latency under burst (thundering herd) | Behavior when input volume explodes | 10 | ? |
| Time to redeploy a single step | Cost of shipping a change | 5 | ? |
| Throughput per step (records/sec) | Capacity per stateless replica | 6 | ? |
| Degradation under a slow step | Pipeline isolation guarantees | 10 | ? |
Lines of code to an equivalent pipeline
| Stack | LOC* | Components to operate |
|---|---|---|
| Medallion | ??? | 1 |
| Faust + custom archival + compose | ??? | ??? |
| Bytewax + custom archival + compose | ??? | ??? |
| Celery + FastAPI + broker + S3 sink (hand-rolled) | ??? | ??? |
*LOC includes source code and config files.
End-to-end latency
TODO: add timestamp sent to the output of each step. This allows any two steps in a pipeline to measure the latency between them, including the first and the last step.
Consider one message equaling 10 lines of csv data, going through a pipeline with 2 steps (Extractor → Transformer). We measure the latency between the output of the Extractor and the output of the Transformer. The extractor in our case is a mock that produces messages at a configurable rate, so we can measure latency under different loads.
| Scenario | p50 | p95 | p99 |
|---|---|---|---|
| Steady state (10 messages/sec) | ??? ms | ??? ms | ??? ms |
| Thundering herd (1000 messages/sec) | ??? ms | ??? ms | ??? ms |
Target: structured output available to internal users in 0.5 s or less.
Throughput and scaling
| Replicas per step | Throughput (records/sec) | Latency p99 |
|---|---|---|
| 1 | ??? | ??? ms |
| 4 | ??? | ??? ms |
| 16 | ??? | ??? ms |
Deployment cost
| Action | Medallion | Other |
|---|---|---|
| Redeploy a single step | ??? s | ??? s |
| Add a new transformer | ??? LOC / ??? s | ??? |
Reproducing these results
cd benchmarks
poetry install --with benchmarks
poetry run python run_benchmarks.py
Contributing
See CONTRIBUTING.md for developer notes and the release process.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file medallion_pipeline-0.2.5.tar.gz.
File metadata
- Download URL: medallion_pipeline-0.2.5.tar.gz
- Upload date:
- Size: 47.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4288a2972bc743dd1477f82d614548109bf53ae060c0f6235ddeb7c34ae992e2
|
|
| MD5 |
e7302597b48b792bba0c6f170d912c82
|
|
| BLAKE2b-256 |
6ecc9bb5d793849fa72dea7da4ca01ea29adfc0fb5c0f3f2caddd5c00dedb9cc
|
Provenance
The following attestation bundles were made for medallion_pipeline-0.2.5.tar.gz:
Publisher:
release.yml on Mcklmo/medallion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
medallion_pipeline-0.2.5.tar.gz -
Subject digest:
4288a2972bc743dd1477f82d614548109bf53ae060c0f6235ddeb7c34ae992e2 - Sigstore transparency entry: 1690999907
- Sigstore integration time:
-
Permalink:
Mcklmo/medallion@5c128f40783057bc8f5b66764401d71f464dd801 -
Branch / Tag:
refs/tags/v0.2.5 - Owner: https://github.com/Mcklmo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5c128f40783057bc8f5b66764401d71f464dd801 -
Trigger Event:
push
-
Statement type:
File details
Details for the file medallion_pipeline-0.2.5-py3-none-any.whl.
File metadata
- Download URL: medallion_pipeline-0.2.5-py3-none-any.whl
- Upload date:
- Size: 62.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b470ea70874a0847802099e188cfd8bd23e1279b0ccf16819c746e178ec6706b
|
|
| MD5 |
7199b5777a32233230a3872f858b1f44
|
|
| BLAKE2b-256 |
a8ea25c2c597020cad331cd99a61b84d23fb97c6357036e83aa8f6df4d061a84
|
Provenance
The following attestation bundles were made for medallion_pipeline-0.2.5-py3-none-any.whl:
Publisher:
release.yml on Mcklmo/medallion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
medallion_pipeline-0.2.5-py3-none-any.whl -
Subject digest:
b470ea70874a0847802099e188cfd8bd23e1279b0ccf16819c746e178ec6706b - Sigstore transparency entry: 1690999999
- Sigstore integration time:
-
Permalink:
Mcklmo/medallion@5c128f40783057bc8f5b66764401d71f464dd801 -
Branch / Tag:
refs/tags/v0.2.5 - Owner: https://github.com/Mcklmo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5c128f40783057bc8f5b66764401d71f464dd801 -
Trigger Event:
push
-
Statement type: