diff --git a/codecov/sentiment.py b/codecov/sentiment.py new file mode 100644 index 0000000000..8e26a72b88 --- /dev/null +++ b/codecov/sentiment.py @@ -0,0 +1,35 @@ +from textblob import TextBlob + + +class SentimentAnalyzer: + def __init__(self): + pass + + def get_sentiment(self, sentence: str) -> str: + polarity = self._get_polarity(sentence) + if polarity > 0: + return "positive" + elif polarity < 0: + return "negative" + else: + return "neutral" + + def get_polarity_score(self, sentence: str) -> float: + return self._get_polarity(sentence) + + def get_subjectivity_score(self, sentence: str) -> float: + return self._get_subjectivity(sentence) + + def is_neutral(self, sentence: str) -> bool: + return self.get_sentiment(sentence) == "neutral" + + def is_positive(self, sentence: str) -> bool: + return self.get_sentiment(sentence) == "positive" + + def _get_polarity(self, sentence: str) -> float: + analysis = TextBlob(sentence) + return analysis.sentiment.polarity + + def _get_subjectivity(self, sentence: str) -> float: + analysis = TextBlob(sentence) + return analysis.sentiment.subjectivity \ No newline at end of file diff --git a/codecov/tests/test-sentiment.py b/codecov/tests/test-sentiment.py new file mode 100644 index 0000000000..0cc1367859 --- /dev/null +++ b/codecov/tests/test-sentiment.py @@ -0,0 +1,76 @@ +from unittest.mock import patch + +import pytest + +from codecov.sentiment import SentimentAnalyzer + + +@pytest.fixture +def sentiment_analyzer(): + return SentimentAnalyzer() + + +@patch("codecov.sentiment.TextBlob") +def test_get_sentiment_positive(mock_textblob, sentiment_analyzer): + mock_textblob.return_value.sentiment.polarity = 0.5 + assert sentiment_analyzer.get_sentiment("Great job!") == "positive" + + +@patch("codecov.sentiment.TextBlob") +def test_get_sentiment_negative(mock_textblob, sentiment_analyzer): + mock_textblob.return_value.sentiment.polarity = -0.5 + assert sentiment_analyzer.get_sentiment("Terrible experience.") == "negative" + + +@patch("codecov.sentiment.TextBlob") +def test_get_sentiment_neutral(mock_textblob, sentiment_analyzer): + mock_textblob.return_value.sentiment.polarity = 0.0 + assert sentiment_analyzer.get_sentiment("This is a neutral statement.") == "neutral" + + +@patch("codecov.sentiment.TextBlob") +def test_get_polarity_score(mock_textblob, sentiment_analyzer): + mock_textblob.return_value.sentiment.polarity = 0.75 + assert sentiment_analyzer.get_polarity_score("Excellent work!") == 0.75 + + +@patch("codecov.sentiment.TextBlob") +def test_get_subjectivity_score(mock_textblob, sentiment_analyzer): + mock_textblob.return_value.sentiment.subjectivity = 0.6 + assert sentiment_analyzer.get_subjectivity_score("I think this is amazing!") == 0.6 + + +@patch("codecov.sentiment.TextBlob") +def test_is_neutral_true(mock_textblob, sentiment_analyzer): + mock_textblob.return_value.sentiment.polarity = 0.0 + assert sentiment_analyzer.is_neutral("This is a fact.") == True + + +@patch("codecov.sentiment.TextBlob") +def test_is_neutral_false(mock_textblob, sentiment_analyzer): + mock_textblob.return_value.sentiment.polarity = 0.5 + assert sentiment_analyzer.is_neutral("This is great!") == False + + +@patch("codecov.sentiment.TextBlob") +def test_is_positive_true(mock_textblob, sentiment_analyzer): + mock_textblob.return_value.sentiment.polarity = 0.5 + assert sentiment_analyzer.is_positive("This is wonderful!") == True + + +@patch("codecov.sentiment.TextBlob") +def test_is_positive_false(mock_textblob, sentiment_analyzer): + mock_textblob.return_value.sentiment.polarity = -0.5 + assert sentiment_analyzer.is_positive("This is awful.") == False + + +@patch("codecov.sentiment.TextBlob") +def test_private_get_polarity(mock_textblob, sentiment_analyzer): + mock_textblob.return_value.sentiment.polarity = 0.8 + assert sentiment_analyzer._get_polarity("Amazing!") == 0.8 + + +@patch("codecov.sentiment.TextBlob") +def test_private_get_subjectivity(mock_textblob, sentiment_analyzer): + mock_textblob.return_value.sentiment.subjectivity = 0.9 + assert sentiment_analyzer._get_subjectivity("I absolutely love this!") == 0.9 \ No newline at end of file diff --git a/requirements.in b/requirements.in index 7c354bad41..c4a706bfe5 100644 --- a/requirements.in +++ b/requirements.in @@ -34,6 +34,8 @@ pre-commit psycopg2 PyJWT pydantic +nltk==3.8.1 +textblob==0.17.1 pytest>=7.2.0 pytest-cov pytest-django diff --git a/requirements.txt b/requirements.txt index 6c46440655..76e1c4d614 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile +# pip-compile requirements.in # aiodataloader==0.4.0 # via -r requirements.in @@ -83,6 +83,7 @@ click==8.1.7 # click-didyoumean # click-plugins # click-repl + # nltk click-didyoumean==0.3.0 # via celery click-plugins==1.1.1 @@ -177,6 +178,7 @@ freezegun==1.1.0 # via -r requirements.in google-api-core[grpc]==2.11.1 # via + # google-api-core # google-cloud-core # google-cloud-pubsub # google-cloud-storage @@ -249,6 +251,8 @@ jmespath==0.10.0 # via # boto3 # botocore +joblib==1.4.2 + # via nltk jsonschema==4.14.0 # via drf-spectacular kombu==5.3.6 @@ -263,6 +267,10 @@ monotonic==1.5 # via analytics-python multidict==4.7.6 # via yarl +nltk==3.8.1 + # via + # -r requirements.in + # textblob nodeenv==1.5.0 # via pre-commit oauth2==1.9.0.post1 @@ -307,8 +315,6 @@ packaging==24.1 # pytest pluggy==1.5.0 # via pytest -polars==1.12.0 - # via -r requirements.in pre-commit==2.11.1 # via -r requirements.in prometheus-client==0.17.1 @@ -399,7 +405,9 @@ redis==4.4.4 # python-redis-lock # shared regex==2023.12.25 - # via -r requirements.in + # via + # -r requirements.in + # nltk requests==2.32.3 # via # -r requirements.in @@ -410,7 +418,9 @@ requests==2.32.3 # shared # stripe rfc3986[idna2008]==1.4.0 - # via httpx + # via + # httpx + # rfc3986 rsa==4.7.2 # via google-auth s3transfer==0.5.0 @@ -421,7 +431,7 @@ sentry-sdk[celery]==2.13.0 # shared setproctitle==1.1.10 # via -r requirements.in -shared @ https://github.com/codecov/shared/archive/4db8f12c1a4ad43cedb1a965d2fa8cd8138fc2b3.tar.gz +shared @ https://github.com/codecov/shared/archive/067b2e4ec72cdf1b3213306ea8aaaccbb374aecc.tar.gz # via -r requirements.in simplejson==3.17.2 # via -r requirements.in @@ -457,10 +467,14 @@ stripe==9.6.0 # via -r requirements.in text-unidecode==1.3 # via faker +textblob==0.17.1 + # via -r requirements.in tlslite-ng==0.8.0b1 # via shared toml==0.10.2 # via pre-commit +tqdm==4.66.6 + # via nltk typing==3.7.4.3 # via shared typing-extensions==4.6.2 @@ -514,4 +528,4 @@ zipp==3.19.2 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -# setuptools +# setuptools \ No newline at end of file