reqstorm 2.4.0__tar.gz → 2.4.1__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.
- {reqstorm-2.4.0/reqstorm.egg-info → reqstorm-2.4.1}/PKG-INFO +1 -1
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/__init__.py +1 -1
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_client.py +2 -2
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_socks.py +19 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1/reqstorm.egg-info}/PKG-INFO +1 -1
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_socks_backoff.py +20 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/LICENSE +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/README.md +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/pyproject.toml +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/__main__.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_adaptive.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_auth.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_cache.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_cli.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_files.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_legacy.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_limits.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_observe.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_paginate.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_plan.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_progress.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_pydantic.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_report.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_schema.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_schema_sinks.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_sync.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/_template.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm/py.typed +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm.egg-info/SOURCES.txt +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm.egg-info/dependency_links.txt +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm.egg-info/entry_points.txt +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm.egg-info/requires.txt +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/reqstorm.egg-info/top_level.txt +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/setup.cfg +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_adaptive.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_auth.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_cache_proxy.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_cli.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_fetch_all.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_files.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_legacy.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_limits.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_observe.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_ordered_and_db.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_paginate.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_pydantic.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_report.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_run_report.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_schema.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_schema_db.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_stream.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_sync.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_template.py +0 -0
- {reqstorm-2.4.0 → reqstorm-2.4.1}/tests/test_tls.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reqstorm
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: Send large numbers of HTTP requests concurrently with asyncio, with retries, timeouts and per-request results.
|
|
5
5
|
Author-email: Melih Colpan <colpanmelih@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -33,7 +33,7 @@ from ._schema import Field, Schema, SchemaError, extract, infer_schema
|
|
|
33
33
|
from ._sync import fetch_all_sync, fetch_to_db_sync, fetch_to_file_sync, stream_sync
|
|
34
34
|
from ._template import from_template, read_csv, read_sql
|
|
35
35
|
|
|
36
|
-
__version__ = "2.4.
|
|
36
|
+
__version__ = "2.4.1"
|
|
37
37
|
|
|
38
38
|
__all__ = [
|
|
39
39
|
"Attempt",
|
|
@@ -35,7 +35,7 @@ from multidict import CIMultiDict, CIMultiDictProxy
|
|
|
35
35
|
from ._adaptive import AdaptiveRateLimiter
|
|
36
36
|
from ._limits import HostRateLimiter, RateLimit, host_key
|
|
37
37
|
from ._observe import LogLevel, LogTarget, ProgressTarget, Run, describe
|
|
38
|
-
from ._socks import SocksSessions, check_available, is_socks
|
|
38
|
+
from ._socks import SocksSessions, check_available, is_proxy_error, is_socks
|
|
39
39
|
|
|
40
40
|
__all__ = ["Attempt", "HTTPStatusError", "Request", "Result", "Results", "fetch_all", "stream"]
|
|
41
41
|
|
|
@@ -284,7 +284,7 @@ def _resolve(request: Union[str, Request], options: _Options) -> Request:
|
|
|
284
284
|
def _is_retryable_error(error: BaseException) -> bool:
|
|
285
285
|
if isinstance(error, (aiohttp.InvalidURL, ValueError)):
|
|
286
286
|
return False
|
|
287
|
-
return isinstance(error, (aiohttp.ClientError, asyncio.TimeoutError))
|
|
287
|
+
return isinstance(error, (aiohttp.ClientError, asyncio.TimeoutError)) or is_proxy_error(error)
|
|
288
288
|
|
|
289
289
|
|
|
290
290
|
def _should_retry(result: Result, retry_statuses: frozenset) -> bool:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import importlib
|
|
5
6
|
from typing import Any, Dict, Optional, Tuple
|
|
6
7
|
from urllib.parse import unquote, urlsplit
|
|
7
8
|
|
|
@@ -15,6 +16,24 @@ def is_socks(proxy: Optional[str]) -> bool:
|
|
|
15
16
|
return proxy is not None and urlsplit(proxy).scheme.lower() in SCHEMES
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
def is_proxy_error(error: BaseException) -> bool:
|
|
20
|
+
"""A failure of a SOCKS proxy: unreachable, refused, timed out or a bad reply.
|
|
21
|
+
|
|
22
|
+
These are worth retrying: the next attempt may use another proxy from the pool, or the
|
|
23
|
+
proxy may be back.
|
|
24
|
+
"""
|
|
25
|
+
kinds: Tuple[type, ...] = ()
|
|
26
|
+
# aiohttp-socks raises its own classes; python-socks has classes of the same names
|
|
27
|
+
for module in ("aiohttp_socks", "python_socks"):
|
|
28
|
+
try:
|
|
29
|
+
package = importlib.import_module(module)
|
|
30
|
+
except ImportError:
|
|
31
|
+
continue
|
|
32
|
+
names = ("ProxyError", "ProxyConnectionError", "ProxyTimeoutError")
|
|
33
|
+
kinds += tuple(getattr(package, name) for name in names if hasattr(package, name))
|
|
34
|
+
return bool(kinds) and isinstance(error, kinds)
|
|
35
|
+
|
|
36
|
+
|
|
18
37
|
def check_available() -> None:
|
|
19
38
|
try:
|
|
20
39
|
import aiohttp_socks # noqa: F401
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reqstorm
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: Send large numbers of HTTP requests concurrently with asyncio, with retries, timeouts and per-request results.
|
|
5
5
|
Author-email: Melih Colpan <colpanmelih@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -142,3 +142,23 @@ async def test_max_backoff_limits_the_real_wait(server):
|
|
|
142
142
|
async def test_negative_backoff_is_rejected(server):
|
|
143
143
|
with pytest.raises(ValueError, match="backoff"):
|
|
144
144
|
await reqstorm.fetch_all([server + "/ok"], max_backoff=-1)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
async def test_unreachable_socks_proxy_is_retried_with_the_next_one(server, socks_proxy):
|
|
148
|
+
dead = "socks5://127.0.0.1:9"
|
|
149
|
+
results = await reqstorm.fetch_all(
|
|
150
|
+
[server + "/ok"], proxy=[dead, socks_proxy.url()], retries=1, backoff=0
|
|
151
|
+
)
|
|
152
|
+
assert results[0].text() == "ok" and results[0].attempts == 2
|
|
153
|
+
assert results[0].history[0].error is not None and len(socks_proxy.requests) == 1
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
async def test_proxy_failures_count_as_retryable_errors():
|
|
157
|
+
import aiohttp_socks
|
|
158
|
+
import python_socks
|
|
159
|
+
|
|
160
|
+
from reqstorm._client import _is_retryable_error
|
|
161
|
+
|
|
162
|
+
for package in (aiohttp_socks, python_socks):
|
|
163
|
+
for name in ("ProxyError", "ProxyConnectionError", "ProxyTimeoutError"):
|
|
164
|
+
assert _is_retryable_error(getattr(package, name)("x")), (package.__name__, name)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|