Skip to content

Commit ccb237b

Browse files
chore: pytest
1 parent 4da8f3f commit ccb237b

File tree

7 files changed

+74
-34
lines changed

7 files changed

+74
-34
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,31 @@ Start using TLS Requests with just a few lines of code:
4444
200
4545
```
4646

47+
Basic automatically rotates:
48+
49+
```pycon
50+
>>> import tls_requests
51+
>>> proxy_list = [
52+
"http://user1:[email protected]:8080",
53+
"http://user2:[email protected]:8081",
54+
"socks5://proxy.example.com:8082",
55+
"proxy.example.com:8083", # (defaults to http)
56+
"http://user:[email protected]:8084|1.0|US", # http://user:pass@host:port|weight|region
57+
]
58+
>>> r = tls_requests.get(
59+
"https://httpbin.org/get",
60+
proxy=proxy,
61+
headers=tls_requests.HeaderRotator(),
62+
tls_identifier=tls_requests.TLSIdentifierRotator()
63+
)
64+
>>> r
65+
<Response [200 OK]>
66+
>>> r.status_code
67+
200
68+
>>> tls_requests.HeaderRotator(strategy = "round_robin") # strategy: Literal["round_robin", "random", "weighted"]
69+
>>> tls_requests.Proxy("http://user1:[email protected]:8080", weight=0.1) # default weight: 1.0
70+
```
71+
4772
**Introduction**
4873
----------------
4974

tests/test_headers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ def test_request_headers(httpserver: HTTPServer):
2323
httpserver.expect_request("/headers").with_post_hook(hook_request_headers).respond_with_data(b"OK")
2424
response = tls_requests.get(httpserver.url_for("/headers"), headers={"foo": "bar"})
2525
assert response.status_code == 200
26-
assert response.headers.get("foo") == "bar"
26+
assert response.request.headers["foo"] == "bar"
2727

2828

2929
def test_response_headers(httpserver: HTTPServer):
3030
httpserver.expect_request("/headers").with_post_hook(hook_response_headers).respond_with_data(b"OK")
3131
response = tls_requests.get(httpserver.url_for("/headers"))
3232
assert response.status_code, 200
33-
assert response.headers.get("foo") == "bar"
33+
assert response.headers["foo"] == "bar"
3434

3535

3636
def test_response_case_insensitive_headers(httpserver: HTTPServer):
3737
httpserver.expect_request("/headers").with_post_hook(hook_response_case_insensitive_headers).respond_with_data(b"OK")
3838
response = tls_requests.get(httpserver.url_for("/headers"))
3939
assert response.status_code, 200
40-
assert response.headers.get("foo") == "bar"
40+
assert response.headers["foo"] == "bar"

tests/test_rotators.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ async def test_async_strategies(self, strategy, proxy_list_fixture):
183183

184184
class TestProxyRotator:
185185
def test_mark_result_weighted(self):
186-
proxy = Proxy("p1:8080", weight=2.0)
186+
proxy = Proxy("proxy.example.com:8080", weight=2.0)
187187
rotator = ProxyRotator([proxy], strategy="weighted")
188188

189189
initial_weight = proxy.weight
@@ -196,7 +196,7 @@ def test_mark_result_weighted(self):
196196

197197
@pytest.mark.asyncio
198198
async def test_async_mark_result_weighted(self):
199-
proxy = Proxy("p1:8080", weight=2.0)
199+
proxy = Proxy("proxy.example.com:8080", weight=2.0)
200200
rotator = ProxyRotator([proxy], strategy="weighted")
201201

202202
initial_weight = proxy.weight

tls_requests/client.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,11 @@ def prepare_auth(
193193

194194
def prepare_headers(self, headers: HeaderTypes = None, user_agent: Optional[str] = None) -> Headers:
195195
"""Prepare Headers. Gets base headers from rotator if available."""
196+
if headers is None:
197+
return self.headers.copy()
196198
if isinstance(headers, HeaderRotator):
197-
headers_copy = headers.next(user_agent=user_agent)
198-
else:
199-
headers_copy = self.headers.copy()
200-
201-
return headers_copy
199+
return headers.next(user_agent=user_agent)
200+
return Headers(headers)
202201

203202
def prepare_cookies(self, cookies: CookieTypes = None) -> Cookies:
204203
"""Prepare Cookies"""
@@ -371,7 +370,8 @@ def _send(
371370
self, request: Request, *, history: list = None, start: float = None
372371
) -> Response:
373372
start = start or time.perf_counter()
374-
config = self.prepare_config(request, tls_identifier=self.prepare_tls_identifier(self.client_identifier))
373+
tls_identifier = self.prepare_tls_identifier(self.client_identifier)
374+
config = self.prepare_config(request, tls_identifier=tls_identifier)
375375
response = Response.from_tls_response(
376376
self.session.request(config.to_dict()),
377377
is_byte_response=config.isByteResponse,
@@ -515,6 +515,12 @@ def send(
515515
self.follow_redirects = follow_redirects
516516
response = self._send(request, start=time.perf_counter(), history=[])
517517

518+
if isinstance(self.proxy, ProxyRotator) and response.request.proxy:
519+
proxy_success = 200 <= response.status_code < 500 and response.status_code not in [407]
520+
self.proxy.mark_result(
521+
proxy=response.request.proxy, success=proxy_success, latency=response.elapsed
522+
)
523+
518524
if self.hooks.get("response"):
519525
response_ = self.build_hook_response(response)
520526
if isinstance(response_, Response):
@@ -757,12 +763,11 @@ class AsyncClient(BaseClient):
757763

758764
async def aprepare_headers(self, headers: HeaderTypes = None, user_agent: Optional[str] = None) -> Headers:
759765
"""Prepare Headers. Gets base headers from rotator if available."""
766+
if headers is None:
767+
return self.headers.copy()
760768
if isinstance(headers, HeaderRotator):
761-
headers_copy = await headers.anext(user_agent=user_agent)
762-
else:
763-
headers_copy = self.headers.copy()
764-
765-
return headers_copy
769+
return await headers.anext(user_agent=user_agent)
770+
return Headers(headers)
766771

767772
async def aprepare_proxy(self, proxy: ProxyTypes | None) -> Optional[Proxy]:
768773
if proxy is None:
@@ -1070,6 +1075,12 @@ async def send(
10701075
self.follow_redirects = follow_redirects
10711076
response = await self._send(request, start=time.perf_counter(), history=[])
10721077

1078+
if isinstance(self.proxy, ProxyRotator) and response.request.proxy:
1079+
proxy_success = 200 <= response.status_code < 500 and response.status_code not in [407]
1080+
await self.proxy.amark_result(
1081+
proxy=response.request.proxy, success=proxy_success, latency=response.elapsed
1082+
)
1083+
10731084
if self.hooks.get("response"):
10741085
response_ = self.build_hook_response(response)
10751086
if isinstance(response_, Response):
@@ -1084,7 +1095,8 @@ async def _send(
10841095
self, request: Request, *, history: list = None, start: float = None
10851096
) -> Response:
10861097
start = start or time.perf_counter()
1087-
config = self.prepare_config(request, tls_identifier=await self.aprepare_tls_identifier(self.client_identifier))
1098+
tls_identifier = await self.aprepare_tls_identifier(self.client_identifier)
1099+
config = self.prepare_config(request, tls_identifier=tls_identifier)
10881100
response = Response.from_tls_response(
10891101
await self.session.arequest(config.to_dict()),
10901102
is_byte_response=config.isByteResponse,

tls_requests/models/rotators.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def rebuild_item(cls, item: Any) -> Optional[Proxy]:
319319
"""Constructs a `Proxy` object from various input types."""
320320
try:
321321
if isinstance(item, Proxy):
322-
return item
322+
return Proxy.from_string(str(item))
323323
if isinstance(item, dict):
324324
return Proxy.from_dict(item)
325325
if isinstance(item, str):
@@ -414,7 +414,7 @@ class HeaderRotator(BaseRotator[Headers]):
414414
def __init__(
415415
self,
416416
items: Optional[Iterable[T]] = None,
417-
strategy: Literal["round_robin", "random", "weighted"] = "round_robin",
417+
strategy: Literal["round_robin", "random", "weighted"] = "random",
418418
) -> None:
419419
super().__init__(items or HEADER_TEMPLATES, strategy)
420420

@@ -434,11 +434,9 @@ def rebuild_item(cls, item: HeaderTypes) -> Optional[Headers]:
434434
try:
435435
if isinstance(item, Headers):
436436
return item
437-
if isinstance(item, (dict, list)):
438-
return Headers(item)
437+
return Headers(item)
439438
except Exception:
440439
return None
441-
return None
442440

443441
def next(self, user_agent: Optional[str] = None) -> Headers:
444442
"""
@@ -452,12 +450,12 @@ def next(self, user_agent: Optional[str] = None) -> Headers:
452450
Returns:
453451
A copy of the next `Headers` object, potentially with a modified User-Agent.
454452
"""
455-
base_headers = super().next()
456-
headers_copy = base_headers.copy()
457-
453+
headers = super().next()
454+
headers_copy = headers.copy()
455+
if not isinstance(headers_copy, Headers):
456+
headers_copy = Headers(headers_copy)
458457
if user_agent:
459458
headers_copy["User-Agent"] = user_agent
460-
461459
return headers_copy
462460

463461
async def anext(self, user_agent: Optional[str] = None) -> Headers:
@@ -472,8 +470,10 @@ async def anext(self, user_agent: Optional[str] = None) -> Headers:
472470
Returns:
473471
A copy of the next `Headers` object, potentially with a modified User-Agent.
474472
"""
475-
base_headers = await super().anext()
476-
headers_copy = base_headers.copy()
473+
headers = await super().anext()
474+
headers_copy = headers.copy()
475+
if not isinstance(headers_copy, Headers):
476+
headers_copy = Headers(headers_copy)
477477
if user_agent:
478478
headers_copy["User-Agent"] = user_agent
479479
return headers_copy

tls_requests/models/urls.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -455,18 +455,18 @@ def __init__(
455455
Raises:
456456
ProxyError: If the URL is invalid or the scheme is not supported.
457457
"""
458-
super().__init__(url, **kwargs)
459-
self.weight = weight
458+
self.weight = weight or 1.0
460459
self.region = region
461460
self.latency = latency
462461
self.success_rate = success_rate
463462
self.meta = meta or {}
464463
self.failures: int = 0
465464
self.last_used: Optional[float] = None
465+
super().__init__(url, **kwargs)
466466

467467
def __repr__(self):
468468
"""Returns a secure representation of the proxy with its weight."""
469-
return "<%s: %s, weight=%s>" % (self.__class__.__name__, unquote(self._build(True)), self.weight)
469+
return "<%s: %s, weight=%s>" % (self.__class__.__name__, unquote(self._build(True)), getattr(self, "weight", "unset"))
470470

471471
def _prepare(self, url: ProxyTypes) -> ParseResult:
472472
"""
@@ -490,6 +490,9 @@ def _prepare(self, url: ProxyTypes) -> ParseResult:
490490
if isinstance(url, str):
491491
url = url.strip()
492492

493+
if "://" not in str(url):
494+
url = f"http://{url}"
495+
493496
parsed = super(Proxy, self)._prepare(url)
494497
if str(parsed.scheme).lower() not in self.ALLOWED_SCHEMES:
495498
raise ProxyError(
@@ -632,13 +635,13 @@ def from_string(cls, raw: str, separator: str = "|") -> "Proxy":
632635

633636
parts = [p.strip() for p in raw.split(separator)]
634637
url = parts[0]
635-
weight = None
638+
weight = 1.0
636639
region = None
637640
if len(parts) >= 2 and parts[1]:
638641
try:
639642
weight = float(parts[1])
640643
except Exception:
641-
weight = None
644+
pass
642645
if len(parts) >= 3 and parts[2]:
643646
region = parts[2]
644647

tls_requests/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
if TYPE_CHECKING: # pragma: no cover
1111
from .models import (Cookies, HeaderRotator, Headers, # noqa: F401
12-
ProxyRotator, Request, TLSIdentifierRotator)
12+
ProxyRotator, TLSIdentifierRotator)
1313

1414
AuthTypes = Optional[
1515
Union[

0 commit comments

Comments
 (0)