Skip to main content

title: "FeedSpine README" type: readme status: active tags: [feed-spine, pipeline, data-model, deduplication, medallion] created: 2025-01-15 updated: 2026-04-12

feedspine

Storage-agnostic feed capture with automatic deduplication, sighting history, and medallion architecture.

Version Python 3.12+ License: MIT Tests Ruff

Quick Start · Architecture · Adapters · Storage · API · Examples


What Is FeedSpine?

FeedSpine is a Python framework for collecting structured data from feeds — RSS, JSON APIs, CSV files, SEC EDGAR, financial data providers — and storing it with automatic deduplication, version tracking, and quality-layer promotion.

Every record is identified by a normalized natural key and a SHA-256 content hash. Collect the same feed a thousand times — each item is stored exactly once, with a full sighting history of when and where it was seen.

When to use it

Use case FeedSpine?
Collect from RSS / JSON / CSV / file feeds with automatic dedup
Promote records through quality layers (Bronze → Silver → Gold)
Track sighting history — "when did each source last see this item?"
Swap storage (Memory ↔ SQLite ↔ DuckDB ↔ Postgres) without code changes
Enrich records with entity resolution, metadata, or custom logic
Full web scraping or browser automation

Quick Start

Install

uv add feedspine                      # Core
uv add "feedspine[duckdb]"            # + DuckDB analytical storage
uv add "feedspine[api]"               # + FastAPI REST server
uv add "feedspine[elasticsearch]"     # + Elasticsearch search
uv add "feedspine[entity]"            # + Entity resolution
uv add "feedspine[all]"               # Everything

Collect and deduplicate in 10 lines

import asyncio
from feedspine import create_feed_spine, MemoryStorage, RSSFeedAdapter

async def main():
    storage = MemoryStorage()
    app = create_feed_spine(storage)
    app.register_feed(RSSFeedAdapter(name="news", url="https://news.ycombinator.com/rss"))

    # First run — all items are new
    outcome = await app.collection_service.run_collection("news")
    print(f"New: {outcome.stats.new}, Duplicates: {outcome.stats.duplicates}")

    # Second run — duplicates detected automatically
    outcome = await app.collection_service.run_collection("news")
    print(f"New: {outcome.stats.new}, Duplicates: {outcome.stats.duplicates}")

asyncio.run(main())

Or from the CLI

uv run feedspine collect run --feed news
uv run feedspine feeds list-types        # Show available adapters
uv run feedspine health summary          # Feed health (RAG status)

Architecture

                    ┌──────────────────────────────────────────────┐
                    │              PRESENTATION LAYER              │
                    │  CLI (Typer) · REST API (FastAPI) · MCP      │
                    │  Thin wrappers — delegate to Ops layer       │
                    └──────────────────┬───────────────────────────┘
                                       │
                    ┌──────────────────▼───────────────────────────┐
                    │                OPS LAYER                     │
                    │  OperationContext → OperationResult[T]       │
                    │  query · feed · enrich · schedules · runs    │
                    │  Pure business logic — no transport imports  │
                    └──────────────────┬───────────────────────────┘
                                       │
                    ┌──────────────────▼───────────────────────────┐
                    │             PIPELINE LAYER                   │
                    │  RecordCandidate → dedup → Record + Sighting │
                    │  stages · runner · stats · dedup             │
                    └──────────────────┬───────────────────────────┘
                                       │
┌───────────┐       ┌──────────────────▼───────────────────────────┐
│  Sources   │──────▶│            STORAGE LAYER                    │
│ RSS · JSON │       │  Protocols: StorageBackend, SearchBackend   │
│ CSV · File │       │  Repository pattern + dialect abstraction   │
│ SEC EDGAR  │       ├─────────┬──────────┬──────────┬─────────────┤
│ Polygon.io │       │ Memory  │  SQLite  │  DuckDB  │  PostgreSQL │
└───────────┘       └─────────┴──────────┴──────────┴─────────────┘

Core data flow

  1. FeedAdapter fetches raw data from a source and yields RecordCandidate objects
  2. Pipeline deduplicates each candidate (natural key + content hash), creating or updating a Record
  3. Sighting is logged for every observation — full audit trail of when and where each item was seen
  4. Enricher can promote records through medallion layers and add metadata
  5. StorageBackend persists everything — swap backends without changing pipeline code

Key primitives

Primitive Purpose
RecordCandidate Raw input from an adapter. Content hash computed automatically (SHA-256).
Record Stored item with natural_key, content_hash, layer, version tracking, timestamps.
Sighting Observation audit trail — every time a record is seen, from any source.
Layer Quality tier: BRONZE (raw) → SILVER (validated) → GOLD (enriched).
Pipeline Core processing engine: candidate → dedup → record + sighting.
FeedSpineApp Application object created by create_feed_spine() — holds storage, services, feeds.
CollectionOutcome Result of a collection run with stats (processed, new, duplicates, errors).
OperationContext Context for ops-layer functions: storage, search, request_id, caller, dry_run.
OperationResult[T] Typed success/failure envelope returned by all ops functions.

Built-in Feed Adapters

Adapter Source Natural Key
RSSFeedAdapter RSS 2.0 and Atom feeds Entry GUID or link
JSONFeedAdapter JSON API endpoints with dot-notation path mapping Configurable field
CSVFeedAdapter Local or HTTP CSV/TSV with composite key support Configurable column(s)
FileFeedAdapter File-based feeds with content hash change detection File path
SECEdgarFilingAdapter SEC EDGAR filing submissions API Accession number
PolygonEarningsAdapter Polygon.io earnings calendar Ticker + fiscal period

All adapters implement the FeedAdapter protocol — a @runtime_checkable interface with fetch(), initialize(), and close() methods. Write your own adapter in ~30 lines.


Storage Backends

Backend Best For Install
MemoryStorage Testing, development, prototyping Included
SQLiteStorage Single-user, local dev, small-to-medium datasets feedspine[sqlalchemy]
DuckDBStorage Analytical queries, time-series, Parquet export feedspine[duckdb]
PostgresStorage Multi-user production, large datasets, concurrent access feedspine[postgres]

All backends implement the StorageBackend protocol — CRUD, batch operations, natural-key lookup for dedup, sighting tracking, and query with filtering/pagination.

# Swap storage in one line — pipeline code stays the same
from feedspine.storage.backends.duckdb import DuckDBStorage

storage = DuckDBStorage("feeds.duckdb")
app = create_feed_spine(storage)

Enrichment

Enrichers transform records and promote them through medallion layers:

Enricher Purpose Install
PassthroughEnricher Layer promotion without data changes Included
MetadataEnricher Add custom fields to record metadata Included
EntityEnricher Entity resolution (CIK/ticker/name lookup) feedspine[entity]

Enrichment is orchestrated through FeedEnrichmentWorker with batch support.


Protocols

FeedSpine defines @runtime_checkable protocols for every extension point:

Protocol Module Purpose
StorageBackend protocols.storage Record persistence (CRUD, query, batch, sightings)
RecordStore protocols.storage Record-specific storage operations
SightingStore protocols.storage Sighting tracking and queries
StorageLifecycle protocols.storage initialize() + close() lifecycle
FeedAdapter protocols.feed Feed source (fetch, initialize, close)
Enricher protocols.enricher Single-record enrichment
BatchEnricher protocols.enricher Batch enrichment
SearchBackend protocols.search Full-text search (index, search, delete)
RunLogStore protocols.run_log Pipeline run event logging
FetchContextStore protocols.fetch_context HTTP ETag/Last-Modified conditional fetching state
BlobStorage protocols.blob Binary file storage
Cache protocols.cache Async get/set/delete with TTL
ProgressReporter protocols.progress Operation monitoring with ETA
MessageQueue protocols.queue Pub/sub messaging
CollectionStrategy protocols.strategy Multi-source optimization

Implement any protocol to extend FeedSpine — no subclassing, no registration boilerplate.


REST API / CLI / MCP

FeedSpine ships with three transport layers. All delegate to the same ops/ business logic.

FastAPI REST API

uv run feedspine api serve --port 11300
# → OpenAPI docs at http://localhost:11300/docs

15 route modules: records, feeds, sightings, search, enrichment, health, metrics, stats, timeline, export, schedules, syndication (RSS/OPML), observations, runs, storage, and collection.

Typer CLI

uv run feedspine collect run --feed sec-filings     # Collect from a feed
uv run feedspine feeds list-types                   # List available adapters
uv run feedspine feeds list                         # List configured feeds
uv run feedspine health summary                     # Feed health (RAG: Red/Amber/Green)
uv run feedspine stats summary                      # Record counts, layer distribution
uv run feedspine query records --limit 10           # Query stored records
uv run feedspine export json output.json            # Export to JSON/CSV/Parquet
uv run feedspine info                               # System info

MCP Server (Model Context Protocol)

13 tools for LLM integration — feed collection, enrichment, timeline queries, search, health, and storage stats:

uv run feedspine-mcp                                # Start MCP server (stdio)

Search

Backend Features Install
MemorySearch Keyword search (linear scan, dev/testing) Included
ElasticsearchSearch Distributed full-text, relevance scoring, highlighting, aggregations feedspine[elasticsearch]

Both implement the SearchBackend protocol with index(), search(), delete(), exists(), and initialize().


Examples

25 runnable examples across 7 categories:

Category Examples Highlights
Getting Started 2 Quickstart, multi-feed collection
Storage 2 DuckDB persistence, data types
Domain Feeds 1 SEC EDGAR filing monitor
Operations 11 Tracking, enrichment, scheduling, health, stats, export
Earnings 7 Calendar API, CLI, REST, WebSocket, full workflow
API 3 Unified timeline, RSS/Atom syndication, export formats
CLI 1 CLI command examples
uv run python examples/01_getting_started/01_quickstart.py
uv run python examples/run_all.py                          # Run all 25

Development

uv sync --dev                # Install all dependencies
uv run pytest                # 1217 tests
uv run ruff check .          # Lint
uv run ruff format .         # Format
uv run mypy src              # Type check
uv run mkdocs serve          # Local docs site

Project stats

Metric Value
Source files ~190
Test files ~96
Source LOC ~35,000
Tests 1,217 passed, 23 skipped

Stability

Aspect Status
Version 0.3.0
Python ≥ 3.12
API stability v0.x — API may change between minor versions
License MIT

Contributing

See CONTRIBUTING.md.

License

MIT — see LICENSE.

Release files for feed-spine 0.3.0

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

Source distribution (sdist)

Source distribution for feed-spine 0.3.0
File Size Uploaded
feed_spine-0.3.0.tar.gz 578.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for feed-spine 0.3.0
File Interpreter ABI Platform
feed_spine-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 925.0 kB

Release files / feed_spine-0.3.0.tar.gz

Download URL feed_spine-0.3.0.tar.gz
Size 578.4 kB
Tags Source
SHA-256 checksum
How to use checksums
a920ce8c2889b1983c9d33280c1442287cae85002a731ac2b879e4bd7dc14bb8
BLAKE2b-256 checksum
How to use checksums
ad885c167031c9f4aa966afff85829b5dbd5e049ef1d115a5318c8cf81e3ac9d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 13, 2026.

Transparency log

Release files / feed_spine-0.3.0-py3-none-any.whl

Download URL feed_spine-0.3.0-py3-none-any.whl
Size 346.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b495f59c9b4b22dc4e07826ea2a2618d54d02a6522dd9817125f569e0017de68
BLAKE2b-256 checksum
How to use checksums
cce861f92438a6076873441ca37a7ee30ee8daf5b308de46cfe5333413894d4a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 13, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

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