rairos 0.2.0__tar.gz

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.
rairos-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: rairos
3
+ Version: 0.2.0
4
+ Summary: Python SDK for Rairos API
5
+ Author-email: Rairos Team <team@rairos.ai>
6
+ License: MIT
7
+ Project-URL: Homepage, https://rairos.ai
8
+ Project-URL: Documentation, https://docs.rairos.ai
9
+ Project-URL: Repository, https://github.com/shushuzn/Rairos
10
+ Project-URL: Changelog, https://github.com/shushuzn/Rairos/blob/main/sdks/python/CHANGELOG.md
11
+ Keywords: rairos,api,research,ai
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: requests>=2.28.0
23
+ Requires-Dist: urllib3>=2.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
26
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
27
+ Requires-Dist: black>=23.0.0; extra == "dev"
28
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
29
+
30
+ # Rairos Python SDK
31
+
32
+ [![PyPI version](https://img.shields.io/pypi/v/rairos.svg)](https://pypi.org/project/rairos/)
33
+ [![Python versions](https://img.shields.io/pypi/pyversions/rairos.svg)](https://pypi.org/project/rairos/)
34
+ [![License](https://img.shields.io/pypi/l/rairos.svg)](https://github.com/shushuzn/Rairos/blob/main/sdks/python/pyproject.toml)
35
+
36
+ Python SDK for the Rairos API platform.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ # From PyPI (coming soon)
42
+ pip install rairos
43
+
44
+ # From source
45
+ cd sdks/python && pip install -e . && cd ../..
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ ```python
51
+ from rairos import RairosClient
52
+
53
+ # Initialize client
54
+ client = RairosClient(api_key="your_api_key_here")
55
+
56
+ # Search papers
57
+ results = client.search_papers(query="machine learning", page=1, per_page=20)
58
+ print(results)
59
+
60
+ # Detect research gaps (requires Pro tier)
61
+ gaps = client.detect_gap(query="neural network architecture")
62
+ print(gaps)
63
+
64
+ # Get usage statistics
65
+ usage = client.get_usage()
66
+ print(usage)
67
+ ```
68
+
69
+ ## Authentication
70
+
71
+ You can provide your API key in three ways:
72
+
73
+ 1. **Environment variable** (recommended):
74
+ ```bash
75
+ export RAIROS_API_KEY=your_api_key_here
76
+ ```
77
+
78
+ 2. **Constructor parameter**:
79
+ ```python
80
+ client = RairosClient(api_key="your_api_key_here")
81
+ ```
82
+
83
+ 3. **Register/Login** to get a new API key:
84
+ ```python
85
+ # Register
86
+ auth = client.register(email="user@example.com", password="secure_password")
87
+
88
+ # Login
89
+ auth = client.login(email="user@example.com", password="secure_password")
90
+ print(auth["api_key"]) # Use this key
91
+ ```
92
+
93
+ ## API Reference
94
+
95
+ ### Papers
96
+
97
+ ```python
98
+ # Search papers
99
+ results = client.search_papers(query="quantum computing")
100
+
101
+ # Get specific paper
102
+ paper = client.get_paper(paper_id="uuid-here")
103
+ ```
104
+
105
+ ### Gap Detection (Pro tier)
106
+
107
+ ```python
108
+ # Detect research gaps
109
+ gaps = client.detect_gap(
110
+ query="What are the gaps in transformer architecture research?",
111
+ categories=["cs.AI", "cs.LG"]
112
+ )
113
+ ```
114
+
115
+ ### Research (Team tier)
116
+
117
+ ```python
118
+ # Run automated research
119
+ results = client.run_research(
120
+ query="Analyze the state of AI safety research in 2025"
121
+ )
122
+ ```
123
+
124
+ ### Subscriptions
125
+
126
+ ```python
127
+ # Get available tiers
128
+ tiers = client.get_tiers()
129
+
130
+ # Create checkout session
131
+ checkout = client.create_checkout(
132
+ price_id="price_pro_monthly",
133
+ success_url="https://yourapp.com/success",
134
+ cancel_url="https://yourapp.com/pricing"
135
+ )
136
+ print(checkout["checkout_url"]) # Redirect user to this URL
137
+
138
+ # Get subscription status
139
+ status = client.get_subscription_status()
140
+ ```
141
+
142
+ ## Error Handling
143
+
144
+ ```python
145
+ from rairos import (
146
+ RairosClient,
147
+ RairosError,
148
+ AuthenticationError,
149
+ RateLimitError
150
+ )
151
+
152
+ try:
153
+ client = RairosClient(api_key="invalid_key")
154
+ results = client.search_papers(query="test")
155
+ except AuthenticationError:
156
+ print("Invalid API key")
157
+ except RateLimitError:
158
+ print("Rate limit exceeded - wait and retry")
159
+ except RairosError as e:
160
+ print(f"API error: {e.message}")
161
+ ```
162
+
163
+ ## License
164
+
165
+ MIT License
rairos-0.2.0/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # Rairos Python SDK
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/rairos.svg)](https://pypi.org/project/rairos/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/rairos.svg)](https://pypi.org/project/rairos/)
5
+ [![License](https://img.shields.io/pypi/l/rairos.svg)](https://github.com/shushuzn/Rairos/blob/main/sdks/python/pyproject.toml)
6
+
7
+ Python SDK for the Rairos API platform.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ # From PyPI (coming soon)
13
+ pip install rairos
14
+
15
+ # From source
16
+ cd sdks/python && pip install -e . && cd ../..
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```python
22
+ from rairos import RairosClient
23
+
24
+ # Initialize client
25
+ client = RairosClient(api_key="your_api_key_here")
26
+
27
+ # Search papers
28
+ results = client.search_papers(query="machine learning", page=1, per_page=20)
29
+ print(results)
30
+
31
+ # Detect research gaps (requires Pro tier)
32
+ gaps = client.detect_gap(query="neural network architecture")
33
+ print(gaps)
34
+
35
+ # Get usage statistics
36
+ usage = client.get_usage()
37
+ print(usage)
38
+ ```
39
+
40
+ ## Authentication
41
+
42
+ You can provide your API key in three ways:
43
+
44
+ 1. **Environment variable** (recommended):
45
+ ```bash
46
+ export RAIROS_API_KEY=your_api_key_here
47
+ ```
48
+
49
+ 2. **Constructor parameter**:
50
+ ```python
51
+ client = RairosClient(api_key="your_api_key_here")
52
+ ```
53
+
54
+ 3. **Register/Login** to get a new API key:
55
+ ```python
56
+ # Register
57
+ auth = client.register(email="user@example.com", password="secure_password")
58
+
59
+ # Login
60
+ auth = client.login(email="user@example.com", password="secure_password")
61
+ print(auth["api_key"]) # Use this key
62
+ ```
63
+
64
+ ## API Reference
65
+
66
+ ### Papers
67
+
68
+ ```python
69
+ # Search papers
70
+ results = client.search_papers(query="quantum computing")
71
+
72
+ # Get specific paper
73
+ paper = client.get_paper(paper_id="uuid-here")
74
+ ```
75
+
76
+ ### Gap Detection (Pro tier)
77
+
78
+ ```python
79
+ # Detect research gaps
80
+ gaps = client.detect_gap(
81
+ query="What are the gaps in transformer architecture research?",
82
+ categories=["cs.AI", "cs.LG"]
83
+ )
84
+ ```
85
+
86
+ ### Research (Team tier)
87
+
88
+ ```python
89
+ # Run automated research
90
+ results = client.run_research(
91
+ query="Analyze the state of AI safety research in 2025"
92
+ )
93
+ ```
94
+
95
+ ### Subscriptions
96
+
97
+ ```python
98
+ # Get available tiers
99
+ tiers = client.get_tiers()
100
+
101
+ # Create checkout session
102
+ checkout = client.create_checkout(
103
+ price_id="price_pro_monthly",
104
+ success_url="https://yourapp.com/success",
105
+ cancel_url="https://yourapp.com/pricing"
106
+ )
107
+ print(checkout["checkout_url"]) # Redirect user to this URL
108
+
109
+ # Get subscription status
110
+ status = client.get_subscription_status()
111
+ ```
112
+
113
+ ## Error Handling
114
+
115
+ ```python
116
+ from rairos import (
117
+ RairosClient,
118
+ RairosError,
119
+ AuthenticationError,
120
+ RateLimitError
121
+ )
122
+
123
+ try:
124
+ client = RairosClient(api_key="invalid_key")
125
+ results = client.search_papers(query="test")
126
+ except AuthenticationError:
127
+ print("Invalid API key")
128
+ except RateLimitError:
129
+ print("Rate limit exceeded - wait and retry")
130
+ except RairosError as e:
131
+ print(f"API error: {e.message}")
132
+ ```
133
+
134
+ ## License
135
+
136
+ MIT License
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rairos"
7
+ version = "0.2.0"
8
+ description = "Python SDK for Rairos API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Rairos Team", email = "team@rairos.ai"}
14
+ ]
15
+ keywords = ["rairos", "api", "research", "ai"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.8",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ ]
26
+ dependencies = [
27
+ "requests>=2.28.0",
28
+ "urllib3>=2.0.0",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "pytest>=7.0.0",
34
+ "pytest-asyncio>=0.21.0",
35
+ "black>=23.0.0",
36
+ "mypy>=1.0.0",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://rairos.ai"
41
+ Documentation = "https://docs.rairos.ai"
42
+ Repository = "https://github.com/shushuzn/Rairos"
43
+ Changelog = "https://github.com/shushuzn/Rairos/blob/main/sdks/python/CHANGELOG.md"
44
+
45
+ [tool.setuptools.packages.find]
46
+ where = ["."]
47
+ include = ["rairos*"]
48
+
49
+ [tool.black]
50
+ line-length = 88
51
+ target-version = ['py38', 'py39', 'py310', 'py311']
52
+
53
+ [tool.mypy]
54
+ python_version = "3.8"
55
+ warn_return_any = true
56
+ warn_unused_configs = true
@@ -0,0 +1,32 @@
1
+ """Rairos API Python SDK
2
+
3
+ A Python SDK for the Rairos API platform.
4
+ """
5
+
6
+ __version__ = "0.2.0"
7
+
8
+ from .client import RairosClient
9
+ from .exceptions import (
10
+ RairosError,
11
+ AuthenticationError,
12
+ RateLimitError,
13
+ ValidationError,
14
+ NotFoundError,
15
+ ForbiddenError,
16
+ ServerError,
17
+ PaymentError,
18
+ raise_from_response,
19
+ )
20
+
21
+ __all__ = [
22
+ "RairosClient",
23
+ "RairosError",
24
+ "AuthenticationError",
25
+ "RateLimitError",
26
+ "ValidationError",
27
+ "NotFoundError",
28
+ "ForbiddenError",
29
+ "ServerError",
30
+ "PaymentError",
31
+ "raise_from_response",
32
+ ]
@@ -0,0 +1,392 @@
1
+ """Rairos API Client
2
+
3
+ Main client for interacting with the Rairos API.
4
+ """
5
+
6
+ import os
7
+ import time
8
+ from typing import Optional, Dict, Any, List
9
+ from .exceptions import (
10
+ RairosError,
11
+ AuthenticationError,
12
+ RateLimitError,
13
+ ValidationError,
14
+ NotFoundError,
15
+ ForbiddenError,
16
+ ServerError,
17
+ PaymentError,
18
+ raise_from_response,
19
+ )
20
+
21
+
22
+ class RairosClient:
23
+ """Python SDK for Rairos API.
24
+
25
+ Example:
26
+ >>> client = RairosClient(api_key="your-api-key")
27
+ >>> usage = client.get_usage()
28
+ >>> papers = client.search_papers(query="machine learning")
29
+ """
30
+
31
+ BASE_URL = os.environ.get("RAIROS_API_URL", "https://api.rairos.ai/api/v1")
32
+
33
+ def __init__(
34
+ self,
35
+ api_key: Optional[str] = None,
36
+ max_retries: int = 3,
37
+ retry_delay: float = 1.0,
38
+ timeout: Optional[int] = 30
39
+ ):
40
+ """Initialize the Rairos client.
41
+
42
+ Args:
43
+ api_key: Your Rairos API key. If not provided, will look for RAIROS_API_KEY env var.
44
+ max_retries: Maximum number of retries for failed requests (default: 3)
45
+ retry_delay: Initial delay between retries in seconds (default: 1.0)
46
+ timeout: Request timeout in seconds (default: 30)
47
+ """
48
+ self.api_key = api_key or os.environ.get("RAIROS_API_KEY")
49
+ if not self.api_key:
50
+ raise AuthenticationError("API key is required. Pass api_key or set RAIROS_API_KEY env var.")
51
+
52
+ self.max_retries = max_retries
53
+ self.retry_delay = retry_delay
54
+ self.timeout = timeout
55
+
56
+ def _request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
57
+ """Make an HTTP request to the Rairos API with automatic retry.
58
+
59
+ Args:
60
+ method: HTTP method (GET, POST, etc.)
61
+ endpoint: API endpoint path
62
+ **kwargs: Additional arguments passed to requests.request
63
+
64
+ Returns:
65
+ Parsed JSON response
66
+
67
+ Raises:
68
+ AuthenticationError: If API key is invalid
69
+ RateLimitError: If rate limit is exceeded
70
+ RairosError: For other errors
71
+ """
72
+ import requests
73
+ from requests.adapters import HTTPAdapter
74
+ from urllib3.util.retry import Retry
75
+
76
+ url = f"{self.BASE_URL}{endpoint}"
77
+ headers = {"Authorization": f"Bearer {self.api_key}"}
78
+ headers.update(kwargs.pop("headers", {}))
79
+
80
+ timeout = kwargs.pop("timeout", self.timeout)
81
+
82
+ session = requests.Session()
83
+ retry_strategy = Retry(
84
+ total=self.max_retries,
85
+ backoff_factor=1,
86
+ status_forcelist=[429, 500, 502, 503, 504],
87
+ allowed_methods=["HEAD", "GET", "OPTIONS", "POST"],
88
+ raise_on_status=False
89
+ )
90
+ adapter = HTTPAdapter(max_retries=retry_strategy)
91
+ session.mount("http://", adapter)
92
+ session.mount("https://", adapter)
93
+
94
+ try:
95
+ response = session.request(
96
+ method,
97
+ url,
98
+ headers=headers,
99
+ timeout=timeout,
100
+ **kwargs
101
+ )
102
+
103
+ if response.status_code == 401:
104
+ raise AuthenticationError(
105
+ "Invalid or expired API key",
106
+ details={"endpoint": endpoint}
107
+ )
108
+
109
+ if response.status_code == 429:
110
+ error_data = {}
111
+ try:
112
+ error_data = response.json()
113
+ except ValueError:
114
+ pass
115
+
116
+ limit = error_data.get("error", {}).get("limit")
117
+ reset_at = error_data.get("error", {}).get("reset_at")
118
+
119
+ raise RateLimitError(
120
+ "Rate limit exceeded. Please retry after the reset time.",
121
+ limit=limit,
122
+ reset_at=reset_at,
123
+ details=error_data
124
+ )
125
+
126
+ if not response.ok:
127
+ error_data = {}
128
+ try:
129
+ error_data = response.json()
130
+ except ValueError:
131
+ error_data = {"error": {"message": response.text or "Unknown error"}}
132
+
133
+ raise_from_response(response.status_code, error_data)
134
+
135
+ if response.content:
136
+ return response.json()
137
+ return {}
138
+
139
+ except requests.exceptions.Timeout:
140
+ raise RairosError(
141
+ f"Request timed out after {timeout}s: {endpoint}",
142
+ code="TIMEOUT",
143
+ details={"endpoint": endpoint, "timeout": timeout}
144
+ )
145
+ except requests.exceptions.ConnectionError as e:
146
+ raise RairosError(
147
+ f"Connection failed: {str(e)}",
148
+ code="CONNECTION_ERROR",
149
+ details={"endpoint": endpoint}
150
+ )
151
+ except RairosError:
152
+ raise
153
+ except Exception as e:
154
+ raise RairosError(
155
+ f"Request failed: {str(e)}",
156
+ code="UNKNOWN_ERROR",
157
+ details={"endpoint": endpoint, "error": str(e)}
158
+ )
159
+
160
+ # Authentication
161
+ def register(self, email: str, password: str) -> Dict[str, Any]:
162
+ """Register a new user.
163
+
164
+ Args:
165
+ email: User email
166
+ password: User password (min 8 characters)
167
+
168
+ Returns:
169
+ Auth response with user_id and API key
170
+
171
+ Raises:
172
+ ValidationError: If email or password is invalid
173
+ """
174
+ return self._request(
175
+ "POST",
176
+ "/auth/register",
177
+ json={"email": email, "password": password}
178
+ )
179
+
180
+ def login(self, email: str, password: str) -> Dict[str, Any]:
181
+ """Login and get a new API key.
182
+
183
+ Args:
184
+ email: User email
185
+ password: User password
186
+
187
+ Returns:
188
+ Auth response with user_id and API key
189
+ """
190
+ return self._request(
191
+ "POST",
192
+ "/auth/login",
193
+ json={"email": email, "password": password}
194
+ )
195
+
196
+ # API Keys
197
+ def list_keys(self) -> List[Dict[str, Any]]:
198
+ """List all API keys for the current user.
199
+
200
+ Returns:
201
+ List of API key objects
202
+ """
203
+ return self._request("GET", "/keys")
204
+
205
+ def create_key(self, name: Optional[str] = None) -> Dict[str, Any]:
206
+ """Create a new API key.
207
+
208
+ Args:
209
+ name: Optional name for the key
210
+
211
+ Returns:
212
+ New API key details including the raw key (only shown once)
213
+ """
214
+ return self._request(
215
+ "POST",
216
+ "/keys",
217
+ json={"name": name} if name else {}
218
+ )
219
+
220
+ def rotate_key(
221
+ self,
222
+ key_id: str,
223
+ grace_period_hours: int = 24
224
+ ) -> Dict[str, Any]:
225
+ """Rotate an API key with a grace period.
226
+
227
+ The old key remains valid during the grace period.
228
+
229
+ Args:
230
+ key_id: ID of the key to rotate
231
+ grace_period_hours: Hours until old key expires (default: 24)
232
+
233
+ Returns:
234
+ New key details and old key expiration time
235
+ """
236
+ return self._request(
237
+ "POST",
238
+ "/keys/rotate",
239
+ json={
240
+ "key_id": key_id,
241
+ "grace_period_hours": grace_period_hours
242
+ }
243
+ )
244
+
245
+ # Usage
246
+ def get_usage(self) -> Dict[str, Any]:
247
+ """Get current API usage statistics.
248
+
249
+ Returns:
250
+ Usage statistics including tier, requests used/remaining
251
+ """
252
+ return self._request("GET", "/usage")
253
+
254
+ def get_usage_dashboard(self) -> Dict[str, Any]:
255
+ """Get detailed usage dashboard with breakdowns.
256
+
257
+ Returns:
258
+ Detailed usage stats including endpoint breakdown and trends
259
+ """
260
+ return self._request("GET", "/usage/dashboard")
261
+
262
+ # Papers
263
+ def search_papers(
264
+ self,
265
+ query: Optional[str] = None,
266
+ page: int = 1,
267
+ per_page: int = 20
268
+ ) -> Dict[str, Any]:
269
+ """Search papers.
270
+
271
+ Args:
272
+ query: Search query (searches title and abstract)
273
+ page: Page number (default: 1)
274
+ per_page: Results per page (default: 20, max: 100)
275
+
276
+ Returns:
277
+ Paginated paper results
278
+ """
279
+ params = {"page": page, "per_page": per_page}
280
+ if query:
281
+ params["q"] = query
282
+
283
+ return self._request("GET", "/papers/search", params=params)
284
+
285
+ def get_paper(self, paper_id: str) -> Dict[str, Any]:
286
+ """Get a specific paper by ID.
287
+
288
+ Args:
289
+ paper_id: Paper UUID
290
+
291
+ Returns:
292
+ Paper details
293
+ """
294
+ return self._request("GET", f"/papers/{paper_id}")
295
+
296
+ # Gap Detection (Pro+)
297
+ def detect_gap(self, query: str, **kwargs) -> Dict[str, Any]:
298
+ """Detect research gaps for a query. Requires Pro tier or higher.
299
+
300
+ Args:
301
+ query: Research query to analyze
302
+ **kwargs: Additional parameters for gap detection
303
+
304
+ Returns:
305
+ Gap detection results with identified gaps
306
+
307
+ Raises:
308
+ ForbiddenError: If tier is insufficient
309
+ """
310
+ return self._request(
311
+ "POST",
312
+ "/gap/detect",
313
+ json={"query": query, **kwargs}
314
+ )
315
+
316
+ # Research (Team+)
317
+ def run_research(self, query: str, **kwargs) -> Dict[str, Any]:
318
+ """Run automated research. Requires Team tier or higher.
319
+
320
+ Args:
321
+ query: Research query
322
+ **kwargs: Additional parameters for research
323
+
324
+ Returns:
325
+ Research results
326
+
327
+ Raises:
328
+ ForbiddenError: If tier is insufficient
329
+ """
330
+ return self._request(
331
+ "POST",
332
+ "/research/run",
333
+ json={"query": query, **kwargs}
334
+ )
335
+
336
+ # Subscriptions
337
+ def get_tiers(self) -> Dict[str, Any]:
338
+ """Get available subscription tiers.
339
+
340
+ Returns:
341
+ List of available tiers with pricing
342
+ """
343
+ return self._request("GET", "/tiers")
344
+
345
+ def create_checkout(
346
+ self,
347
+ price_id: str,
348
+ success_url: str = "https://rairos.ai/success",
349
+ cancel_url: str = "https://rairos.ai/pricing"
350
+ ) -> Dict[str, Any]:
351
+ """Create a Stripe checkout session for subscription.
352
+
353
+ Args:
354
+ price_id: Stripe price ID for the tier
355
+ success_url: URL to redirect on success
356
+ cancel_url: URL to redirect on cancel
357
+
358
+ Returns:
359
+ Checkout URL and session ID
360
+ """
361
+ return self._request(
362
+ "POST",
363
+ "/subscription/checkout",
364
+ json={
365
+ "price_id": price_id,
366
+ "success_url": success_url,
367
+ "cancel_url": cancel_url
368
+ }
369
+ )
370
+
371
+ def create_portal(self, return_url: str = "https://rairos.ai/dashboard") -> Dict[str, Any]:
372
+ """Create a Stripe customer portal session.
373
+
374
+ Args:
375
+ return_url: URL to redirect after using the portal
376
+
377
+ Returns:
378
+ Portal URL
379
+ """
380
+ return self._request(
381
+ "POST",
382
+ "/subscription/portal",
383
+ json={"return_url": return_url}
384
+ )
385
+
386
+ def get_subscription_status(self) -> Dict[str, Any]:
387
+ """Get current subscription status.
388
+
389
+ Returns:
390
+ Subscription status including tier and Stripe info
391
+ """
392
+ return self._request("GET", "/subscription/status")
@@ -0,0 +1,181 @@
1
+ """Rairos SDK Exceptions."""
2
+
3
+ from datetime import datetime
4
+ from typing import Optional
5
+
6
+
7
+ class RairosError(Exception):
8
+ """Base exception for Rairos SDK errors.
9
+
10
+ Attributes:
11
+ message: Error message
12
+ code: Error code (e.g., 'RATE_LIMITED', 'AUTH')
13
+ status_code: HTTP status code if available
14
+ details: Additional error details from API
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ message: str,
20
+ code: str = "ERROR",
21
+ status_code: Optional[int] = None,
22
+ details: Optional[dict] = None
23
+ ):
24
+ super().__init__(message)
25
+ self.message = message
26
+ self.code = code
27
+ self.status_code = status_code
28
+ self.details = details or {}
29
+
30
+ def __str__(self) -> str:
31
+ if self.code != "ERROR":
32
+ return f"[{self.code}] {self.message}"
33
+ return self.message
34
+
35
+
36
+ class AuthenticationError(RairosError):
37
+ """Raised when authentication fails (invalid or expired API key).
38
+
39
+ HTTP Status: 401
40
+ Error Codes: AUTH, INVALID_API_KEY, UNAUTHORIZED
41
+ """
42
+
43
+ def __init__(self, message: str = "Authentication failed", details: Optional[dict] = None):
44
+ super().__init__(message, code="AUTH", status_code=401, details=details)
45
+
46
+
47
+ class RateLimitError(RairosError):
48
+ """Raised when rate limit is exceeded.
49
+
50
+ HTTP Status: 429
51
+ Error Code: RATE_LIMITED
52
+
53
+ Attributes:
54
+ limit: The rate limit that was exceeded
55
+ reset_at: When the rate limit will reset
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ message: str = "Rate limit exceeded",
61
+ limit: Optional[int] = None,
62
+ reset_at: Optional[datetime] = None,
63
+ details: Optional[dict] = None
64
+ ):
65
+ super().__init__(message, code="RATE_LIMITED", status_code=429, details=details)
66
+ self.limit = limit
67
+ self.reset_at = reset_at
68
+
69
+ def __str__(self) -> str:
70
+ parts = [f"[RATE_LIMITED] {self.message}"]
71
+ if self.limit:
72
+ parts.append(f"Limit: {self.limit}")
73
+ if self.reset_at:
74
+ parts.append(f"Resets at: {self.reset_at.isoformat()}")
75
+ return " | ".join(parts)
76
+
77
+
78
+ class ValidationError(RairosError):
79
+ """Raised when request validation fails.
80
+
81
+ HTTP Status: 400
82
+ Error Codes: VALIDATION_ERROR, BAD_REQUEST
83
+ """
84
+
85
+ def __init__(self, message: str = "Validation failed", details: Optional[dict] = None):
86
+ super().__init__(message, code="VALIDATION_ERROR", status_code=400, details=details)
87
+
88
+
89
+ class NotFoundError(RairosError):
90
+ """Raised when a resource is not found.
91
+
92
+ HTTP Status: 404
93
+ Error Codes: NOT_FOUND
94
+ """
95
+
96
+ def __init__(self, message: str = "Resource not found", details: Optional[dict] = None):
97
+ super().__init__(message, code="NOT_FOUND", status_code=404, details=details)
98
+
99
+
100
+ class ForbiddenError(RairosError):
101
+ """Raised when access to a resource is forbidden (insufficient tier).
102
+
103
+ HTTP Status: 403
104
+ Error Codes: FORBIDDEN, INSUFFICIENT_PERMISSIONS
105
+ """
106
+
107
+ def __init__(self, message: str = "Access forbidden", details: Optional[dict] = None):
108
+ super().__init__(message, code="FORBIDDEN", status_code=403, details=details)
109
+
110
+
111
+ class ServerError(RairosError):
112
+ """Raised when an internal server error occurs.
113
+
114
+ HTTP Status: 500+
115
+ Error Codes: INTERNAL_ERROR, DATABASE_ERROR, SERVER_ERROR
116
+ """
117
+
118
+ def __init__(self, message: str = "Internal server error", details: Optional[dict] = None):
119
+ super().__init__(message, code="SERVER_ERROR", status_code=500, details=details)
120
+
121
+
122
+ class PaymentError(RairosError):
123
+ """Raised when a payment-related error occurs.
124
+
125
+ HTTP Status: 402
126
+ Error Codes: PAYMENT_ERROR
127
+ """
128
+
129
+ def __init__(self, message: str = "Payment error", details: Optional[dict] = None):
130
+ super().__init__(message, code="PAYMENT_ERROR", status_code=402, details=details)
131
+
132
+
133
+ def parse_error_response(response_data: dict) -> tuple[str, dict]:
134
+ """Parse error response from API.
135
+
136
+ Returns:
137
+ Tuple of (error_code, error_details)
138
+ """
139
+ error = response_data.get("error", {})
140
+ code = error.get("code", "ERROR")
141
+ details = {
142
+ "limit": error.get("limit"),
143
+ "reset_at": error.get("reset_at"),
144
+ }
145
+ return code, details
146
+
147
+
148
+ def raise_from_response(status_code: int, response_data: dict):
149
+ """Raise appropriate exception from API error response.
150
+
151
+ Args:
152
+ status_code: HTTP status code
153
+ response_data: Parsed JSON response
154
+
155
+ Raises:
156
+ Appropriate RairosError subclass
157
+ """
158
+ code, details = parse_error_response(response_data)
159
+ message = response_data.get("error", {}).get("message", "Unknown error")
160
+
161
+ error_mapping = {
162
+ 400: (ValidationError, "VALIDATION_ERROR"),
163
+ 401: (AuthenticationError, "AUTH"),
164
+ 403: (ForbiddenError, "FORBIDDEN"),
165
+ 404: (NotFoundError, "NOT_FOUND"),
166
+ 402: (PaymentError, "PAYMENT_ERROR"),
167
+ 429: (RateLimitError, "RATE_LIMITED"),
168
+ }
169
+
170
+ if status_code in error_mapping:
171
+ exc_class, expected_code = error_mapping[status_code]
172
+ if status_code == 429 and details.get("limit"):
173
+ reset_at_str = details.get("reset_at")
174
+ reset_at = datetime.fromisoformat(reset_at_str.replace("Z", "+00:00")) if reset_at_str else None
175
+ raise exc_class(message, limit=details["limit"], reset_at=reset_at, details=details)
176
+ raise exc_class(message, details=details)
177
+
178
+ if status_code >= 500:
179
+ raise ServerError(message, details=details)
180
+
181
+ raise RairosError(message, code=code, status_code=status_code, details=details)
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: rairos
3
+ Version: 0.2.0
4
+ Summary: Python SDK for Rairos API
5
+ Author-email: Rairos Team <team@rairos.ai>
6
+ License: MIT
7
+ Project-URL: Homepage, https://rairos.ai
8
+ Project-URL: Documentation, https://docs.rairos.ai
9
+ Project-URL: Repository, https://github.com/shushuzn/Rairos
10
+ Project-URL: Changelog, https://github.com/shushuzn/Rairos/blob/main/sdks/python/CHANGELOG.md
11
+ Keywords: rairos,api,research,ai
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: requests>=2.28.0
23
+ Requires-Dist: urllib3>=2.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
26
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
27
+ Requires-Dist: black>=23.0.0; extra == "dev"
28
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
29
+
30
+ # Rairos Python SDK
31
+
32
+ [![PyPI version](https://img.shields.io/pypi/v/rairos.svg)](https://pypi.org/project/rairos/)
33
+ [![Python versions](https://img.shields.io/pypi/pyversions/rairos.svg)](https://pypi.org/project/rairos/)
34
+ [![License](https://img.shields.io/pypi/l/rairos.svg)](https://github.com/shushuzn/Rairos/blob/main/sdks/python/pyproject.toml)
35
+
36
+ Python SDK for the Rairos API platform.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ # From PyPI (coming soon)
42
+ pip install rairos
43
+
44
+ # From source
45
+ cd sdks/python && pip install -e . && cd ../..
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ ```python
51
+ from rairos import RairosClient
52
+
53
+ # Initialize client
54
+ client = RairosClient(api_key="your_api_key_here")
55
+
56
+ # Search papers
57
+ results = client.search_papers(query="machine learning", page=1, per_page=20)
58
+ print(results)
59
+
60
+ # Detect research gaps (requires Pro tier)
61
+ gaps = client.detect_gap(query="neural network architecture")
62
+ print(gaps)
63
+
64
+ # Get usage statistics
65
+ usage = client.get_usage()
66
+ print(usage)
67
+ ```
68
+
69
+ ## Authentication
70
+
71
+ You can provide your API key in three ways:
72
+
73
+ 1. **Environment variable** (recommended):
74
+ ```bash
75
+ export RAIROS_API_KEY=your_api_key_here
76
+ ```
77
+
78
+ 2. **Constructor parameter**:
79
+ ```python
80
+ client = RairosClient(api_key="your_api_key_here")
81
+ ```
82
+
83
+ 3. **Register/Login** to get a new API key:
84
+ ```python
85
+ # Register
86
+ auth = client.register(email="user@example.com", password="secure_password")
87
+
88
+ # Login
89
+ auth = client.login(email="user@example.com", password="secure_password")
90
+ print(auth["api_key"]) # Use this key
91
+ ```
92
+
93
+ ## API Reference
94
+
95
+ ### Papers
96
+
97
+ ```python
98
+ # Search papers
99
+ results = client.search_papers(query="quantum computing")
100
+
101
+ # Get specific paper
102
+ paper = client.get_paper(paper_id="uuid-here")
103
+ ```
104
+
105
+ ### Gap Detection (Pro tier)
106
+
107
+ ```python
108
+ # Detect research gaps
109
+ gaps = client.detect_gap(
110
+ query="What are the gaps in transformer architecture research?",
111
+ categories=["cs.AI", "cs.LG"]
112
+ )
113
+ ```
114
+
115
+ ### Research (Team tier)
116
+
117
+ ```python
118
+ # Run automated research
119
+ results = client.run_research(
120
+ query="Analyze the state of AI safety research in 2025"
121
+ )
122
+ ```
123
+
124
+ ### Subscriptions
125
+
126
+ ```python
127
+ # Get available tiers
128
+ tiers = client.get_tiers()
129
+
130
+ # Create checkout session
131
+ checkout = client.create_checkout(
132
+ price_id="price_pro_monthly",
133
+ success_url="https://yourapp.com/success",
134
+ cancel_url="https://yourapp.com/pricing"
135
+ )
136
+ print(checkout["checkout_url"]) # Redirect user to this URL
137
+
138
+ # Get subscription status
139
+ status = client.get_subscription_status()
140
+ ```
141
+
142
+ ## Error Handling
143
+
144
+ ```python
145
+ from rairos import (
146
+ RairosClient,
147
+ RairosError,
148
+ AuthenticationError,
149
+ RateLimitError
150
+ )
151
+
152
+ try:
153
+ client = RairosClient(api_key="invalid_key")
154
+ results = client.search_papers(query="test")
155
+ except AuthenticationError:
156
+ print("Invalid API key")
157
+ except RateLimitError:
158
+ print("Rate limit exceeded - wait and retry")
159
+ except RairosError as e:
160
+ print(f"API error: {e.message}")
161
+ ```
162
+
163
+ ## License
164
+
165
+ MIT License
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ rairos/__init__.py
4
+ rairos/client.py
5
+ rairos/exceptions.py
6
+ rairos.egg-info/PKG-INFO
7
+ rairos.egg-info/SOURCES.txt
8
+ rairos.egg-info/dependency_links.txt
9
+ rairos.egg-info/requires.txt
10
+ rairos.egg-info/top_level.txt
@@ -0,0 +1,8 @@
1
+ requests>=2.28.0
2
+ urllib3>=2.0.0
3
+
4
+ [dev]
5
+ pytest>=7.0.0
6
+ pytest-asyncio>=0.21.0
7
+ black>=23.0.0
8
+ mypy>=1.0.0
@@ -0,0 +1 @@
1
+ rairos
rairos-0.2.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+