pararamio-aio 2.1.1__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.
- pararamio_aio/__init__.py +78 -0
- pararamio_aio/_core/__init__.py +125 -0
- pararamio_aio/_core/_types.py +120 -0
- pararamio_aio/_core/base.py +143 -0
- pararamio_aio/_core/client_protocol.py +90 -0
- pararamio_aio/_core/constants/__init__.py +7 -0
- pararamio_aio/_core/constants/base.py +9 -0
- pararamio_aio/_core/constants/endpoints.py +84 -0
- pararamio_aio/_core/cookie_decorator.py +208 -0
- pararamio_aio/_core/cookie_manager.py +1222 -0
- pararamio_aio/_core/endpoints.py +67 -0
- pararamio_aio/_core/exceptions/__init__.py +6 -0
- pararamio_aio/_core/exceptions/auth.py +91 -0
- pararamio_aio/_core/exceptions/base.py +124 -0
- pararamio_aio/_core/models/__init__.py +17 -0
- pararamio_aio/_core/models/base.py +66 -0
- pararamio_aio/_core/models/chat.py +92 -0
- pararamio_aio/_core/models/post.py +65 -0
- pararamio_aio/_core/models/user.py +54 -0
- pararamio_aio/_core/py.typed +2 -0
- pararamio_aio/_core/utils/__init__.py +73 -0
- pararamio_aio/_core/utils/async_requests.py +417 -0
- pararamio_aio/_core/utils/auth_flow.py +202 -0
- pararamio_aio/_core/utils/authentication.py +235 -0
- pararamio_aio/_core/utils/captcha.py +92 -0
- pararamio_aio/_core/utils/helpers.py +336 -0
- pararamio_aio/_core/utils/http_client.py +199 -0
- pararamio_aio/_core/utils/requests.py +424 -0
- pararamio_aio/_core/validators.py +78 -0
- pararamio_aio/_types.py +29 -0
- pararamio_aio/client.py +989 -0
- pararamio_aio/constants/__init__.py +16 -0
- pararamio_aio/cookie_manager.py +15 -0
- pararamio_aio/exceptions/__init__.py +31 -0
- pararamio_aio/exceptions/base.py +1 -0
- pararamio_aio/file_operations.py +232 -0
- pararamio_aio/models/__init__.py +32 -0
- pararamio_aio/models/activity.py +127 -0
- pararamio_aio/models/attachment.py +141 -0
- pararamio_aio/models/base.py +83 -0
- pararamio_aio/models/bot.py +274 -0
- pararamio_aio/models/chat.py +722 -0
- pararamio_aio/models/deferred_post.py +174 -0
- pararamio_aio/models/file.py +103 -0
- pararamio_aio/models/group.py +361 -0
- pararamio_aio/models/poll.py +275 -0
- pararamio_aio/models/post.py +643 -0
- pararamio_aio/models/team.py +403 -0
- pararamio_aio/models/user.py +239 -0
- pararamio_aio/py.typed +2 -0
- pararamio_aio/utils/__init__.py +18 -0
- pararamio_aio/utils/authentication.py +383 -0
- pararamio_aio/utils/requests.py +75 -0
- pararamio_aio-2.1.1.dist-info/METADATA +269 -0
- pararamio_aio-2.1.1.dist-info/RECORD +57 -0
- pararamio_aio-2.1.1.dist-info/WHEEL +5 -0
- pararamio_aio-2.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,208 @@
|
|
1
|
+
"""Decorator for automatic authentication error handling with cookie manager."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import functools
|
6
|
+
import inspect
|
7
|
+
import logging
|
8
|
+
from typing import Any, Callable, TypeVar
|
9
|
+
|
10
|
+
from .cookie_manager import (
|
11
|
+
AsyncCookieManager,
|
12
|
+
AsyncFileCookieManager,
|
13
|
+
AsyncRedisCookieManager,
|
14
|
+
CookieManager,
|
15
|
+
FileCookieManager,
|
16
|
+
RedisCookieManager,
|
17
|
+
)
|
18
|
+
|
19
|
+
log = logging.getLogger(__name__)
|
20
|
+
|
21
|
+
F = TypeVar('F', bound=Callable[..., Any])
|
22
|
+
|
23
|
+
|
24
|
+
async def _ensure_cookies_async(obj: Any, cookie_manager_attr: str) -> None:
|
25
|
+
"""Ensure cookies are loaded and refreshed for async operations."""
|
26
|
+
cookie_manager = getattr(obj, cookie_manager_attr, None)
|
27
|
+
if isinstance(cookie_manager, AsyncCookieManager):
|
28
|
+
# Ensure cookies are loaded
|
29
|
+
if not cookie_manager.has_cookies():
|
30
|
+
await cookie_manager.load_cookies()
|
31
|
+
# Check version and refresh if needed
|
32
|
+
await cookie_manager.refresh_if_needed()
|
33
|
+
|
34
|
+
|
35
|
+
def _ensure_cookies_sync(obj: Any, cookie_manager_attr: str) -> None:
|
36
|
+
"""Ensure cookies are loaded and refreshed for sync operations."""
|
37
|
+
cookie_manager = getattr(obj, cookie_manager_attr, None)
|
38
|
+
if isinstance(cookie_manager, CookieManager):
|
39
|
+
# Ensure cookies are loaded
|
40
|
+
if not cookie_manager.has_cookies():
|
41
|
+
cookie_manager.load_cookies()
|
42
|
+
# Check version and refresh if needed
|
43
|
+
cookie_manager.refresh_if_needed()
|
44
|
+
|
45
|
+
|
46
|
+
# List of error indicators for authentication failures
|
47
|
+
AUTH_ERROR_INDICATORS = ['401', 'unauthorized', 'authentication', 'auth failed']
|
48
|
+
|
49
|
+
|
50
|
+
def is_auth_error(error: Exception) -> bool:
|
51
|
+
"""Check if an exception is an authentication error."""
|
52
|
+
error_str = str(error).lower()
|
53
|
+
return any(indicator in error_str for indicator in AUTH_ERROR_INDICATORS)
|
54
|
+
|
55
|
+
|
56
|
+
def with_auth_retry(cookie_manager_attr: str = 'cookie_manager'):
|
57
|
+
"""Decorator to automatically handle authentication errors with cookie manager.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
cookie_manager_attr: Name of the attribute containing the CookieManager instance
|
61
|
+
|
62
|
+
Example:
|
63
|
+
class MyClient:
|
64
|
+
def __init__(self):
|
65
|
+
self.cookie_manager = FileCookieManager('/path/to/cookies')
|
66
|
+
|
67
|
+
@with_auth_retry()
|
68
|
+
def make_api_call(self):
|
69
|
+
# This will automatically retry with cookie refresh on auth errors
|
70
|
+
return self._do_api_request()
|
71
|
+
"""
|
72
|
+
|
73
|
+
def decorator(func: F) -> F:
|
74
|
+
if inspect.iscoroutinefunction(func):
|
75
|
+
# Async version
|
76
|
+
@functools.wraps(func)
|
77
|
+
async def async_wrapper(self, *args, **kwargs):
|
78
|
+
cookie_manager = getattr(self, cookie_manager_attr, None)
|
79
|
+
if not isinstance(cookie_manager, (CookieManager, AsyncCookieManager)):
|
80
|
+
# No cookie manager, just call the function
|
81
|
+
return await func(self, *args, **kwargs)
|
82
|
+
|
83
|
+
try:
|
84
|
+
return await func(self, *args, **kwargs)
|
85
|
+
except Exception as e:
|
86
|
+
# Check if this is an authentication error
|
87
|
+
if is_auth_error(e):
|
88
|
+
log.info('Authentication error in %s: %s', func.__name__, e)
|
89
|
+
|
90
|
+
# Create retry function
|
91
|
+
async def retry():
|
92
|
+
return await func(self, *args, **kwargs)
|
93
|
+
|
94
|
+
# Use cookie manager to handle the error
|
95
|
+
return await cookie_manager.handle_auth_error(retry)
|
96
|
+
# Not an auth error, re-raise
|
97
|
+
raise
|
98
|
+
|
99
|
+
return async_wrapper
|
100
|
+
|
101
|
+
# Sync version
|
102
|
+
@functools.wraps(func)
|
103
|
+
def sync_wrapper(self, *args, **kwargs):
|
104
|
+
cookie_manager = getattr(self, cookie_manager_attr, None)
|
105
|
+
if not isinstance(cookie_manager, CookieManager):
|
106
|
+
# No cookie manager, just call the function
|
107
|
+
return func(self, *args, **kwargs)
|
108
|
+
|
109
|
+
try:
|
110
|
+
return func(self, *args, **kwargs)
|
111
|
+
except Exception as e:
|
112
|
+
# Check if this is an authentication error
|
113
|
+
if is_auth_error(e):
|
114
|
+
log.info('Authentication error in %s: %s', func.__name__, e)
|
115
|
+
|
116
|
+
# Create retry function
|
117
|
+
def retry():
|
118
|
+
return func(self, *args, **kwargs)
|
119
|
+
|
120
|
+
# Use cookie manager to handle the error
|
121
|
+
return cookie_manager.handle_auth_error(retry)
|
122
|
+
# Not an auth error, re-raise
|
123
|
+
raise
|
124
|
+
|
125
|
+
return sync_wrapper
|
126
|
+
|
127
|
+
return decorator
|
128
|
+
|
129
|
+
|
130
|
+
def auth_required(cookie_manager_attr: str = 'cookie_manager'):
|
131
|
+
"""Decorator that ensures cookies are loaded before method execution.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
cookie_manager_attr: Name of the attribute containing the CookieManager instance
|
135
|
+
|
136
|
+
Example:
|
137
|
+
@auth_required()
|
138
|
+
def get_profile(self):
|
139
|
+
# Cookies will be loaded automatically if needed
|
140
|
+
return self.api_get('/user/profile')
|
141
|
+
"""
|
142
|
+
|
143
|
+
def decorator(func: F) -> F:
|
144
|
+
if inspect.iscoroutinefunction(func):
|
145
|
+
# Async version
|
146
|
+
@functools.wraps(func)
|
147
|
+
async def async_wrapper(self, *args, **kwargs):
|
148
|
+
await _ensure_cookies_async(self, cookie_manager_attr)
|
149
|
+
return await func(self, *args, **kwargs)
|
150
|
+
|
151
|
+
return async_wrapper
|
152
|
+
|
153
|
+
# Sync version
|
154
|
+
@functools.wraps(func)
|
155
|
+
def sync_wrapper(self, *args, **kwargs):
|
156
|
+
_ensure_cookies_sync(self, cookie_manager_attr)
|
157
|
+
return func(self, *args, **kwargs)
|
158
|
+
|
159
|
+
return sync_wrapper
|
160
|
+
|
161
|
+
return decorator
|
162
|
+
|
163
|
+
|
164
|
+
class CookieManagerMixin:
|
165
|
+
"""Mixin class to add cookie manager support to any client class.
|
166
|
+
|
167
|
+
Example:
|
168
|
+
class MyAPIClient(CookieManagerMixin):
|
169
|
+
def __init__(self, cookie_path: str):
|
170
|
+
self.init_cookie_manager(cookie_path)
|
171
|
+
|
172
|
+
@with_auth_retry()
|
173
|
+
def get_data(self):
|
174
|
+
return self.api_request('/data')
|
175
|
+
"""
|
176
|
+
|
177
|
+
cookie_manager: CookieManager | AsyncCookieManager
|
178
|
+
|
179
|
+
def init_cookie_manager(
|
180
|
+
self,
|
181
|
+
cookie_path: str | None = None,
|
182
|
+
redis_client: Any = None,
|
183
|
+
key_prefix: str = 'pararamio:cookies',
|
184
|
+
use_async: bool = False,
|
185
|
+
):
|
186
|
+
"""Initialize cookie manager.
|
187
|
+
|
188
|
+
Args:
|
189
|
+
cookie_path: Path to cookie file (for file-based storage)
|
190
|
+
redis_client: Redis client instance (for Redis-based storage)
|
191
|
+
key_prefix: Redis key prefix
|
192
|
+
use_async: Whether to use async cookie manager
|
193
|
+
"""
|
194
|
+
if redis_client:
|
195
|
+
# Use Redis-based manager
|
196
|
+
if use_async:
|
197
|
+
self.cookie_manager = AsyncRedisCookieManager(redis_client, key_prefix)
|
198
|
+
else:
|
199
|
+
self.cookie_manager = RedisCookieManager(redis_client, key_prefix)
|
200
|
+
elif cookie_path:
|
201
|
+
# Use file-based manager
|
202
|
+
if use_async:
|
203
|
+
self.cookie_manager = AsyncFileCookieManager(cookie_path)
|
204
|
+
else:
|
205
|
+
self.cookie_manager = FileCookieManager(cookie_path)
|
206
|
+
else:
|
207
|
+
msg = 'Either cookie_path or redis_client must be provided'
|
208
|
+
raise ValueError(msg)
|