Add Reddit API wrapper and related tests; update environment configuration
This commit is contained in:
16
.env.example
16
.env.example
@@ -10,11 +10,11 @@ GOOGLE_API_KEY=
|
||||
# Configurazioni per gli agenti di mercato
|
||||
###############################################################################
|
||||
|
||||
# Ottenibili da: https://portal.cdp.coinbase.com/access/api
|
||||
# https://portal.cdp.coinbase.com/access/api
|
||||
CDP_API_KEY_NAME=
|
||||
CDP_API_PRIVATE_KEY=
|
||||
|
||||
# Ottenibile da: https://www.cryptocompare.com/cryptopian/api-keys
|
||||
# https://www.cryptocompare.com/cryptopian/api-keys
|
||||
CRYPTOCOMPARE_API_KEY=
|
||||
|
||||
# Binance API per Market Agent (alternativa)
|
||||
@@ -25,8 +25,16 @@ BINANCE_API_SECRET=
|
||||
# Configurazioni per gli agenti di notizie
|
||||
###############################################################################
|
||||
|
||||
# Ottenibile da: https://newsapi.org/docs
|
||||
# https://newsapi.org/docs
|
||||
NEWS_API_KEY=
|
||||
|
||||
# Ottenibile da: https://cryptopanic.com/developers/api/
|
||||
# https://cryptopanic.com/developers/api/
|
||||
CRYPTOPANIC_API_KEY=
|
||||
|
||||
###############################################################################
|
||||
# Configurazioni per API di social media
|
||||
###############################################################################
|
||||
|
||||
# https://www.reddit.com/prefs/apps
|
||||
REDDIT_API_CLIENT_ID=
|
||||
REDDIT_API_CLIENT_SECRET=
|
||||
|
||||
@@ -10,30 +10,30 @@ requires-python = "==3.12.*"
|
||||
# Per ogni roba ho fatto un commento per evitare di dimenticarmi cosa fa chi.
|
||||
# Inoltre ho messo una emoji per indicare se è raccomandato o meno.
|
||||
dependencies = [
|
||||
# ✅ per i test
|
||||
"pytest",
|
||||
# ✅ per gestire variabili d'ambiente (generalmente API keys od opzioni)
|
||||
"dotenv",
|
||||
# ✅ per fare una UI web semplice con input e output
|
||||
"gradio",
|
||||
"pytest", # Test
|
||||
"dotenv", # Gestire variabili d'ambiente (generalmente API keys od opzioni)
|
||||
"gradio", # UI web semplice con user_input e output
|
||||
|
||||
# ✅ per costruire agenti (ovvero modelli che possono fare più cose tramite tool) https://github.com/agno-agi/agno
|
||||
# Per costruire agenti (ovvero modelli che possono fare più cose tramite tool) https://github.com/agno-agi/agno
|
||||
# altamente consigliata dato che ha anche tools integrati per fare scraping, calcoli e molto altro
|
||||
# oltre a questa è necessario installare anche le librerie specifiche per i modelli che si vogliono usare
|
||||
"agno",
|
||||
|
||||
# ✅ Modelli supportati e installati (aggiungere qui sotto quelli che si vogliono usare)
|
||||
# Modelli supportati e installati (aggiungere qui sotto quelli che si vogliono usare)
|
||||
"google-genai",
|
||||
"ollama",
|
||||
|
||||
# ✅ per interagire con API di exchange di criptovalute
|
||||
# API di exchange di criptovalute
|
||||
"coinbase-advanced-py",
|
||||
"python-binance",
|
||||
|
||||
# ✅ per interagire con API di notizie
|
||||
# API di notizie
|
||||
"newsapi-python",
|
||||
"gnews",
|
||||
"ddgs",
|
||||
|
||||
# API di social media
|
||||
"praw", # Reddit
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class SocialPost(BaseModel):
|
||||
time: str = ""
|
||||
title: str = ""
|
||||
description: str = ""
|
||||
comments: list["SocialComment"] = []
|
||||
|
||||
def __str__(self):
|
||||
return f"Title: {self.title}\nDescription: {self.description}\nComments: {len(self.comments)}\n[{" | ".join(str(c) for c in self.comments)}]"
|
||||
|
||||
class SocialComment(BaseModel):
|
||||
time: str = ""
|
||||
description: str = ""
|
||||
|
||||
def __str__(self):
|
||||
return f"Time: {self.time}\nDescription: {self.description}"
|
||||
|
||||
# TODO IMPLEMENTARLO SE SI USANO PIU' WRAPPER (E QUINDI PIU' SOCIAL)
|
||||
class SocialWrapper:
|
||||
pass
|
||||
|
||||
53
src/app/social/reddit.py
Normal file
53
src/app/social/reddit.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import os
|
||||
from praw import Reddit
|
||||
from praw.models import Submission, MoreComments
|
||||
from .base import SocialWrapper, SocialPost, SocialComment
|
||||
|
||||
MAX_COMMENTS = 5
|
||||
|
||||
|
||||
def create_social_post(post: Submission) -> SocialPost:
|
||||
social = SocialPost()
|
||||
social.time = str(post.created)
|
||||
social.title = post.title
|
||||
social.description = post.selftext
|
||||
|
||||
for i, top_comment in enumerate(post.comments):
|
||||
if i >= MAX_COMMENTS:
|
||||
break
|
||||
if isinstance(top_comment, MoreComments): #skip MoreComments objects
|
||||
continue
|
||||
|
||||
comment = SocialComment()
|
||||
comment.time = str(top_comment.created)
|
||||
comment.description = top_comment.body
|
||||
social.comments.append(comment)
|
||||
return social
|
||||
|
||||
class RedditWrapper(SocialWrapper):
|
||||
"""
|
||||
A wrapper for the Reddit API using PRAW (Python Reddit API Wrapper).
|
||||
Requires the following environment variables to be set:
|
||||
- REDDIT_API_CLIENT_ID
|
||||
- REDDIT_API_CLIENT_SECRET
|
||||
You can get them by creating an app at https://www.reddit.com/prefs/apps
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.client_id = os.getenv("REDDIT_API_CLIENT_ID")
|
||||
assert self.client_id is not None, "REDDIT_API_CLIENT_ID environment variable is not set"
|
||||
|
||||
self.client_secret = os.getenv("REDDIT_API_CLIENT_SECRET")
|
||||
assert self.client_secret is not None, "REDDIT_API_CLIENT_SECRET environment variable is not set"
|
||||
|
||||
self.tool = Reddit(
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
user_agent="upo-appAI",
|
||||
)
|
||||
|
||||
def get_top_crypto_posts(self, limit=5) -> list[SocialPost]:
|
||||
subreddit = self.tool.subreddit("CryptoCurrency")
|
||||
top_posts = subreddit.top(limit=limit, time_filter="week")
|
||||
return [create_social_post(post) for post in top_posts]
|
||||
|
||||
24
tests/api/test_reddit.py
Normal file
24
tests/api/test_reddit.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
from praw import Reddit
|
||||
from app.social.reddit import MAX_COMMENTS, RedditWrapper
|
||||
|
||||
@pytest.mark.social
|
||||
@pytest.mark.api
|
||||
class TestRedditWrapper:
|
||||
def test_initialization(self):
|
||||
wrapper = RedditWrapper()
|
||||
assert wrapper.client_id is not None
|
||||
assert wrapper.client_secret is not None
|
||||
assert isinstance(wrapper.tool, Reddit)
|
||||
|
||||
def test_get_top_crypto_posts(self):
|
||||
wrapper = RedditWrapper()
|
||||
posts = wrapper.get_top_crypto_posts(limit=2)
|
||||
assert isinstance(posts, list)
|
||||
assert len(posts) == 2
|
||||
for post in posts:
|
||||
assert post.title != ""
|
||||
assert isinstance(post.comments, list)
|
||||
assert len(post.comments) <= MAX_COMMENTS
|
||||
for comment in post.comments:
|
||||
assert comment.description != ""
|
||||
@@ -21,6 +21,7 @@ def pytest_configure(config:pytest.Config):
|
||||
("ollama_gpt", "marks tests that use Ollama GPT model"),
|
||||
("ollama_qwen", "marks tests that use Ollama Qwen model"),
|
||||
("news", "marks tests that use news"),
|
||||
("social", "marks tests that use social media"),
|
||||
("limited", "marks tests that have limited execution due to API constraints"),
|
||||
("wrapper", "marks tests for wrapper handler"),
|
||||
]
|
||||
|
||||
49
uv.lock
generated
49
uv.lock
generated
@@ -961,6 +961,32 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "praw"
|
||||
version = "7.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "prawcore" },
|
||||
{ name = "update-checker" },
|
||||
{ name = "websocket-client" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/52/7dd0b3d9ccb78e90236420ef6c51b6d9b2400a7229442f0cfcf2258cce21/praw-7.8.1.tar.gz", hash = "sha256:3c5767909f71e48853eb6335fef7b50a43cbe3da728cdfb16d3be92904b0a4d8", size = 154106, upload-time = "2024-10-25T21:49:33.16Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/73/ca/60ec131c3b43bff58261167045778b2509b83922ce8f935ac89d871bd3ea/praw-7.8.1-py3-none-any.whl", hash = "sha256:15917a81a06e20ff0aaaf1358481f4588449fa2421233040cb25e5c8202a3e2f", size = 189338, upload-time = "2024-10-25T21:49:31.109Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prawcore"
|
||||
version = "2.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/62/d4c99cf472205f1e5da846b058435a6a7c988abf8eb6f7d632a7f32f4a77/prawcore-2.4.0.tar.gz", hash = "sha256:b7b2b5a1d04406e086ab4e79988dc794df16059862f329f4c6a43ed09986c335", size = 15862, upload-time = "2023-10-01T23:30:49.408Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/96/5c/8af904314e42d5401afcfaff69940dc448e974f80f7aa39b241a4fbf0cf1/prawcore-2.4.0-py3-none-any.whl", hash = "sha256:29af5da58d85704b439ad3c820873ad541f4535e00bb98c66f0fbcc8c603065a", size = 17203, upload-time = "2023-10-01T23:30:47.651Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primp"
|
||||
version = "0.15.0"
|
||||
@@ -1490,6 +1516,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "update-checker"
|
||||
version = "0.18.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/0b/1bec4a6cc60d33ce93d11a7bcf1aeffc7ad0aa114986073411be31395c6f/update_checker-0.18.0.tar.gz", hash = "sha256:6a2d45bb4ac585884a6b03f9eade9161cedd9e8111545141e9aa9058932acb13", size = 6699, upload-time = "2020-08-04T07:08:50.429Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/ba/8dd7fa5f0b1c6a8ac62f8f57f7e794160c1f86f31c6d0fb00f582372a3e4/update_checker-0.18.0-py3-none-any.whl", hash = "sha256:cbba64760a36fe2640d80d85306e8fe82b6816659190993b7bdabadee4d4bbfd", size = 7008, upload-time = "2020-08-04T07:08:49.51Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "upo-app-ai"
|
||||
version = "0.1.0"
|
||||
@@ -1504,6 +1542,7 @@ dependencies = [
|
||||
{ name = "gradio" },
|
||||
{ name = "newsapi-python" },
|
||||
{ name = "ollama" },
|
||||
{ name = "praw" },
|
||||
{ name = "pytest" },
|
||||
{ name = "python-binance" },
|
||||
]
|
||||
@@ -1519,6 +1558,7 @@ requires-dist = [
|
||||
{ name = "gradio" },
|
||||
{ name = "newsapi-python" },
|
||||
{ name = "ollama" },
|
||||
{ name = "praw" },
|
||||
{ name = "pytest" },
|
||||
{ name = "python-binance" },
|
||||
]
|
||||
@@ -1545,6 +1585,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websocket-client"
|
||||
version = "1.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "13.1"
|
||||
|
||||
Reference in New Issue
Block a user