fix: wrap backend-native exceptions; correct setup/desc docs (v0.1.3)
- CurlCffi/Noble raw_request translate backend-native network errors (curl_cffi RequestException, noble_tls TLSClientException) into aiohttp.ClientError so the bare request() path gives the same typed-failure contract as the aiohttp backend (L6) - Noble.setup uses download_if_necessary (the current noble_tls API), with update_if_necessary only as a fallback; docstring/CLAUDE.md no longer claim the dead 'refreshes an existing one' path (L7) - pyproject description says composition (one injectable TLSSession), not the old 'ExtendedSession subclasses' (L8). Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
parent
5eb689ba73
commit
ccea880df0
12
README.md
12
README.md
@ -22,17 +22,17 @@ you want; importing the package never fails because an extra is missing.
|
|||||||
`requirements.txt` (pick the extra you need):
|
`requirements.txt` (pick the extra you need):
|
||||||
|
|
||||||
```
|
```
|
||||||
aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2
|
aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3
|
||||||
aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2
|
aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3
|
||||||
aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2
|
aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3
|
||||||
```
|
```
|
||||||
|
|
||||||
Direct:
|
Direct:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install "aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2"
|
pip install "aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3"
|
||||||
pip install "aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2"
|
pip install "aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3"
|
||||||
pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2"
|
pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3"
|
||||||
```
|
```
|
||||||
|
|
||||||
- `[curl]` → curl_cffi backend · `[noble]` → noble_tls backend · `[all]` → both.
|
- `[curl]` → curl_cffi backend · `[noble]` → noble_tls backend · `[all]` → both.
|
||||||
|
|||||||
@ -4,8 +4,8 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "aioweb_tls"
|
name = "aioweb_tls"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
description = "TLS-fingerprinting backends for aioweb — curl_cffi / noble_tls ExtendedSession subclasses, config-free, installable."
|
description = "TLS-fingerprinting backends (curl_cffi / noble_tls) for aioweb via one injectable TLSSession, config-free, installable."
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.0",
|
"aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.0",
|
||||||
|
|||||||
@ -15,10 +15,23 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
from aioweb import Response
|
from aioweb import Response
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _as_client_error(error: Exception, backend: str) -> aiohttp.ClientError:
|
||||||
|
"""wrap a backend-native network exception as an aiohttp.ClientError
|
||||||
|
|
||||||
|
aioweb's request() only re-wraps aiohttp.ClientError; curl_cffi raises
|
||||||
|
RequestException(OSError) and noble_tls raises TLSClientException(IOError), neither
|
||||||
|
of which is an aiohttp.ClientError. translating here gives TLS backends the same
|
||||||
|
typed failure contract as the aiohttp path on the bare request() route.
|
||||||
|
"""
|
||||||
|
return aiohttp.ClientError(f"{backend} request failed: {error}")
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from curl_cffi import AsyncSession as _CurlAsyncSession
|
from curl_cffi import AsyncSession as _CurlAsyncSession
|
||||||
_CURL_ERROR = None
|
_CURL_ERROR = None
|
||||||
@ -94,10 +107,13 @@ class CurlCffi:
|
|||||||
if proxy:
|
if proxy:
|
||||||
kwargs["proxy"] = proxy
|
kwargs["proxy"] = proxy
|
||||||
|
|
||||||
response = await session.request(method, url, impersonate=impersonate, **kwargs)
|
try:
|
||||||
content = response.content
|
response = await session.request(method, url, impersonate=impersonate, **kwargs)
|
||||||
if content is None:
|
except aiohttp.ClientError:
|
||||||
content = response.text.encode() if response.text else b""
|
raise
|
||||||
|
except Exception as error:
|
||||||
|
raise _as_client_error(error, "curl_cffi") from error
|
||||||
|
content = response.content if response.content is not None else b""
|
||||||
return Response(
|
return Response(
|
||||||
status_code=response.status_code,
|
status_code=response.status_code,
|
||||||
headers=dict(response.headers),
|
headers=dict(response.headers),
|
||||||
@ -161,9 +177,9 @@ class Noble:
|
|||||||
async def setup(self) -> None:
|
async def setup(self) -> None:
|
||||||
"""fetch the noble_tls Go shared library once; idempotent and concurrency-safe
|
"""fetch the noble_tls Go shared library once; idempotent and concurrency-safe
|
||||||
|
|
||||||
download_if_necessary handles the first-time fetch (no lib present);
|
uses noble_tls.download_if_necessary (the current API: it fetches the asset on
|
||||||
update_if_necessary refreshes an existing one. try download first so a
|
first use and no-ops when it already exists). older noble_tls without that name
|
||||||
clean environment works, falling back to update.
|
is handled via update_if_necessary as a fallback.
|
||||||
|
|
||||||
guarded by an asyncio.Lock with a check-lock-recheck so concurrent first
|
guarded by an asyncio.Lock with a check-lock-recheck so concurrent first
|
||||||
requests don't both run the fetch: the fast path returns once _updated is
|
requests don't both run the fetch: the fast path returns once _updated is
|
||||||
@ -177,7 +193,7 @@ class Noble:
|
|||||||
download = getattr(noble_tls, "download_if_necessary", None)
|
download = getattr(noble_tls, "download_if_necessary", None)
|
||||||
if download is not None:
|
if download is not None:
|
||||||
await download()
|
await download()
|
||||||
else:
|
elif hasattr(noble_tls, "update_if_necessary"):
|
||||||
await noble_tls.update_if_necessary()
|
await noble_tls.update_if_necessary()
|
||||||
self._updated = True
|
self._updated = True
|
||||||
|
|
||||||
@ -210,7 +226,12 @@ class Noble:
|
|||||||
if proxy:
|
if proxy:
|
||||||
kwargs["proxy"] = proxy
|
kwargs["proxy"] = proxy
|
||||||
|
|
||||||
response = await session.execute_request(method=method.upper(), url=url, **kwargs)
|
try:
|
||||||
|
response = await session.execute_request(method=method.upper(), url=url, **kwargs)
|
||||||
|
except aiohttp.ClientError:
|
||||||
|
raise
|
||||||
|
except Exception as error:
|
||||||
|
raise _as_client_error(error, "noble_tls") from error
|
||||||
content = getattr(response, "content", None)
|
content = getattr(response, "content", None)
|
||||||
if content is None:
|
if content is None:
|
||||||
text = getattr(response, "text", "") or ""
|
text = getattr(response, "text", "") or ""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user