Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions httpxthrottlecache/httpxclientmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import httpx
from httpx._types import ProxyTypes
from pyrate_limiter import Duration, Limiter
from pyrate_limiter import Limiter

from .filecache.transport import CachingTransport
from .ratelimiter import AsyncRateLimitingTransport, RateLimitingTransport, create_rate_limiter
Expand Down Expand Up @@ -40,7 +40,6 @@ class HttpxThrottleCache:
rate_limiter_enabled: bool = True
cache_mode: Literal[False, "Disabled", "FileCache"] = "FileCache"
request_per_sec_limit: int = 10
max_delay: Duration = field(default_factory=lambda: Duration.DAY)
_client: Optional[httpx.Client] = None

rate_limiter: Optional[Limiter] = None
Expand All @@ -62,7 +61,7 @@ def __post_init__(self):

if self.rate_limiter_enabled and self.rate_limiter is None:
self.rate_limiter = create_rate_limiter(
requests_per_second=self.request_per_sec_limit, max_delay=self.max_delay
requests_per_second=self.request_per_sec_limit
)

if (self.cache_mode != "Disabled" or self.cache_mode is False) and not self.cache_rules:
Expand Down Expand Up @@ -162,8 +161,9 @@ async def task(url: str, path: Optional[Path]) -> Path | bytes:
def _get_httpx_transport_params(self, params: dict[str, Any]):
http2 = params.get("http2", False)
proxy = self.proxy
verify = params.get("verify", True)

return {"http2": http2, "proxy": proxy}
return {"http2": http2, "proxy": proxy, "verify": verify}

@contextmanager
def http_client(self, bypass_cache: bool = False, **kwargs: dict[str, Any]) -> Generator[httpx.Client, None, None]:
Expand Down Expand Up @@ -191,8 +191,8 @@ def close(self):
self._client.close()
self._client = None

def update_rate_limiter(self, requests_per_second: int, max_delay: Duration = Duration.DAY):
self.rate_limiter = create_rate_limiter(requests_per_second=requests_per_second, max_delay=Duration.DAY)
def update_rate_limiter(self, requests_per_second: int):
self.rate_limiter = create_rate_limiter(requests_per_second=requests_per_second)

self.close()

Expand Down
22 changes: 5 additions & 17 deletions httpxthrottlecache/ratelimiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,14 @@
from typing import Any

import httpx
from pyrate_limiter import Duration, InMemoryBucket, Limiter, Rate
from pyrate_limiter import Duration, Limiter, Rate

logger = logging.getLogger(__name__)


def create_rate_limiter(requests_per_second: int, max_delay: Duration | int = Duration.DAY) -> Limiter:
def create_rate_limiter(requests_per_second: int) -> Limiter:
rate = Rate(requests_per_second, Duration.SECOND)
rate_limits = [rate]

base_bucket = InMemoryBucket(rate_limits)

bucket = base_bucket

limiter = Limiter(bucket, max_delay=max_delay, raise_when_fail=False, retry_until_max_delay=True)

return limiter
return Limiter(rate)


class RateLimitingTransport(httpx.HTTPTransport):
Expand All @@ -33,9 +25,7 @@ def handle_request(self, request: httpx.Request, **kwargs: dict[str, Any]) -> ht
# using a constant string for item name means that the same
# rate is applied to all requests.
if self.limiter:
while not self.limiter.try_acquire(__name__):
logger.debug("Lock acquisition timed out, retrying") # pragma: no cover

self.limiter.try_acquire(__name__)
logger.debug("Acquired lock")

logger.info("Making HTTP Request %s", request)
Expand All @@ -49,9 +39,7 @@ def __init__(self, limiter: Limiter, **kwargs: dict[str, Any]):

async def handle_async_request(self, request: httpx.Request, **kwargs: dict[str, Any]) -> httpx.Response:
if self.limiter:
while not await self.limiter.try_acquire_async(__name__):
logger.debug("Lock acquisition timed out, retrying") # pragma: no cover

await self.limiter.try_acquire_async(__name__)
logger.debug("Acquired lock")

logger.info("Making HTTP Request %s", request)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dependencies = [
"aiofiles>=24.1.0",
"filelock>=3.18.0",
"httpx>=0.28.1",
"pyrate-limiter>=3.9.0",
"pyrate-limiter>=4.0.0",
]
classifiers = [
"Programming Language :: Python :: 3.10",
Expand Down
21 changes: 21 additions & 0 deletions tests/test_misc2.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,27 @@ def test_user_agent_factor(tmp_path):
htc._populate_user_agent(params)
assert params["headers"]["User-Agent"] == "foo"


def test_verify_passed_to_transport(tmp_path):
"""Issue #37: verify parameter should be forwarded to transport"""
htc = HttpxThrottleCache(
cache_dir=tmp_path,
cache_mode="Disabled",
httpx_params={"verify": False, "http2": False},
rate_limiter_enabled=False,
)
transport_params = htc._get_httpx_transport_params(htc.httpx_params)
assert transport_params["verify"] is False

htc_default = HttpxThrottleCache(
cache_dir=tmp_path,
cache_mode="Disabled",
rate_limiter_enabled=False,
)
transport_params_default = htc_default._get_httpx_transport_params(htc_default.httpx_params)
assert transport_params_default["verify"] is True


def test_no_ratelimit(manager_cache):
htc = HttpxThrottleCache(cache_mode=manager_cache.cache_mode, cache_dir=manager_cache.cache_dir, user_agent_factory=lambda: "foo", rate_limiter_enabled=False)
url = "https://example.com/file.bin"
Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading