diff --git a/pyproject.toml b/pyproject.toml index b88acb28..43d8cc55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,7 @@ fix = [ ] sentiment = [ "textblob>=0.17.1", + "nltk>=3.10.3", "vaderSentiment>=3.3.2", "transformers>=4.21.0", "torch>=1.12.0", diff --git a/tests/test_sentiment_textblob.py b/tests/test_sentiment_textblob.py new file mode 100644 index 00000000..6684bfde --- /dev/null +++ b/tests/test_sentiment_textblob.py @@ -0,0 +1,33 @@ +"""Optional TextBlob smoke coverage without downloaded corpora or network access.""" + +import socket + +import pytest + + +@pytest.mark.parametrize( + ("text", "direction"), + [("I love this excellent result.", 1), ("I hate this terrible result.", -1)], +) +def test_textblob_sentiment_without_network_or_corpora(monkeypatch, text, direction): + def forbid_network(*args, **kwargs): + raise AssertionError("Sentiment analysis must not access the network or download corpora") + + monkeypatch.setattr(socket.socket, "connect", forbid_network) + monkeypatch.setattr(socket.socket, "connect_ex", forbid_network) + monkeypatch.setattr(socket, "create_connection", forbid_network) + monkeypatch.setattr(socket, "getaddrinfo", forbid_network) + + nltk = pytest.importorskip("nltk") + monkeypatch.setattr(nltk.data, "path", []) + monkeypatch.setattr(nltk, "download", forbid_network) + pytest.importorskip("textblob") + + from neural.analysis.sentiment import SentimentAnalyzer, SentimentEngine + + score = SentimentAnalyzer(engine=SentimentEngine.TEXTBLOB).analyze_text(text) + + assert "textblob" in score.metadata["engines_used"] + assert score.engine_used is SentimentEngine.TEXTBLOB + assert 0 < direction * score.overall_score <= 1 + assert 0 <= score.subjectivity <= 1 diff --git a/uv.lock b/uv.lock index 73195b16..b42996c8 100644 --- a/uv.lock +++ b/uv.lock @@ -710,6 +710,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/02/59a5bc738a09def0b49aea0e460bdf97f65206d0d041246147cf6207e69c/cuda_pathfinder-1.4.1-py3-none-any.whl", hash = "sha256:40793006082de88e0950753655e55558a446bed9a7d9d0bcb48b2506d50ed82a", size = 43903, upload-time = "2026-03-06T21:05:24.372Z" }, ] +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + [[package]] name = "docker" version = "7.1.0" @@ -1767,6 +1776,7 @@ fix = [ { name = "simplefix" }, ] sentiment = [ + { name = "nltk" }, { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "textblob" }, @@ -1793,6 +1803,7 @@ requires-dist = [ { name = "jsonschema", extras = ["format-nongpl"], specifier = ">=4.23.0" }, { name = "kalshi-python", marker = "extra == 'trading'", specifier = ">=2.1.4,<3" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.4.1" }, + { name = "nltk", marker = "extra == 'sentiment'", specifier = ">=3.10.3" }, { name = "numpy", specifier = ">=1.24.0,<2.0" }, { name = "pandas", specifier = ">=2.0.0" }, { name = "plotly", marker = "extra == 'analysis'", specifier = ">=5.15.0" }, @@ -1857,17 +1868,18 @@ wheels = [ [[package]] name = "nltk" -version = "3.9.3" +version = "3.10.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, + { name = "defusedxml" }, { name = "joblib" }, { name = "regex" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/8f/915e1c12df07c70ed779d18ab83d065718a926e70d3ea33eb0cd66ffb7c0/nltk-3.9.3.tar.gz", hash = "sha256:cb5945d6424a98d694c2b9a0264519fab4363711065a46aa0ae7a2195b92e71f", size = 2923673, upload-time = "2026-02-24T12:05:53.833Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/e6/fe51d2bb1a3b446f59c5c8165999a9fee208bc346af90a7cbf7657bc0d75/nltk-3.10.3.tar.gz", hash = "sha256:bb9327a461c3811c2fa4900e03840401f2126adfb30c0072827c433bd2444ea4", size = 5137152, upload-time = "2026-08-12T23:46:37.258Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/7e/9af5a710a1236e4772de8dfcc6af942a561327bb9f42b5b4a24d0cf100fd/nltk-3.9.3-py3-none-any.whl", hash = "sha256:60b3db6e9995b3dd976b1f0fa7dec22069b2677e759c28eb69b62ddd44870522", size = 1525385, upload-time = "2026-02-24T12:05:46.54Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6d/ebd2af4640b12168fdf0cb74b6118df2f32a2f62ec7e0c06fbfd80706639/nltk-3.10.3-py3-none-any.whl", hash = "sha256:ff9598a8e20518ee0d557745890cc4435b9578489e2dcbc69c4f81fa060caf7c", size = 1798643, upload-time = "2026-08-12T23:44:13.478Z" }, ] [[package]]