crawlora-cricbuzz 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Crawlora
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: crawlora-cricbuzz
3
+ Version: 0.1.0
4
+ Summary: Crawlora Cricbuzz API client with 29 endpoints for cricket matches, schedules, news, rankings, series, and records
5
+ Author: Crawlora
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://crawlora.net/?utm_source=pypi&utm_medium=referral&utm_campaign=platform-clients&utm_content=cricbuzz-python-homepage
8
+ Project-URL: Documentation, https://crawlora.net/docs?utm_source=pypi&utm_medium=referral&utm_campaign=platform-clients&utm_content=cricbuzz-python-api-docs
9
+ Project-URL: Repository, https://github.com/Crawlora-org/crawlora-cricbuzz
10
+ Keywords: crawlora,api-client,sdk,web-scraping,scraping-api,cricbuzz,cricbuzz-api,cricbuzz-scraper,cricbuzz-data,cricbuzz-cricket-matches,cricbuzz-schedules,cricbuzz-news,cricbuzz-rankings,cricbuzz-series,cricbuzz-records
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Typing :: Typed
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: typing-extensions>=4.7; python_version < "3.11"
18
+ Provides-Extra: async
19
+ Requires-Dist: httpx>=0.27; extra == "async"
20
+ Provides-Extra: test
21
+ Requires-Dist: mypy>=1.11; extra == "test"
22
+ Requires-Dist: pytest>=8; extra == "test"
23
+ Dynamic: license-file
24
+
25
+ # crawlora-cricbuzz — Cricbuzz API client for Python
26
+
27
+ Python client for Crawlora's hosted Cricbuzz API. It exposes 29 API endpoints covering cricket matches, schedules, news, rankings, series, and records. Requests go to [Crawlora](https://crawlora.net/?utm_source=pypi&utm_medium=referral&utm_campaign=platform-clients&utm_content=cricbuzz-python-homepage); the package does not run a browser or scrape
28
+ Cricbuzz locally. A Crawlora account and `CRAWLORA_API_KEY` are required,
29
+ and API use is billed under your Crawlora account. Crawlora is independent from and not endorsed by
30
+ Cricbuzz or its owners.
31
+
32
+ ## Install
33
+
34
+ ```sh
35
+ python -m pip install crawlora-cricbuzz
36
+ ```
37
+
38
+ ## Get an API key
39
+
40
+ Create an account at [crawlora.net](https://crawlora.net/signup?utm_source=pypi&utm_medium=referral&utm_campaign=platform-clients&utm_content=cricbuzz-python-signup), then open the [Crawlora console](https://crawlora.net/app?utm_source=pypi&utm_medium=referral&utm_campaign=platform-clients&utm_content=cricbuzz-python-console) for API-key setup. Set your key in the shell before running the client:
41
+
42
+ ```sh
43
+ export CRAWLORA_API_KEY="your-crawlora-api-key"
44
+ ```
45
+
46
+ ## Use
47
+
48
+ Save this example as `example.py`, then run `python example.py` after installing the package and setting your API key.
49
+
50
+ ```python
51
+ import os
52
+
53
+ from crawlora_cricbuzz import CricbuzzClient
54
+
55
+ api_key = os.environ.get("CRAWLORA_API_KEY")
56
+ if not api_key:
57
+ raise RuntimeError("Set CRAWLORA_API_KEY before running this example.")
58
+
59
+ with CricbuzzClient(api_key=api_key) as client:
60
+ result_1 = client.matches()
61
+ print(result_1)
62
+ result_2 = client.schedule()
63
+ print(result_2)
64
+ ```
65
+
66
+ The package also exports `Client` as an alias for `CricbuzzClient`. Operation
67
+ methods are available directly in snake_case and through the `cricbuzz`
68
+ group. The async package client is `AsyncCricbuzzClient`; see the [online
69
+ endpoint and parameter reference](https://github.com/Crawlora-org/crawlora-cricbuzz/blob/main/docs/usage.md) and [runnable example](https://github.com/Crawlora-org/crawlora-cricbuzz/blob/main/examples/python.py).
70
+
71
+ ### Async usage
72
+
73
+ The package also exports `AsyncClient` for asynchronous requests:
74
+
75
+ ```python
76
+ import asyncio
77
+ import os
78
+
79
+ from crawlora_cricbuzz import AsyncClient
80
+
81
+ async def main():
82
+ api_key = os.environ.get("CRAWLORA_API_KEY")
83
+ if not api_key:
84
+ raise RuntimeError("Set CRAWLORA_API_KEY before running this example.")
85
+ async with AsyncClient(api_key=api_key) as client:
86
+ result_1 = await client.matches()
87
+ print(result_1)
88
+
89
+ asyncio.run(main())
90
+ ```
91
+
92
+ See the [runnable example](https://github.com/Crawlora-org/crawlora-cricbuzz/blob/main/examples/python.py) for a complete usage example.
93
+
94
+ ## Supported Cricbuzz API endpoints
95
+
96
+ This package exposes every route below through direct snake_case methods and the `cricbuzz` group. See the [endpoint and parameter reference](https://github.com/Crawlora-org/crawlora-cricbuzz/blob/main/docs/usage.md) for complete input details.
97
+
98
+ | Method | Endpoint | API operation | Description |
99
+ | --- | --- | --- | --- |
100
+ | `GET` | `/cricbuzz/archives` | `cricbuzz-archives` | Discover Cricbuzz archive years and series |
101
+ | `GET` | `/cricbuzz/auction/info` | `cricbuzz-auction-info` | Get the Cricbuzz auction guide |
102
+ | `GET` | `/cricbuzz/auction/live` | `cricbuzz-auction-live` | Get the current Cricbuzz auction Live-tab payload |
103
+ | `GET` | `/cricbuzz/auction/options` | `cricbuzz-auction-options` | Discover Cricbuzz auction filters and sort values |
104
+ | `GET` | `/cricbuzz/auction/player` | `cricbuzz-auction-player-detail` | Get a Cricbuzz auction player profile and earnings |
105
+ | `GET` | `/cricbuzz/auction/player-search` | `cricbuzz-auction-player-search` | Search Cricbuzz auction players |
106
+ | `GET` | `/cricbuzz/auction/results` | `cricbuzz-auction-results` | Get filtered and sorted Cricbuzz completed-auction players |
107
+ | `GET` | `/cricbuzz/auction/seasons` | `cricbuzz-auction-seasons` | Discover Cricbuzz auction leagues, editions and currencies |
108
+ | `GET` | `/cricbuzz/auction/team` | `cricbuzz-auction-team-detail` | Get a Cricbuzz auction team's roster |
109
+ | `GET` | `/cricbuzz/auction/teams` | `cricbuzz-auction-teams` | Get Cricbuzz auction team purse and roster summaries |
110
+ | `GET` | `/cricbuzz/auction/teams/options` | `cricbuzz-auction-teams-options` | Discover Cricbuzz auction teams and sort values |
111
+ | `GET` | `/cricbuzz/auction/upcoming` | `cricbuzz-auction-upcoming` | Get filtered Cricbuzz unauctioned players |
112
+ | `GET` | `/cricbuzz/auction/upcoming/options` | `cricbuzz-auction-upcoming-options` | Discover Cricbuzz unauctioned-player filters |
113
+ | `GET` | `/cricbuzz/match-facts` | `cricbuzz-match-facts` | Get Cricbuzz match information and squads |
114
+ | `GET` | `/cricbuzz/match-graphs` | `cricbuzz-match-graphs` | Get numeric Cricbuzz match graphs |
115
+ | `GET` | `/cricbuzz/match-overs` | `cricbuzz-match-overs` | Get Cricbuzz over summaries |
116
+ | `GET` | `/cricbuzz/matches` | `cricbuzz-matches` | List Cricbuzz live, recent, or upcoming matches |
117
+ | `GET` | `/cricbuzz/navigation` | `cricbuzz-navigation` | Discover Cricbuzz top-level menus and dropdown links |
118
+ | `GET` | `/cricbuzz/news/topics` | `cricbuzz-news-topics` | Discover Cricbuzz News sections and topics |
119
+ | `GET` | `/cricbuzz/rankings` | `cricbuzz-rankings` | Get an ICC ranking table from Cricbuzz |
120
+ | `GET` | `/cricbuzz/records` | `cricbuzz-records` | Discover Cricbuzz record topics and filter values |
121
+ | `GET` | `/cricbuzz/schedule` | `cricbuzz-schedule` | List Cricbuzz matches by day |
122
+ | `GET` | `/cricbuzz/scorecard` | `cricbuzz-scorecard` | Get a Cricbuzz match scorecard |
123
+ | `GET` | `/cricbuzz/series` | `cricbuzz-series` | Discover Cricbuzz current and future series |
124
+ | `GET` | `/cricbuzz/series/stats` | `cricbuzz-series-stats` | Discover Cricbuzz series statistics |
125
+ | `GET` | `/cricbuzz/teams` | `cricbuzz-teams` | Discover Cricbuzz teams |
126
+ | `GET` | `/cricbuzz/videos/categories` | `cricbuzz-video-categories` | Discover Cricbuzz video categories |
127
+ | `GET` | `/cricbuzz/videos/playlists` | `cricbuzz-video-playlists` | Discover Cricbuzz video playlists |
128
+ | `GET` | `/cricbuzz/wtc` | `cricbuzz-wtc` | Get Cricbuzz World Test Championship standings |
129
+
130
+ ## Configuration
131
+
132
+ Pass your key through `api_key` or read `CRAWLORA_API_KEY` from the environment.
133
+ Keep credentials out of source control and logs. Requests go to Crawlora's
134
+ hosted API.
@@ -0,0 +1,110 @@
1
+ # crawlora-cricbuzz — Cricbuzz API client for Python
2
+
3
+ Python client for Crawlora's hosted Cricbuzz API. It exposes 29 API endpoints covering cricket matches, schedules, news, rankings, series, and records. Requests go to [Crawlora](https://crawlora.net/?utm_source=pypi&utm_medium=referral&utm_campaign=platform-clients&utm_content=cricbuzz-python-homepage); the package does not run a browser or scrape
4
+ Cricbuzz locally. A Crawlora account and `CRAWLORA_API_KEY` are required,
5
+ and API use is billed under your Crawlora account. Crawlora is independent from and not endorsed by
6
+ Cricbuzz or its owners.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ python -m pip install crawlora-cricbuzz
12
+ ```
13
+
14
+ ## Get an API key
15
+
16
+ Create an account at [crawlora.net](https://crawlora.net/signup?utm_source=pypi&utm_medium=referral&utm_campaign=platform-clients&utm_content=cricbuzz-python-signup), then open the [Crawlora console](https://crawlora.net/app?utm_source=pypi&utm_medium=referral&utm_campaign=platform-clients&utm_content=cricbuzz-python-console) for API-key setup. Set your key in the shell before running the client:
17
+
18
+ ```sh
19
+ export CRAWLORA_API_KEY="your-crawlora-api-key"
20
+ ```
21
+
22
+ ## Use
23
+
24
+ Save this example as `example.py`, then run `python example.py` after installing the package and setting your API key.
25
+
26
+ ```python
27
+ import os
28
+
29
+ from crawlora_cricbuzz import CricbuzzClient
30
+
31
+ api_key = os.environ.get("CRAWLORA_API_KEY")
32
+ if not api_key:
33
+ raise RuntimeError("Set CRAWLORA_API_KEY before running this example.")
34
+
35
+ with CricbuzzClient(api_key=api_key) as client:
36
+ result_1 = client.matches()
37
+ print(result_1)
38
+ result_2 = client.schedule()
39
+ print(result_2)
40
+ ```
41
+
42
+ The package also exports `Client` as an alias for `CricbuzzClient`. Operation
43
+ methods are available directly in snake_case and through the `cricbuzz`
44
+ group. The async package client is `AsyncCricbuzzClient`; see the [online
45
+ endpoint and parameter reference](https://github.com/Crawlora-org/crawlora-cricbuzz/blob/main/docs/usage.md) and [runnable example](https://github.com/Crawlora-org/crawlora-cricbuzz/blob/main/examples/python.py).
46
+
47
+ ### Async usage
48
+
49
+ The package also exports `AsyncClient` for asynchronous requests:
50
+
51
+ ```python
52
+ import asyncio
53
+ import os
54
+
55
+ from crawlora_cricbuzz import AsyncClient
56
+
57
+ async def main():
58
+ api_key = os.environ.get("CRAWLORA_API_KEY")
59
+ if not api_key:
60
+ raise RuntimeError("Set CRAWLORA_API_KEY before running this example.")
61
+ async with AsyncClient(api_key=api_key) as client:
62
+ result_1 = await client.matches()
63
+ print(result_1)
64
+
65
+ asyncio.run(main())
66
+ ```
67
+
68
+ See the [runnable example](https://github.com/Crawlora-org/crawlora-cricbuzz/blob/main/examples/python.py) for a complete usage example.
69
+
70
+ ## Supported Cricbuzz API endpoints
71
+
72
+ This package exposes every route below through direct snake_case methods and the `cricbuzz` group. See the [endpoint and parameter reference](https://github.com/Crawlora-org/crawlora-cricbuzz/blob/main/docs/usage.md) for complete input details.
73
+
74
+ | Method | Endpoint | API operation | Description |
75
+ | --- | --- | --- | --- |
76
+ | `GET` | `/cricbuzz/archives` | `cricbuzz-archives` | Discover Cricbuzz archive years and series |
77
+ | `GET` | `/cricbuzz/auction/info` | `cricbuzz-auction-info` | Get the Cricbuzz auction guide |
78
+ | `GET` | `/cricbuzz/auction/live` | `cricbuzz-auction-live` | Get the current Cricbuzz auction Live-tab payload |
79
+ | `GET` | `/cricbuzz/auction/options` | `cricbuzz-auction-options` | Discover Cricbuzz auction filters and sort values |
80
+ | `GET` | `/cricbuzz/auction/player` | `cricbuzz-auction-player-detail` | Get a Cricbuzz auction player profile and earnings |
81
+ | `GET` | `/cricbuzz/auction/player-search` | `cricbuzz-auction-player-search` | Search Cricbuzz auction players |
82
+ | `GET` | `/cricbuzz/auction/results` | `cricbuzz-auction-results` | Get filtered and sorted Cricbuzz completed-auction players |
83
+ | `GET` | `/cricbuzz/auction/seasons` | `cricbuzz-auction-seasons` | Discover Cricbuzz auction leagues, editions and currencies |
84
+ | `GET` | `/cricbuzz/auction/team` | `cricbuzz-auction-team-detail` | Get a Cricbuzz auction team's roster |
85
+ | `GET` | `/cricbuzz/auction/teams` | `cricbuzz-auction-teams` | Get Cricbuzz auction team purse and roster summaries |
86
+ | `GET` | `/cricbuzz/auction/teams/options` | `cricbuzz-auction-teams-options` | Discover Cricbuzz auction teams and sort values |
87
+ | `GET` | `/cricbuzz/auction/upcoming` | `cricbuzz-auction-upcoming` | Get filtered Cricbuzz unauctioned players |
88
+ | `GET` | `/cricbuzz/auction/upcoming/options` | `cricbuzz-auction-upcoming-options` | Discover Cricbuzz unauctioned-player filters |
89
+ | `GET` | `/cricbuzz/match-facts` | `cricbuzz-match-facts` | Get Cricbuzz match information and squads |
90
+ | `GET` | `/cricbuzz/match-graphs` | `cricbuzz-match-graphs` | Get numeric Cricbuzz match graphs |
91
+ | `GET` | `/cricbuzz/match-overs` | `cricbuzz-match-overs` | Get Cricbuzz over summaries |
92
+ | `GET` | `/cricbuzz/matches` | `cricbuzz-matches` | List Cricbuzz live, recent, or upcoming matches |
93
+ | `GET` | `/cricbuzz/navigation` | `cricbuzz-navigation` | Discover Cricbuzz top-level menus and dropdown links |
94
+ | `GET` | `/cricbuzz/news/topics` | `cricbuzz-news-topics` | Discover Cricbuzz News sections and topics |
95
+ | `GET` | `/cricbuzz/rankings` | `cricbuzz-rankings` | Get an ICC ranking table from Cricbuzz |
96
+ | `GET` | `/cricbuzz/records` | `cricbuzz-records` | Discover Cricbuzz record topics and filter values |
97
+ | `GET` | `/cricbuzz/schedule` | `cricbuzz-schedule` | List Cricbuzz matches by day |
98
+ | `GET` | `/cricbuzz/scorecard` | `cricbuzz-scorecard` | Get a Cricbuzz match scorecard |
99
+ | `GET` | `/cricbuzz/series` | `cricbuzz-series` | Discover Cricbuzz current and future series |
100
+ | `GET` | `/cricbuzz/series/stats` | `cricbuzz-series-stats` | Discover Cricbuzz series statistics |
101
+ | `GET` | `/cricbuzz/teams` | `cricbuzz-teams` | Discover Cricbuzz teams |
102
+ | `GET` | `/cricbuzz/videos/categories` | `cricbuzz-video-categories` | Discover Cricbuzz video categories |
103
+ | `GET` | `/cricbuzz/videos/playlists` | `cricbuzz-video-playlists` | Discover Cricbuzz video playlists |
104
+ | `GET` | `/cricbuzz/wtc` | `cricbuzz-wtc` | Get Cricbuzz World Test Championship standings |
105
+
106
+ ## Configuration
107
+
108
+ Pass your key through `api_key` or read `CRAWLORA_API_KEY` from the environment.
109
+ Keep credentials out of source control and logs. Requests go to Crawlora's
110
+ hosted API.
@@ -0,0 +1,18 @@
1
+ """Typed Cricbuzz client for the Crawlora hosted API."""
2
+
3
+ from .platform import CricbuzzClient, AsyncCricbuzzClient
4
+ from .client import CrawloraClientError, CrawloraError, CrawloraNetworkError, CrawloraServerError
5
+ from .operations import OPERATION_COUNT, OPERATION_IDS, PLATFORM
6
+
7
+ Client = CricbuzzClient
8
+ AsyncClient = AsyncCricbuzzClient
9
+ __version__ = '0.1.0'
10
+ DISPLAY_NAME = 'Cricbuzz'
11
+ PLATFORM = 'cricbuzz'
12
+ CONTRACT_REVISION = 'sha256:24090022ab422946e7b2a0693ba68ff52b3751b406e29ef04e14c88905c28a6b'
13
+
14
+ __all__ = [
15
+ "CricbuzzClient", "AsyncCricbuzzClient", "Client", "AsyncClient",
16
+ "CrawloraError", "CrawloraClientError", "CrawloraServerError", "CrawloraNetworkError",
17
+ "DISPLAY_NAME", "PLATFORM", "CONTRACT_REVISION", "OPERATION_COUNT", "OPERATION_IDS", "__version__",
18
+ ]
@@ -0,0 +1,10 @@
1
+ from .platform import CricbuzzClient as CricbuzzClient
2
+ from .platform import AsyncCricbuzzClient as AsyncCricbuzzClient
3
+ Client = CricbuzzClient
4
+ AsyncClient = AsyncCricbuzzClient
5
+ from .client import CrawloraClientError as CrawloraClientError, CrawloraError as CrawloraError, CrawloraNetworkError as CrawloraNetworkError, CrawloraServerError as CrawloraServerError
6
+ from .operations import OPERATION_COUNT as OPERATION_COUNT, OPERATION_IDS as OPERATION_IDS, PLATFORM as PLATFORM
7
+ __version__: str
8
+ DISPLAY_NAME: str
9
+ CONTRACT_REVISION: str
10
+ __all__ = ['CricbuzzClient', 'AsyncCricbuzzClient', 'Client', 'AsyncClient', 'CrawloraError', 'CrawloraClientError', 'CrawloraServerError', 'CrawloraNetworkError', 'DISPLAY_NAME', 'PLATFORM', 'CONTRACT_REVISION', 'OPERATION_COUNT', 'OPERATION_IDS', '__version__']
@@ -0,0 +1,44 @@
1
+ """Shared pagination helpers used by the sync and async clients.
2
+
3
+ This module deliberately has no `.pyi` stub so type checkers read its inline
4
+ annotations directly (the `client.pyi` stub shadows `client.py`).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Mapping
10
+
11
+ PAGE_PARAM_NAMES = ("page", "offset")
12
+
13
+
14
+ def detect_page_param(operation: Mapping[str, Any]) -> str | None:
15
+ names = {parameter["name"] for parameter in operation.get("queryParams", [])}
16
+ for candidate in PAGE_PARAM_NAMES:
17
+ if candidate in names:
18
+ return candidate
19
+ return None
20
+
21
+
22
+ def page_is_empty(response: Any) -> bool:
23
+ data = response
24
+ if isinstance(response, Mapping) and "data" in response:
25
+ data = response["data"]
26
+ if data is None:
27
+ return True
28
+ if isinstance(data, (list, tuple, dict, str)):
29
+ return len(data) == 0
30
+ return not data
31
+
32
+
33
+ def default_start(page_param: str) -> int:
34
+ return 0 if page_param == "offset" else 1
35
+
36
+
37
+ def default_items(response: Any) -> list[Any]:
38
+ """Default item extractor: the response's ``data`` list (Crawlora envelope),
39
+ or the response itself when it is already a list."""
40
+ if isinstance(response, Mapping) and isinstance(response.get("data"), list):
41
+ return list(response["data"])
42
+ if isinstance(response, list):
43
+ return list(response)
44
+ return []
@@ -0,0 +1,114 @@
1
+ """Keep-alive HTTP transport for the synchronous client (standard library only).
2
+
3
+ Maintains a small pool of reusable connections per ``(scheme, host, port)`` so
4
+ the sync client avoids a fresh TCP + TLS handshake on every request. Each
5
+ request checks out its own connection, so the transport is safe to use from
6
+ multiple threads (e.g. under ``max_concurrency``). This module is stub-free so
7
+ type checkers read its inline annotations directly.
8
+
9
+ The transport returns a lightweight response object exposing ``status``,
10
+ ``headers`` (a dict), and ``body`` (bytes) — the only fields the client reads.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import http.client
16
+ import threading
17
+ from dataclasses import dataclass
18
+ from typing import Any, Mapping
19
+ from urllib.parse import urlsplit
20
+ from urllib.request import Request
21
+
22
+
23
+ def _title_case(name: str) -> str:
24
+ return "-".join(part.capitalize() for part in name.split("-"))
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class _PooledResponse:
29
+ status: int
30
+ headers: Mapping[str, str]
31
+ body: bytes
32
+
33
+
34
+ class KeepAliveTransport:
35
+ """Connection-pooling transport. Drop-in for the urlopen transport: callable
36
+ as ``transport(request, timeout) -> response``."""
37
+
38
+ def __init__(self, max_per_host: int = 8) -> None:
39
+ self._lock = threading.Lock()
40
+ self._pools: dict[tuple, list[http.client.HTTPConnection]] = {}
41
+ self._max_per_host = max_per_host
42
+
43
+ def __call__(self, request: Request, timeout: float) -> _PooledResponse:
44
+ parts = urlsplit(request.full_url)
45
+ key = (parts.scheme, parts.hostname, parts.port)
46
+ path = parts.path or "/"
47
+ if parts.query:
48
+ path = f"{path}?{parts.query}"
49
+ method = request.get_method()
50
+ # Send canonical HTTP title-case header names (matching the urlopen
51
+ # transport's behavior), so receivers see e.g. "X-Api-Key".
52
+ headers = {_title_case(name): value for name, value in request.header_items()}
53
+ body = request.data
54
+
55
+ last_exc: Exception | None = None
56
+ for attempt in range(2):
57
+ conn = self._checkout(key, parts, timeout)
58
+ try:
59
+ conn.request(method, path, body=body, headers=headers)
60
+ response = conn.getresponse()
61
+ data = response.read()
62
+ result = _PooledResponse(response.status, dict(response.getheaders()), data)
63
+ except (http.client.HTTPException, ConnectionError, OSError) as exc:
64
+ # Likely a stale pooled connection the server already closed;
65
+ # discard it and retry once on a fresh connection.
66
+ last_exc = exc
67
+ self._close(conn)
68
+ if attempt == 1:
69
+ raise
70
+ continue
71
+ if response.will_close:
72
+ self._close(conn)
73
+ else:
74
+ self._checkin(key, conn)
75
+ return result
76
+ raise last_exc if last_exc else RuntimeError("keep-alive transport failed")
77
+
78
+ def close(self) -> None:
79
+ with self._lock:
80
+ pools = list(self._pools.values())
81
+ self._pools.clear()
82
+ for pool in pools:
83
+ for conn in pool:
84
+ self._close(conn)
85
+
86
+ def _checkout(self, key: tuple, parts: Any, timeout: float) -> http.client.HTTPConnection:
87
+ with self._lock:
88
+ pool = self._pools.get(key)
89
+ if pool:
90
+ conn = pool.pop()
91
+ conn.timeout = timeout
92
+ return conn
93
+ return self._new(parts, timeout)
94
+
95
+ def _checkin(self, key: tuple, conn: http.client.HTTPConnection) -> None:
96
+ with self._lock:
97
+ pool = self._pools.setdefault(key, [])
98
+ if len(pool) < self._max_per_host:
99
+ pool.append(conn)
100
+ return
101
+ self._close(conn)
102
+
103
+ @staticmethod
104
+ def _new(parts: Any, timeout: float) -> http.client.HTTPConnection:
105
+ if parts.scheme == "https":
106
+ return http.client.HTTPSConnection(parts.hostname, parts.port or 443, timeout=timeout)
107
+ return http.client.HTTPConnection(parts.hostname, parts.port or 80, timeout=timeout)
108
+
109
+ @staticmethod
110
+ def _close(conn: http.client.HTTPConnection) -> None:
111
+ try:
112
+ conn.close()
113
+ except Exception:
114
+ pass