Compare commits

..

No commits in common. "main" and "v0.1.2" have entirely different histories.
main ... v0.1.2

4 changed files with 21 additions and 58 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
# claude
.claude/
CLAUDE.md
# python
__pycache__/

View File

@ -22,17 +22,17 @@ you want; importing the package never fails because an extra is missing.
`requirements.txt` (pick the extra you need):
```
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.3
aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3
aioweb_tls[curl] @ 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.2
aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2
```
Direct:
```bash
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.3"
pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3"
pip install "aioweb_tls[curl] @ 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.2"
pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2"
```
- `[curl]` → curl_cffi backend · `[noble]` → noble_tls backend · `[all]` → both.
@ -44,8 +44,6 @@ pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-publi
Constructing a backend whose client isn't installed raises that `RuntimeError` at
construction, never at import.
Drop the `@v0.1.3` suffix from the line above to install the latest unpinned.
## curl_cffi backend
```python
@ -175,4 +173,4 @@ are separate signals. Use this as one component, not a complete anti-bot solutio
## Versioning
Releases are tagged `vX.Y.Z`. The install line above pins a release; drop the `@vX.Y.Z` suffix to install the latest unpinned. Pin deliberately for reproducible installs.
Tagged `vX.Y.Z`. Pin the tag in `requirements.txt`.

View File

@ -4,11 +4,11 @@ build-backend = "hatchling.build"
[project]
name = "aioweb_tls"
version = "0.1.3"
description = "TLS-fingerprinting backends (curl_cffi / noble_tls) for aioweb via one injectable TLSSession, config-free, installable."
version = "0.1.2"
description = "TLS-fingerprinting backends for aioweb — curl_cffi / noble_tls ExtendedSession subclasses, config-free, installable."
requires-python = ">=3.10"
dependencies = [
"aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.5",
"aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.0",
]
[project.optional-dependencies]

View File

@ -15,23 +15,10 @@ import asyncio
import logging
import math
import aiohttp
from aioweb import Response
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:
from curl_cffi import AsyncSession as _CurlAsyncSession
_CURL_ERROR = None
@ -60,12 +47,7 @@ def _coerce_timeout(value):
def _jar_to_dict(session):
"""best-effort map of a requests-style cookie jar on session to a plain dict
intentionally broad: this feeds preview() only, the two backends expose differently-
shaped jars, and a cookie read must never crash a request so any jar that doesn't
iterate cleanly degrades to {} rather than raising.
"""
"""best-effort map of a requests-style cookie jar on session to a plain dict"""
jar = getattr(session, "cookies", None)
if not jar:
return {}
@ -112,18 +94,10 @@ class CurlCffi:
if proxy:
kwargs["proxy"] = proxy
try:
response = await session.request(method, url, impersonate=impersonate, **kwargs)
except aiohttp.ClientError:
raise
except asyncio.TimeoutError:
raise
except OSError as error:
# curl_cffi's RequestException subclasses OSError; translate the native
# network error into aiohttp.ClientError. narrowed from a bare Exception so a
# real bug (AttributeError/TypeError) isn't laundered into 'client error'
raise _as_client_error(error, "curl_cffi") from error
content = response.content if response.content is not None else b""
content = response.content
if content is None:
content = response.text.encode() if response.text else b""
return Response(
status_code=response.status_code,
headers=dict(response.headers),
@ -187,9 +161,9 @@ class Noble:
async def setup(self) -> None:
"""fetch the noble_tls Go shared library once; idempotent and concurrency-safe
uses noble_tls.download_if_necessary (the current API: it fetches the asset on
first use and no-ops when it already exists). older noble_tls without that name
is handled via update_if_necessary as a fallback.
download_if_necessary handles the first-time fetch (no lib present);
update_if_necessary refreshes an existing one. try download first so a
clean environment works, falling back to update.
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
@ -203,7 +177,7 @@ class Noble:
download = getattr(noble_tls, "download_if_necessary", None)
if download is not None:
await download()
elif hasattr(noble_tls, "update_if_necessary"):
else:
await noble_tls.update_if_necessary()
self._updated = True
@ -236,16 +210,7 @@ class Noble:
if proxy:
kwargs["proxy"] = proxy
try:
response = await session.execute_request(method=method.upper(), url=url, **kwargs)
except aiohttp.ClientError:
raise
except asyncio.TimeoutError:
raise
except OSError as error:
# noble_tls's TLSClientException subclasses IOError (== OSError); translate
# the native network error, narrowed from bare Exception so a real bug surfaces
raise _as_client_error(error, "noble_tls") from error
content = getattr(response, "content", None)
if content is None:
text = getattr(response, "text", "") or ""