Feed Sentiment
Feed Sentiment is an MIT-licensed Python 3.12+ package for one-shot sentiment analysis of RSS and Atom feed snapshots. It normalizes entries, scores each usable entry with VADER, and returns an explicitly defined snapshot aggregate.
Installation
python -m pip install feed-sentiment
Runtime dependencies have narrow roles: httpx performs bounded HTTP retrieval, feedparser interprets RSS and Atom, beautifulsoup4 converts summary HTML to deterministic plain text, vaderSentiment supplies the default lexical analyzer, and typer provides the CLI. The dev extra adds pytest, pytest-httpx, Ruff, mypy, and build tooling.
Python API
from feed_sentiment import analyze_feed, analyze_text
text_score = analyze_text("This release is excellent!")
snapshot = analyze_feed("https://example.com/feed.xml")
for item in snapshot.entries:
print(item.entry.title, item.sentiment.label, item.sentiment.compound)
print(snapshot.snapshot_aggregate)
analyze_entries(entries, *, analyzer=None) analyzes already normalized entries. All public operations return package-owned typed dataclasses; analyzer and parser implementation objects do not leak through the API. A custom analyzer can implement the public SentimentAnalyzer protocol.
CLI
feed-sentiment analyze https://example.com/feed.xml
feed-sentiment analyze https://example.com/feed.xml --format json
feed-sentiment analyze http://intranet/feed.xml --allow-private-network
feed-sentiment --version
Normal results are written to stdout. Expected errors are concise, go to stderr, and exit 1; CLI usage errors exit 2. JSON mode emits exactly one undecorated JSON document.
Score semantics
Each entry contains VADER negative, neutral, and positive proportions in [0, 1] plus compound in [-1, 1]. Compound scores >= 0.05 are positive, scores <= -0.05 are negative, and values between those thresholds are neutral. Analyzer name and installed version (when discoverable) accompany each score. Empty text is an input error rather than an artificial neutral result.
Entry analysis text is title + "\n" + plain_text_summary when both fields exist, or the sole usable field otherwise. HTML tags and script/style content are removed, entities decoded, whitespace collapsed, and Unicode preserved. Entries with no usable text are skipped with structured warnings.
Snapshot aggregation
snapshot.snapshot_aggregate includes exactly the entries successfully analyzed in the current retrieval. Every included entry has equal weight. Negative, neutral, positive, and compound are component-wise arithmetic means calculated without intermediate rounding; the aggregate label uses the same compound thresholds. The result records included and skipped counts plus equal_entry weighting. If no entry succeeds, the aggregate is None/JSON null, and warnings explain empty, skipped, duplicate, or failed entries.
Within one snapshot, duplicate identity prefers entry ID, then canonical entry URL, then a SHA-256 fingerprint of normalized title, summary, and publication timestamp. This fallback is not a persistent monitoring identity contract.
Retrieval and security boundary
Retrieval accepts only HTTP(S), uses finite connect/read timeouts, a redirect cap, a response-size limit, explicit media-type checks, and no automatic retries. The default policy rejects embedded credentials and destinations resolving to private, loopback, link-local, multicast, unspecified, or reserved addresses, including redirect targets. Trusted applications may explicitly enable private-network feeds with RetrievalPolicy(allow_private_networks=True).
These checks are defense in depth for a reusable client, not a complete hosted-service SSRF sandbox. A service accepting untrusted URLs still needs network egress controls and DNS-rebinding defenses. Importing the package never performs network access.
Known limitations
- This release analyzes one snapshot only; it has no subscriptions, persistence,
poll_once(), history, rolling windows, or watch loop. - VADER is lexical and English-oriented; it can miss domain context, irony, and nuanced language.
- Full article pages are not fetched. Only feed titles and summaries/content are analyzed.
- Malformed XML is rejected at document level. Recoverable missing or invalid entry metadata is represented by structured warnings.
Development
python -m pip install -e ".[dev]"
python -m pytest
python -m ruff check .
python -m mypy
python -m build
python -m twine check --strict dist/*
Tests use local fixtures and controlled HTTP doubles; the normal suite does not require live public feeds.
The Quality Gates GitHub Actions workflow runs pytest on Python 3.12, 3.13, and 3.14, then runs Ruff and mypy before validating clean wheel and source-distribution installations. CI retains seven-day diagnostic artifacts named test-results-python-<version>, static-check-results, and package-validation-results. Successfully validated wheel and source-distribution files are uploaded separately as python-package-distributions for a future publishing workflow; generated dist/ files remain local/CI artifacts and are not committed to Git.
Production publishing setup and the maintainer release procedure are documented in docs/releasing.md.
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 feed_sentiment-0.1.0.tar.gz.
File metadata
- Download URL: feed_sentiment-0.1.0.tar.gz
- Upload date:
- Size: 27.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de1e98ba86bcd09f29b2cb3c8fd5e7227477bb9b7a6b57caa05b941c571970d6
|
|
| MD5 |
270ef8c6da2c079a1e5c9403efb2ea9a
|
|
| BLAKE2b-256 |
5fed24631f7833bd4ccbc5bd97bf2b9f4c4e2c6cc96775710021f404afaf8118
|
Provenance
The following attestation bundles were made for feed_sentiment-0.1.0.tar.gz:
Publisher:
publish.yml on kurtpatrickyu/feed-sentiment
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
feed_sentiment-0.1.0.tar.gz -
Subject digest:
de1e98ba86bcd09f29b2cb3c8fd5e7227477bb9b7a6b57caa05b941c571970d6 - Sigstore transparency entry: 2215182966
- Sigstore integration time:
-
Permalink:
kurtpatrickyu/feed-sentiment@ce8d3622063bb96a2e7e24de6a68f833f69910b3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/kurtpatrickyu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ce8d3622063bb96a2e7e24de6a68f833f69910b3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file feed_sentiment-0.1.0-py3-none-any.whl.
File metadata
- Download URL: feed_sentiment-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cac75bf0aa773f797e635860399f913f42f8cd2239c66495644e562b556892da
|
|
| MD5 |
73fafac10a3e85d107506a09e48743eb
|
|
| BLAKE2b-256 |
31cf9ec28f6d12f2b88673b8e00c33a2620ee46a505b93d1b3ddbb491fba03ab
|
Provenance
The following attestation bundles were made for feed_sentiment-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on kurtpatrickyu/feed-sentiment
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
feed_sentiment-0.1.0-py3-none-any.whl -
Subject digest:
cac75bf0aa773f797e635860399f913f42f8cd2239c66495644e562b556892da - Sigstore transparency entry: 2215182982
- Sigstore integration time:
-
Permalink:
kurtpatrickyu/feed-sentiment@ce8d3622063bb96a2e7e24de6a68f833f69910b3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/kurtpatrickyu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ce8d3622063bb96a2e7e24de6a68f833f69910b3 -
Trigger Event:
release
-
Statement type: