diff --git a/.env.example b/.env.example index 0bef205..d5d5a38 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -########################################################################### +############################################################################### # Configurazioni per i modelli di linguaggio ############################################################################### @@ -22,3 +22,10 @@ CRYPTOCOMPARE_API_KEY= # Binance API per Market Agent (alternativa) BINANCE_API_KEY= BINANCE_API_SECRET= + +############################################################################### +# Configurazioni per gli agenti di notizie +############################################################################### +# Ottenibile da: https://newsapi.org/docs +NEWS_API_KEY= + diff --git a/pyproject.toml b/pyproject.toml index d7caff6..3ef7154 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,9 @@ dependencies = [ # ✅ per interagire con API di exchange di criptovalute "coinbase-advanced-py", "python-binance", + + # ✅ per interagire con API di notizie + "newsapi-python", ] [tool.pytest.ini_options] diff --git a/src/app/news/__init__.py b/src/app/news/__init__.py new file mode 100644 index 0000000..9b88ff6 --- /dev/null +++ b/src/app/news/__init__.py @@ -0,0 +1,3 @@ +from .news_api import NewsAPI + +__all__ = ["NewsAPI"] \ No newline at end of file diff --git a/src/app/news/base.py b/src/app/news/base.py new file mode 100644 index 0000000..0391424 --- /dev/null +++ b/src/app/news/base.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + +class Article(BaseModel): + source: str = "" + time: str = "" + title: str = "" + description: str = "" + diff --git a/src/app/news/news_api.py b/src/app/news/news_api.py new file mode 100644 index 0000000..54b210a --- /dev/null +++ b/src/app/news/news_api.py @@ -0,0 +1,34 @@ +import os +import newsapi +from .base import Article + + +def result_to_article(result: dict) -> Article: + article = Article() + article.source = result.get("source", {}).get("name", "") + article.time = result.get("publishedAt", "") + article.title = result.get("title", "") + article.description = result.get("description", "") + return article + +class NewsAPI: + def __init__(self): + api_key = os.getenv("NEWS_API_KEY") + assert api_key is not None, "NEWS_API_KEY environment variable not set" + + self.client = newsapi.NewsApiClient(api_key=api_key) + self.category = "business" + self.language = "en" + self.page_size = 100 + + def get_top_headlines(self, query:str, total:int=100) -> list[Article]: + page_size = min(self.page_size, total) + pages = (total // page_size) + (1 if total % page_size > 0 else 0) + articles = [] + for page in range(1, pages + 1): + headlines = self.client.get_top_headlines(category=self.category, language=self.language, page_size=page_size, page=page) + results = [result_to_article(article) for article in headlines.get("articles", [])] + articles.extend(results) + return articles + + diff --git a/tests/api/test_news_api.py b/tests/api/test_news_api.py new file mode 100644 index 0000000..99a3179 --- /dev/null +++ b/tests/api/test_news_api.py @@ -0,0 +1,19 @@ +from app.news import NewsAPI + +class TestNewsAPI: + + def test_news_api_initialization(self): + news_api = NewsAPI() + assert news_api.client is not None + + def test_news_api_get_top_headlines(self): + news_api = NewsAPI() + articles = news_api.get_top_headlines(query="crypto", total=2) + assert isinstance(articles, list) + assert len(articles) == 2 + for article in articles: + assert hasattr(article, 'source') + assert hasattr(article, 'time') + assert hasattr(article, 'title') + assert hasattr(article, 'description') + diff --git a/tests/conftest.py b/tests/conftest.py index 2b6c16f..f2601b1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,6 +20,7 @@ def pytest_configure(config:pytest.Config): ("gemini", "marks tests that use Gemini model"), ("ollama_gpt", "marks tests that use Ollama GPT model"), ("ollama_qwen", "marks tests that use Ollama Qwen model"), + ("news", "marks tests that use news"), ] for marker in markers: line = f"{marker[0]}: {marker[1]}" @@ -37,6 +38,7 @@ def pytest_collection_modifyitems(config, items): "gemini": pytest.mark.gemini, "ollama_gpt": pytest.mark.ollama_gpt, "ollama_qwen": pytest.mark.ollama_qwen, + "news": pytest.mark.news, } for item in items: diff --git a/uv.lock b/uv.lock index 646299f..26356c0 100644 --- a/uv.lock +++ b/uv.lock @@ -686,6 +686,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, ] +[[package]] +name = "newsapi-python" +version = "0.2.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/4b/12fb9495211fc5a6d3a96968759c1a48444124a1654aaf65d0de80b46794/newsapi-python-0.2.7.tar.gz", hash = "sha256:a4b66d5dd9892198cdaa476f7542f2625cdd218e5e3121c8f880b2ace717a3c2", size = 7485, upload-time = "2023-03-02T13:15:35.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/47/e3b099102f0c826d37841d2266e19f1568dcf58ba86e4c6948e2a124f91d/newsapi_python-0.2.7-py2.py3-none-any.whl", hash = "sha256:11d34013a24d92ca7b7cbdac84ed2d504862b1e22467bc2a9a6913a70962318e", size = 7942, upload-time = "2023-03-02T13:15:34.475Z" }, +] + [[package]] name = "numpy" version = "2.3.3" @@ -1298,6 +1310,7 @@ dependencies = [ { name = "dotenv" }, { name = "google-genai" }, { name = "gradio" }, + { name = "newsapi-python" }, { name = "ollama" }, { name = "pytest" }, { name = "python-binance" }, @@ -1310,6 +1323,7 @@ requires-dist = [ { name = "dotenv" }, { name = "google-genai" }, { name = "gradio" }, + { name = "newsapi-python" }, { name = "ollama" }, { name = "pytest" }, { name = "python-binance" },