dataapp-resource-proxy 0.1.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.
- dataapp_resource_proxy/__init__.py +15 -0
- dataapp_resource_proxy/__main__.py +65 -0
- dataapp_resource_proxy/_local_config.py +286 -0
- dataapp_resource_proxy/client.py +1813 -0
- dataapp_resource_proxy/errors.py +16 -0
- dataapp_resource_proxy/handles.py +199 -0
- dataapp_resource_proxy/py.typed +0 -0
- dataapp_resource_proxy-0.1.0.dist-info/METADATA +6 -0
- dataapp_resource_proxy-0.1.0.dist-info/RECORD +10 -0
- dataapp_resource_proxy-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Public client errors."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ResourceProxyError(RuntimeError):
|
|
5
|
+
"""A managed resource request failed."""
|
|
6
|
+
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
message: str,
|
|
10
|
+
*,
|
|
11
|
+
code: str = "resource-proxy-error",
|
|
12
|
+
status_code: int | None = None,
|
|
13
|
+
) -> None:
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
self.code = code
|
|
16
|
+
self.status_code = status_code
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Owned resource handles returned by :mod:`dataapp_resource_proxy`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .client import ResourceProxy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ProxyResource:
|
|
16
|
+
"""One DataOS proxy lease owned by this client."""
|
|
17
|
+
|
|
18
|
+
client: ResourceProxy = field(repr=False)
|
|
19
|
+
lease_id: str
|
|
20
|
+
proxy_url: str = field(repr=False)
|
|
21
|
+
expires_at: datetime | None = None
|
|
22
|
+
credential_generation: int | None = None
|
|
23
|
+
network: str | None = None
|
|
24
|
+
node_id: str | None = None
|
|
25
|
+
line_tier: str | None = None
|
|
26
|
+
proxy_group: str | None = None
|
|
27
|
+
supplier_code: str | None = None
|
|
28
|
+
supplier_route_id: str | None = None
|
|
29
|
+
supplier_account_ref: str | None = None
|
|
30
|
+
line_policy_version: str | None = None
|
|
31
|
+
_closed: bool = field(default=False, init=False, repr=False)
|
|
32
|
+
_lock: asyncio.Lock = field(default_factory=asyncio.Lock, init=False, repr=False)
|
|
33
|
+
|
|
34
|
+
def __repr__(self) -> str:
|
|
35
|
+
return f"ProxyResource(lease_id={self.lease_id!r}, proxy_url=<redacted>)"
|
|
36
|
+
|
|
37
|
+
async def renew(self) -> None:
|
|
38
|
+
async with self._lock:
|
|
39
|
+
self._require_open()
|
|
40
|
+
value = await self.client._renew_proxy(self.lease_id)
|
|
41
|
+
self._update(value)
|
|
42
|
+
|
|
43
|
+
async def refresh(self) -> None:
|
|
44
|
+
"""Refresh safe lease metadata without changing credentials."""
|
|
45
|
+
async with self._lock:
|
|
46
|
+
self._require_open()
|
|
47
|
+
value = await self.client._get_proxy(self.lease_id)
|
|
48
|
+
self._update(value)
|
|
49
|
+
|
|
50
|
+
async def rotate_credentials(self) -> None:
|
|
51
|
+
"""Rotate only the DataOS gateway credential, not the upstream exit IP."""
|
|
52
|
+
async with self._lock:
|
|
53
|
+
self._require_open()
|
|
54
|
+
value = await self.client._rotate_proxy(
|
|
55
|
+
self.lease_id, credential_generation=self.credential_generation
|
|
56
|
+
)
|
|
57
|
+
self._update(value)
|
|
58
|
+
|
|
59
|
+
async def close(self) -> None:
|
|
60
|
+
async with self._lock:
|
|
61
|
+
if self._closed:
|
|
62
|
+
return
|
|
63
|
+
await self.client._release_proxy(self.lease_id)
|
|
64
|
+
self._closed = True
|
|
65
|
+
self.client._forget(self)
|
|
66
|
+
|
|
67
|
+
def _update(self, value: dict[str, Any]) -> None:
|
|
68
|
+
if "proxy_url" in value:
|
|
69
|
+
self.proxy_url = _required_text(value, "proxy_url")
|
|
70
|
+
if "expires_at" in value:
|
|
71
|
+
self.expires_at = _datetime(value.get("expires_at"))
|
|
72
|
+
if "credential_generation" in value:
|
|
73
|
+
generation = value.get("credential_generation")
|
|
74
|
+
self.credential_generation = int(generation) if generation is not None else None
|
|
75
|
+
if "network" in value:
|
|
76
|
+
self.network = _optional_text(value.get("network"))
|
|
77
|
+
if "node_id" in value:
|
|
78
|
+
self.node_id = _optional_text(value.get("node_id"))
|
|
79
|
+
if "line_tier" in value:
|
|
80
|
+
self.line_tier = _optional_text(value.get("line_tier"))
|
|
81
|
+
if "proxy_group" in value:
|
|
82
|
+
self.proxy_group = _optional_text(value.get("proxy_group"))
|
|
83
|
+
if "supplier_code" in value:
|
|
84
|
+
self.supplier_code = _optional_text(value.get("supplier_code"))
|
|
85
|
+
if "supplier_route_id" in value:
|
|
86
|
+
self.supplier_route_id = _optional_text(value.get("supplier_route_id"))
|
|
87
|
+
if "supplier_account_ref" in value:
|
|
88
|
+
self.supplier_account_ref = _optional_text(value.get("supplier_account_ref"))
|
|
89
|
+
if "line_policy_version" in value:
|
|
90
|
+
self.line_policy_version = _optional_text(value.get("line_policy_version"))
|
|
91
|
+
|
|
92
|
+
def _require_open(self) -> None:
|
|
93
|
+
if self._closed:
|
|
94
|
+
raise RuntimeError("proxy resource is closed")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass(frozen=True)
|
|
98
|
+
class CaptchaTask:
|
|
99
|
+
"""Node-owned task snapshot; solver output still needs site acceptance."""
|
|
100
|
+
|
|
101
|
+
task_id: str
|
|
102
|
+
status: str
|
|
103
|
+
token: str | None = field(default=None, repr=False)
|
|
104
|
+
text: str | None = field(default=None, repr=False)
|
|
105
|
+
user_agent: str | None = field(default=None, repr=False)
|
|
106
|
+
error_code: str | None = None
|
|
107
|
+
node_id: str | None = None
|
|
108
|
+
network: str | None = None
|
|
109
|
+
data: dict[str, Any] | None = field(default=None, repr=False)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True)
|
|
113
|
+
class CaptchaPluginGrant:
|
|
114
|
+
"""Short-lived permission for the managed extension adapter only."""
|
|
115
|
+
|
|
116
|
+
api_url: str
|
|
117
|
+
client_key: str = field(repr=False)
|
|
118
|
+
expires_at: datetime
|
|
119
|
+
node_id: str
|
|
120
|
+
network: str
|
|
121
|
+
protocol_version: int
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class BrowserResource:
|
|
126
|
+
"""One managed browser session owned by this client."""
|
|
127
|
+
|
|
128
|
+
client: ResourceProxy = field(repr=False)
|
|
129
|
+
resource_id: str
|
|
130
|
+
status: str
|
|
131
|
+
session_deadline: datetime | None = None
|
|
132
|
+
network: str | None = None
|
|
133
|
+
node_id: str | None = None
|
|
134
|
+
_closed: bool = field(default=False, init=False, repr=False)
|
|
135
|
+
_lock: asyncio.Lock = field(default_factory=asyncio.Lock, init=False, repr=False)
|
|
136
|
+
|
|
137
|
+
async def connection(self) -> tuple[str, dict[str, str]]:
|
|
138
|
+
async with self._lock:
|
|
139
|
+
self._require_open()
|
|
140
|
+
value = await self.client._browser_connection(self.resource_id)
|
|
141
|
+
ws_url = _required_text(value, "ws_url")
|
|
142
|
+
raw_headers = value.get("headers")
|
|
143
|
+
if not isinstance(raw_headers, dict) or not all(
|
|
144
|
+
isinstance(key, str) and isinstance(item, str)
|
|
145
|
+
for key, item in raw_headers.items()
|
|
146
|
+
):
|
|
147
|
+
raise ValueError("resource service returned invalid browser headers")
|
|
148
|
+
return ws_url, dict(raw_headers)
|
|
149
|
+
|
|
150
|
+
async def refresh(self) -> None:
|
|
151
|
+
"""Refresh the browser status and deadline."""
|
|
152
|
+
async with self._lock:
|
|
153
|
+
self._require_open()
|
|
154
|
+
value = await self.client._get_browser(self.resource_id)
|
|
155
|
+
if "status" in value:
|
|
156
|
+
self.status = str(value["status"])
|
|
157
|
+
if "session_deadline" in value or "expires_at" in value:
|
|
158
|
+
self.session_deadline = _datetime(
|
|
159
|
+
value.get("session_deadline") or value.get("expires_at")
|
|
160
|
+
)
|
|
161
|
+
if "network" in value:
|
|
162
|
+
self.network = _optional_text(value.get("network"))
|
|
163
|
+
if "node_id" in value:
|
|
164
|
+
self.node_id = _optional_text(value.get("node_id"))
|
|
165
|
+
|
|
166
|
+
async def close(self) -> None:
|
|
167
|
+
async with self._lock:
|
|
168
|
+
if self._closed:
|
|
169
|
+
return
|
|
170
|
+
await self.client._release_browser(self.resource_id)
|
|
171
|
+
self._closed = True
|
|
172
|
+
self.client._forget(self)
|
|
173
|
+
|
|
174
|
+
def _require_open(self) -> None:
|
|
175
|
+
if self._closed:
|
|
176
|
+
raise RuntimeError("browser resource is closed")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _required_text(value: dict[str, Any], name: str) -> str:
|
|
180
|
+
result = value.get(name)
|
|
181
|
+
if not isinstance(result, str) or not result:
|
|
182
|
+
raise ValueError(f"resource service returned invalid {name}")
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _datetime(value: Any) -> datetime | None:
|
|
187
|
+
if value is None:
|
|
188
|
+
return None
|
|
189
|
+
if not isinstance(value, str):
|
|
190
|
+
raise ValueError("resource service returned an invalid timestamp")
|
|
191
|
+
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _optional_text(value: Any) -> str | None:
|
|
195
|
+
if value is None:
|
|
196
|
+
return None
|
|
197
|
+
if not isinstance(value, str) or not value:
|
|
198
|
+
raise ValueError("resource service returned invalid resource metadata")
|
|
199
|
+
return value
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
dataapp_resource_proxy/__init__.py,sha256=RHgtBZrNRZpzD5xTD8UaUCtOeT4M0rEYIAJqZg56UM0,405
|
|
2
|
+
dataapp_resource_proxy/__main__.py,sha256=nLRIRYJdKbIbfIa9aZUiHij28aS8cT-waGVWA0CUlzc,2417
|
|
3
|
+
dataapp_resource_proxy/_local_config.py,sha256=VeevtAmHCJZC1Gy4Xc6T0wXHweMDY5RopOBMwwcBNzo,11134
|
|
4
|
+
dataapp_resource_proxy/client.py,sha256=_ACYOpltkUsP8-pM_e3ZINCjnktORuQajXASYjmzSC8,76512
|
|
5
|
+
dataapp_resource_proxy/errors.py,sha256=ASFQ56uxUHlPx7wFySpk5sLO5-k4FcydCgW0dxY4zwo,378
|
|
6
|
+
dataapp_resource_proxy/handles.py,sha256=Y-bSgl7uCA-I01HhCTjMcNLCa4nCl081Bv6KLT0TgdI,7354
|
|
7
|
+
dataapp_resource_proxy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
dataapp_resource_proxy-0.1.0.dist-info/METADATA,sha256=FYg6XLXJMoLya6hpyEBqdTcz5_EqDqnS-GiY6PrazAE,196
|
|
9
|
+
dataapp_resource_proxy-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
10
|
+
dataapp_resource_proxy-0.1.0.dist-info/RECORD,,
|