sylvia-api 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sylvia/__init__.py +22 -0
- sylvia/client.py +117 -0
- sylvia/exceptions.py +13 -0
- sylvia_api-1.0.0.dist-info/METADATA +92 -0
- sylvia_api-1.0.0.dist-info/RECORD +7 -0
- sylvia_api-1.0.0.dist-info/WHEEL +5 -0
- sylvia_api-1.0.0.dist-info/top_level.txt +1 -0
sylvia/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sylvia Reddit API Client — fastest way to access Reddit data at scale.
|
|
3
|
+
No OAuth. No rate limit headaches. Just an API key.
|
|
4
|
+
|
|
5
|
+
pip install sylvia-api
|
|
6
|
+
export SYLVIA_API_KEY=syl_xxx
|
|
7
|
+
|
|
8
|
+
from sylvia import Sylvia
|
|
9
|
+
s = Sylvia()
|
|
10
|
+
python = s.subreddit("python")
|
|
11
|
+
print(f"{python['name']}: {python['members']:,} members")
|
|
12
|
+
for post in s.subreddit_posts("python", sort="hot", limit=5):
|
|
13
|
+
print(f" {post['score']} — {post['title']}")
|
|
14
|
+
|
|
15
|
+
License: MIT
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .client import Sylvia
|
|
19
|
+
from .exceptions import SylviaError, AuthenticationError, RateLimitError, InsufficientCreditsError
|
|
20
|
+
|
|
21
|
+
__version__ = "1.0.0"
|
|
22
|
+
__all__ = ["Sylvia", "SylviaError", "AuthenticationError", "RateLimitError", "InsufficientCreditsError"]
|
sylvia/client.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from typing import Optional, Iterator, Dict, Any, List
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Sylvia:
|
|
9
|
+
"""Reddit data API client. No OAuth. Just works."""
|
|
10
|
+
|
|
11
|
+
BASE_URL = "https://api.sylvia-api.com/v1/reddit/"
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
api_key: Optional[str] = None,
|
|
16
|
+
max_retries: int = 3,
|
|
17
|
+
timeout: int = 60,
|
|
18
|
+
):
|
|
19
|
+
self.api_key = api_key or os.environ.get("SYLVIA_API_KEY")
|
|
20
|
+
if not self.api_key:
|
|
21
|
+
raise ValueError(
|
|
22
|
+
"API key required. Set SYLVIA_API_KEY env var or pass api_key=..."
|
|
23
|
+
)
|
|
24
|
+
self.max_retries = max_retries
|
|
25
|
+
self.timeout = timeout
|
|
26
|
+
self._session = requests.Session()
|
|
27
|
+
self._session.headers.update({"X-API-KEY": self.api_key})
|
|
28
|
+
|
|
29
|
+
def _request(self, path: str, params: Optional[Dict] = None) -> Dict[str, Any]:
|
|
30
|
+
url = f"{self.BASE_URL}{path}"
|
|
31
|
+
for attempt in range(self.max_retries + 1):
|
|
32
|
+
resp = self._session.get(url, params=params, timeout=self.timeout)
|
|
33
|
+
if resp.status_code == 200:
|
|
34
|
+
return resp.json()
|
|
35
|
+
if resp.status_code == 401:
|
|
36
|
+
raise AuthenticationError("Invalid API key. Get one at https://sylvia-api.com")
|
|
37
|
+
if resp.status_code == 429:
|
|
38
|
+
retry = int(resp.headers.get("Retry-After", 5))
|
|
39
|
+
if attempt < self.max_retries:
|
|
40
|
+
time.sleep(retry)
|
|
41
|
+
continue
|
|
42
|
+
raise RateLimitError(retry)
|
|
43
|
+
if resp.status_code == 402:
|
|
44
|
+
raise InsufficientCreditsError("Add credits at https://sylvia-api.com/dashboard")
|
|
45
|
+
resp.raise_for_status()
|
|
46
|
+
raise SylviaError("Max retries exceeded")
|
|
47
|
+
|
|
48
|
+
def subreddit(self, name: str) -> Dict[str, Any]:
|
|
49
|
+
data = self._request(f"r/{name}/about")
|
|
50
|
+
return data.get("data", data)
|
|
51
|
+
|
|
52
|
+
def subreddit_posts(
|
|
53
|
+
self, name: str, sort: str = "hot", limit: int = 25, after: Optional[str] = None,
|
|
54
|
+
) -> List[Dict[str, Any]]:
|
|
55
|
+
params = {"limit": min(limit, 100)}
|
|
56
|
+
if after:
|
|
57
|
+
params["after"] = after
|
|
58
|
+
data = self._request(f"r/{name}/{sort}", params=params)
|
|
59
|
+
return data.get("data", {}).get("posts", [])
|
|
60
|
+
|
|
61
|
+
def subreddit_posts_iter(
|
|
62
|
+
self, name: str, sort: str = "hot", limit: int = 100,
|
|
63
|
+
) -> Iterator[Dict[str, Any]]:
|
|
64
|
+
yielded = 0
|
|
65
|
+
after = None
|
|
66
|
+
while yielded < limit:
|
|
67
|
+
batch = self.subreddit_posts(name, sort=sort, limit=min(limit - yielded, 100), after=after)
|
|
68
|
+
if not batch:
|
|
69
|
+
break
|
|
70
|
+
for post in batch:
|
|
71
|
+
yield post
|
|
72
|
+
yielded += 1
|
|
73
|
+
if yielded >= limit:
|
|
74
|
+
return
|
|
75
|
+
after = batch[-1].get("name")
|
|
76
|
+
|
|
77
|
+
def user(self, username: str) -> Dict[str, Any]:
|
|
78
|
+
data = self._request(f"u/{username}/about")
|
|
79
|
+
return data.get("data", data)
|
|
80
|
+
|
|
81
|
+
def user_posts(self, username: str, where: str = "submitted", limit: int = 25) -> List[Dict[str, Any]]:
|
|
82
|
+
params = {"limit": min(limit, 100)}
|
|
83
|
+
data = self._request(f"u/{username}/{where}", params=params)
|
|
84
|
+
return data.get("data", {}).get("children", [])
|
|
85
|
+
|
|
86
|
+
def search(
|
|
87
|
+
self, query: str, sort: str = "relevance", limit: int = 25,
|
|
88
|
+
subreddit: Optional[str] = None, include_over_18: bool = False,
|
|
89
|
+
) -> List[Dict[str, Any]]:
|
|
90
|
+
params = {"q": query, "sort": sort, "limit": min(limit, 100)}
|
|
91
|
+
if subreddit:
|
|
92
|
+
params["subreddit"] = subreddit
|
|
93
|
+
if include_over_18:
|
|
94
|
+
params["include_over_18"] = "true"
|
|
95
|
+
data = self._request("search", params=params)
|
|
96
|
+
return data.get("data", {}).get("results", [])
|
|
97
|
+
|
|
98
|
+
def post(self, url: str) -> Dict[str, Any]:
|
|
99
|
+
data = self._request("submission", params={"url": url})
|
|
100
|
+
return data.get("data", data)
|
|
101
|
+
|
|
102
|
+
def comment(self, url: str) -> Dict[str, Any]:
|
|
103
|
+
data = self._request("comment", params={"url": url})
|
|
104
|
+
return data.get("data", data)
|
|
105
|
+
|
|
106
|
+
def thread(self, post_id: str) -> Dict[str, Any]:
|
|
107
|
+
data = self._request(f"submission/{post_id}/full")
|
|
108
|
+
return data.get("data", data)
|
|
109
|
+
|
|
110
|
+
def live_comments(self, limit: int = 25) -> List[Dict[str, Any]]:
|
|
111
|
+
data = self._request("comments/live", params={"limit": min(limit, 100)})
|
|
112
|
+
return data.get("data", {}).get("comments", [])
|
|
113
|
+
|
|
114
|
+
def domain(self, domain: str, sort: str = "hot", limit: int = 25) -> List[Dict[str, Any]]:
|
|
115
|
+
params = {"domain": domain, "sort": sort, "limit": min(limit, 100)}
|
|
116
|
+
data = self._request("domain", params=params)
|
|
117
|
+
return data.get("data", {}).get("posts", [])
|
sylvia/exceptions.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class SylviaError(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
class AuthenticationError(SylviaError):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
class RateLimitError(SylviaError):
|
|
8
|
+
def __init__(self, retry_after: int = 0):
|
|
9
|
+
self.retry_after = retry_after
|
|
10
|
+
super().__init__(f"Rate limited. Retry after {retry_after}s")
|
|
11
|
+
|
|
12
|
+
class InsufficientCreditsError(SylviaError):
|
|
13
|
+
pass
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sylvia-api
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Reddit data API client — no OAuth, no rate limits. Subreddits, users, search, live comments.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://sylvia-api.com
|
|
7
|
+
Project-URL: Documentation, https://sylvia-api.com/docs
|
|
8
|
+
Project-URL: Repository, https://github.com/emraan-harshme/sylvia-sdk
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: requests>=2.25
|
|
15
|
+
|
|
16
|
+
# Sylvia Reddit API
|
|
17
|
+
|
|
18
|
+
**The fastest way to access Reddit data at scale.** No OAuth. No rate limit headaches. No browser automation. Just an API key.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install sylvia-api
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from sylvia import Sylvia
|
|
28
|
+
|
|
29
|
+
# Sign up at https://sylvia-api.com to get your key
|
|
30
|
+
sylvia = Sylvia(api_key="syl_...")
|
|
31
|
+
|
|
32
|
+
# Subreddit info
|
|
33
|
+
python_sub = sylvia.subreddit("python")
|
|
34
|
+
print(f"r/{python_sub['name']}: {python_sub['members']:,} members")
|
|
35
|
+
|
|
36
|
+
# Get top posts
|
|
37
|
+
for post in sylvia.subreddit_posts("programming", sort="hot", limit=5):
|
|
38
|
+
print(f"🔥 {post['score']} — {post['title']}")
|
|
39
|
+
|
|
40
|
+
# User profile
|
|
41
|
+
spez = sylvia.user("spez")
|
|
42
|
+
print(f"u/spez: {spez['comment_karma']:,} comment karma")
|
|
43
|
+
|
|
44
|
+
# Global search (not available in Reddit's own API!)
|
|
45
|
+
results = sylvia.search("llama 4 model", limit=10)
|
|
46
|
+
for post in results:
|
|
47
|
+
print(f"r/{post['subreddit']} — {post['title']}")
|
|
48
|
+
|
|
49
|
+
# Live comment firehose
|
|
50
|
+
for comment in sylvia.live_comments(limit=10):
|
|
51
|
+
print(f"u/{comment['author']}: {comment['body'][:100]}")
|
|
52
|
+
|
|
53
|
+
# Thread with ALL nested comments expanded
|
|
54
|
+
thread = sylvia.thread("1tlh5aj")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## How It Works
|
|
58
|
+
|
|
59
|
+
Sylvia operates a fleet of real residential proxy engines that fetch Reddit data through actual browsers. This means:
|
|
60
|
+
- **No OAuth** needed — just an API key
|
|
61
|
+
- **480 req/min free** (with free tier API key)
|
|
62
|
+
- **Global search** across all of Reddit
|
|
63
|
+
- **Live comment firehose** in real-time
|
|
64
|
+
- **Automatic IP rotation** — Reddit never blocks you
|
|
65
|
+
|
|
66
|
+
## Pricing
|
|
67
|
+
|
|
68
|
+
| Tier | Rate Limit | Price |
|
|
69
|
+
|------|-----------|-------|
|
|
70
|
+
| Free | 8 req/s (480/min) | $0 |
|
|
71
|
+
| Strela | 20 req/s | $50/mo |
|
|
72
|
+
| Svetka | 40 req/s | $200/mo |
|
|
73
|
+
| Enterprise | 60+ req/s | Custom |
|
|
74
|
+
|
|
75
|
+
Per-request billing: **$0.0005/request** ($0.50 per 1,000 requests). Failed requests are not charged.
|
|
76
|
+
|
|
77
|
+
[Sign up free →](https://sylvia-api.com)
|
|
78
|
+
|
|
79
|
+
## Comparison
|
|
80
|
+
|
|
81
|
+
| | Sylvia | Reddit API | Bright Data | Apify |
|
|
82
|
+
|---|---|---|---|---|
|
|
83
|
+
| OAuth required | ❌ No | ✅ Yes | ❌ No | ❌ No |
|
|
84
|
+
| Global search | ✅ Yes | ❌ No | ❌ No | Limited |
|
|
85
|
+
| Live comments | ✅ Yes | ❌ No | ❌ No | ✅ Yes |
|
|
86
|
+
| Auto IP rotation | ✅ Yes | ❌ No | ✅ Yes | ✅ Yes |
|
|
87
|
+
| Free tier | 480 req/min | 60 req/min | None | 100 req/mo |
|
|
88
|
+
| Per-1K cost | $0.50 | Free (limited) | $8.40/GB | ~$5/1K |
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
sylvia/__init__.py,sha256=xN_-CJNjzsPYFn9k-Ceq02SjhOhX-Lfwqr-zuCOTXps,731
|
|
2
|
+
sylvia/client.py,sha256=JOFeVn8or-WCOAcz_m5SKelvskDYaBX9DcrGm5ktkKA,4631
|
|
3
|
+
sylvia/exceptions.py,sha256=YPgZyRBGcPBQyxlcYsa-j8jo4-MKsDMiboae7IB42FU,335
|
|
4
|
+
sylvia_api-1.0.0.dist-info/METADATA,sha256=PQv3gzg4gw_nblDrBdpNgbmmFvhfoH1T4KWlQiLrteg,2872
|
|
5
|
+
sylvia_api-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
6
|
+
sylvia_api-1.0.0.dist-info/top_level.txt,sha256=BuiI5LL-PXD444cHUXAFgxx-fDpUjvU_-KXq4w9K1L4,7
|
|
7
|
+
sylvia_api-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sylvia
|