3 market api #8
16
.env.example
16
.env.example
@@ -10,11 +10,11 @@ GOOGLE_API_KEY=
|
|||||||
# Configurazioni per gli agenti di mercato
|
# 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_KEY_NAME=
|
||||||
CDP_API_PRIVATE_KEY=
|
CDP_API_PRIVATE_KEY=
|
||||||
|
|
||||||
# Ottenibile da: https://www.cryptocompare.com/cryptopian/api-keys
|
# https://www.cryptocompare.com/cryptopian/api-keys
|
||||||
CRYPTOCOMPARE_API_KEY=
|
CRYPTOCOMPARE_API_KEY=
|
||||||
|
|
||||||
# Binance API per Market Agent (alternativa)
|
# Binance API per Market Agent (alternativa)
|
||||||
@@ -25,8 +25,16 @@ BINANCE_API_SECRET=
|
|||||||
# Configurazioni per gli agenti di notizie
|
# Configurazioni per gli agenti di notizie
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# Ottenibile da: https://newsapi.org/docs
|
# https://newsapi.org/docs
|
||||||
NEWS_API_KEY=
|
NEWS_API_KEY=
|
||||||
|
|
||||||
# Ottenibile da: https://cryptopanic.com/developers/api/
|
# https://cryptopanic.com/developers/api/
|
||||||
CRYPTOPANIC_API_KEY=
|
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.
|
# 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.
|
# Inoltre ho messo una emoji per indicare se è raccomandato o meno.
|
||||||
dependencies = [
|
dependencies = [
|
||||||
# ✅ per i test
|
"pytest", # Test
|
||||||
"pytest",
|
"dotenv", # Gestire variabili d'ambiente (generalmente API keys od opzioni)
|
||||||
# ✅ per gestire variabili d'ambiente (generalmente API keys od opzioni)
|
"gradio", # UI web semplice con user_input e output
|
||||||
"dotenv",
|
|
||||||
# ✅ per fare una UI web semplice con input e output
|
|
||||||
"gradio",
|
|
||||||
|
|
||||||
# ✅ 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
|
# 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
|
# oltre a questa è necessario installare anche le librerie specifiche per i modelli che si vogliono usare
|
||||||
"agno",
|
"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",
|
"google-genai",
|
||||||
"ollama",
|
"ollama",
|
||||||
|
|
||||||
# ✅ per interagire con API di exchange di criptovalute
|
# API di exchange di criptovalute
|
||||||
"coinbase-advanced-py",
|
"coinbase-advanced-py",
|
||||||
"python-binance",
|
"python-binance",
|
||||||
|
|
||||||
# ✅ per interagire con API di notizie
|
# API di notizie
|
||||||
"newsapi-python",
|
"newsapi-python",
|
||||||
"gnews",
|
"gnews",
|
||||||
"ddgs",
|
"ddgs",
|
||||||
|
|
||||||
|
# API di social media
|
||||||
|
"praw", # Reddit
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[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_gpt", "marks tests that use Ollama GPT model"),
|
||||||
("ollama_qwen", "marks tests that use Ollama Qwen model"),
|
("ollama_qwen", "marks tests that use Ollama Qwen model"),
|
||||||
("news", "marks tests that use news"),
|
("news", "marks tests that use news"),
|
||||||
|
("social", "marks tests that use social media"),
|
||||||
("limited", "marks tests that have limited execution due to API constraints"),
|
("limited", "marks tests that have limited execution due to API constraints"),
|
||||||
("wrapper", "marks tests for wrapper handler"),
|
("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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "primp"
|
name = "primp"
|
||||||
version = "0.15.0"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "upo-app-ai"
|
name = "upo-app-ai"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1504,6 +1542,7 @@ dependencies = [
|
|||||||
{ name = "gradio" },
|
{ name = "gradio" },
|
||||||
{ name = "newsapi-python" },
|
{ name = "newsapi-python" },
|
||||||
{ name = "ollama" },
|
{ name = "ollama" },
|
||||||
|
{ name = "praw" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
{ name = "python-binance" },
|
{ name = "python-binance" },
|
||||||
]
|
]
|
||||||
@@ -1519,6 +1558,7 @@ requires-dist = [
|
|||||||
{ name = "gradio" },
|
{ name = "gradio" },
|
||||||
{ name = "newsapi-python" },
|
{ name = "newsapi-python" },
|
||||||
{ name = "ollama" },
|
{ name = "ollama" },
|
||||||
|
{ name = "praw" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
{ name = "python-binance" },
|
{ 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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "websockets"
|
name = "websockets"
|
||||||
version = "13.1"
|
version = "13.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user