xyz17711-proxy-pool 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,18 @@
1
+ # This is the public repo's OWN .gitignore -- clients/ in the internal monorepo is an
2
+ # exact 1:1 mirror of github.com/17711-services/clients, so this file ships as-is to that
3
+ # repo's own root and applies there too (a contributor cloning the public repo and running the
4
+ # tests below needs this same coverage). Never add anything internal-only here -- that belongs in
5
+ # the platform monorepo's own root .gitignore instead.
6
+
7
+ python/packages/*/src/**/__pycache__/
8
+ python/packages/*/tests/**/__pycache__/
9
+ python/**/.pytest_cache/
10
+ python/**/*.egg-info/
11
+ python/.venv/
12
+ python/.venv-sync/
13
+ python/packages/*/dist/
14
+
15
+ node/node_modules/
16
+ node/packages/*/node_modules/
17
+ node/packages/*/dist/
18
+ node/packages/*/*.tsbuildinfo
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.5
2
+ Name: xyz17711-proxy-pool
3
+ Version: 0.1.0
4
+ Summary: Generated typed client for the proxy-pool 17711.xyz microservice.
5
+ License: MIT
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: xyz17711>=0.1.0
8
+ Provides-Extra: langchain
9
+ Requires-Dist: langchain-core<2.0,>=1.2; extra == 'langchain'
10
+ Provides-Extra: test
11
+ Requires-Dist: pytest>=8; extra == 'test'
12
+ Description-Content-Type: text/markdown
13
+
14
+ # xyz17711-proxy-pool
15
+
16
+ [![Publish](https://github.com/17711-services/clients/actions/workflows/publish-python.yml/badge.svg)](https://github.com/17711-services/clients/actions/workflows/publish-python.yml)
17
+ [![PyPI](https://img.shields.io/pypi/v/xyz17711-proxy-pool)](https://pypi.org/project/xyz17711-proxy-pool/)
18
+ [![Python versions](https://img.shields.io/pypi/pyversions/xyz17711-proxy-pool)](https://pypi.org/project/xyz17711-proxy-pool/)
19
+ [![License](https://img.shields.io/pypi/l/xyz17711-proxy-pool)](https://github.com/17711-services/clients/blob/main/LICENSE)
20
+
21
+ Typed Python client for **proxy-pool**, part of the
22
+ [17711.xyz](https://api.17711.xyz) microservice fleet -- pay-per-call APIs settled in USDC via
23
+ [x402](https://x402.org), listed on [x402scan](https://www.x402scan.com/server/f1c7b972-3405-4b75-8b4f-d1bfbd6305ff). See the
24
+ [Python family README](https://github.com/17711-services/clients/blob/main/python/README.md)
25
+ for the full guide (payment configuration, LangChain integration, logging, error handling) -- this
26
+ page only covers what's specific to this one package.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install "xyz17711[proxy-pool]"
32
+ ```
33
+
34
+ ## Methods
35
+
36
+ | Method | Description |
37
+ | --- | --- |
38
+ | `get_best_proxy` | Returns one of the fastest verified-alive proxies matching the filter, randomized to spread load ($0.001, x402) |
39
+
40
+ Every method above has both a sync form and an async form (the `a`-prefixed sibling, e.g.
41
+ `aget_best_proxy` next to `get_best_proxy`), sharing one underlying client.
42
+
43
+ ## Quickstart
44
+
45
+ ```python
46
+ from xyz17711_proxy_pool import Client
47
+
48
+ client = Client("https://api.17711.xyz/registry/proxy-pool")
49
+ # see the Methods table above for every available call and its request/response model
50
+ ```
51
+
52
+ ## LangChain
53
+
54
+ ```bash
55
+ pip install "xyz17711-proxy-pool[langchain]"
56
+ ```
57
+
58
+ ```python
59
+ from xyz17711_proxy_pool import Client
60
+ from xyz17711_proxy_pool.langchain_tools import ProxyPoolToolkit
61
+
62
+ client = Client("https://api.17711.xyz/registry/proxy-pool")
63
+ tools = ProxyPoolToolkit(client=client).get_tools()
64
+ ```
65
+
66
+ ## License
67
+
68
+ MIT -- see the repository's own `LICENSE`.
@@ -0,0 +1,55 @@
1
+ # xyz17711-proxy-pool
2
+
3
+ [![Publish](https://github.com/17711-services/clients/actions/workflows/publish-python.yml/badge.svg)](https://github.com/17711-services/clients/actions/workflows/publish-python.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/xyz17711-proxy-pool)](https://pypi.org/project/xyz17711-proxy-pool/)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/xyz17711-proxy-pool)](https://pypi.org/project/xyz17711-proxy-pool/)
6
+ [![License](https://img.shields.io/pypi/l/xyz17711-proxy-pool)](https://github.com/17711-services/clients/blob/main/LICENSE)
7
+
8
+ Typed Python client for **proxy-pool**, part of the
9
+ [17711.xyz](https://api.17711.xyz) microservice fleet -- pay-per-call APIs settled in USDC via
10
+ [x402](https://x402.org), listed on [x402scan](https://www.x402scan.com/server/f1c7b972-3405-4b75-8b4f-d1bfbd6305ff). See the
11
+ [Python family README](https://github.com/17711-services/clients/blob/main/python/README.md)
12
+ for the full guide (payment configuration, LangChain integration, logging, error handling) -- this
13
+ page only covers what's specific to this one package.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pip install "xyz17711[proxy-pool]"
19
+ ```
20
+
21
+ ## Methods
22
+
23
+ | Method | Description |
24
+ | --- | --- |
25
+ | `get_best_proxy` | Returns one of the fastest verified-alive proxies matching the filter, randomized to spread load ($0.001, x402) |
26
+
27
+ Every method above has both a sync form and an async form (the `a`-prefixed sibling, e.g.
28
+ `aget_best_proxy` next to `get_best_proxy`), sharing one underlying client.
29
+
30
+ ## Quickstart
31
+
32
+ ```python
33
+ from xyz17711_proxy_pool import Client
34
+
35
+ client = Client("https://api.17711.xyz/registry/proxy-pool")
36
+ # see the Methods table above for every available call and its request/response model
37
+ ```
38
+
39
+ ## LangChain
40
+
41
+ ```bash
42
+ pip install "xyz17711-proxy-pool[langchain]"
43
+ ```
44
+
45
+ ```python
46
+ from xyz17711_proxy_pool import Client
47
+ from xyz17711_proxy_pool.langchain_tools import ProxyPoolToolkit
48
+
49
+ client = Client("https://api.17711.xyz/registry/proxy-pool")
50
+ tools = ProxyPoolToolkit(client=client).get_tools()
51
+ ```
52
+
53
+ ## License
54
+
55
+ MIT -- see the repository's own `LICENSE`.
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "xyz17711-proxy-pool"
3
+ version = "0.1.0"
4
+ description = "Generated typed client for the proxy-pool 17711.xyz microservice."
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ requires-python = ">=3.9"
8
+ dependencies = ["xyz17711>=0.1.0"]
9
+
10
+ # Only needed to import `langchain_tools` (never the base `client`/`models` modules) -- see that
11
+ # module's own doc comment for why it's an extra, never a hard dependency.
12
+ [project.optional-dependencies]
13
+ langchain = ["langchain-core>=1.2,<2.0"]
14
+ test = ["pytest>=8"]
15
+
16
+ [build-system]
17
+ requires = ["hatchling"]
18
+ build-backend = "hatchling.build"
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["src/xyz17711_proxy_pool"]
@@ -0,0 +1,4 @@
1
+ from .client import Client
2
+ from .models import * # noqa: F401,F403
3
+
4
+ __all__ = ["Client"]
@@ -0,0 +1,26 @@
1
+ """Typed HTTP client for proxy-pool -- one method per operation, each with a sync form and an async (`a`-prefixed) form."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+ from xyz17711_core import Client as _BaseClient
8
+
9
+ from .models import * # noqa: F401,F403 -- re-exported for callers building requests
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class Client(_BaseClient):
15
+ """Typed client for proxy-pool. Every method has a sync form and an async form (the `a`-prefixed sibling, e.g. `aget_best_proxy` next to `get_best_proxy`) sharing the same underlying connection/wallet -- pick whichever fits your own code."""
16
+
17
+ def get_best_proxy(self, anonymity: Optional[str] = None, city: Optional[str] = None, country: Optional[str] = None, protocol: Optional[str] = None) -> GetBestProxyResponse:
18
+ """Returns one of the fastest verified-alive proxies matching the filter, randomized to spread load ($0.001, x402)"""
19
+ logger.debug("get_best_proxy: calling %s", "/v1/proxy-pool/get_best_proxy")
20
+ return GetBestProxyResponse.model_validate(self._get("/v1/proxy-pool/get_best_proxy", {"anonymity": anonymity, "city": city, "country": country, "protocol": protocol}))
21
+
22
+ async def aget_best_proxy(self, anonymity: Optional[str] = None, city: Optional[str] = None, country: Optional[str] = None, protocol: Optional[str] = None) -> GetBestProxyResponse:
23
+ """Returns one of the fastest verified-alive proxies matching the filter, randomized to spread load ($0.001, x402) (async)"""
24
+ logger.debug("aget_best_proxy: calling %s", "/v1/proxy-pool/get_best_proxy")
25
+ return GetBestProxyResponse.model_validate(await self._aget("/v1/proxy-pool/get_best_proxy", {"anonymity": anonymity, "city": city, "country": country, "protocol": protocol}))
26
+
@@ -0,0 +1,39 @@
1
+ """LangChain `StructuredTool`s for proxy-pool, grouped in one `BaseToolkit` -- one tool per operation, each with both a sync and an async implementation. Requires the `langchain` extra: pip install "xyz17711-proxy-pool[langchain]"."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any, List, Optional
7
+
8
+ from langchain_core.tools import BaseTool, BaseToolkit, StructuredTool
9
+
10
+ from .client import Client
11
+ from .models import * # noqa: F401,F403 -- request models reused as tool args_schema
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class ProxyPoolToolkit(BaseToolkit):
17
+ """LangChain toolkit for proxy-pool -- one StructuredTool per operation, reusing this package's own generated client and request models directly. Each tool carries both a sync and an async (`coroutine=`) implementation, so `.invoke()` and `.ainvoke()` both work."""
18
+
19
+ client: Client
20
+ model_config = {"arbitrary_types_allowed": True}
21
+
22
+ def get_tools(self) -> List[BaseTool]:
23
+ logger.debug("ProxyPoolToolkit.get_tools: building 1 tool(s)")
24
+ def _get_best_proxy(anonymity: Optional[str] = None, city: Optional[str] = None, country: Optional[str] = None, protocol: Optional[str] = None) -> Any:
25
+ logger.debug("ProxyPoolToolkit.get_tools: calling get_best_proxy")
26
+ return self.client.get_best_proxy(anonymity=anonymity, city=city, country=country, protocol=protocol).model_dump()
27
+
28
+ async def _aget_best_proxy(anonymity: Optional[str] = None, city: Optional[str] = None, country: Optional[str] = None, protocol: Optional[str] = None) -> Any:
29
+ logger.debug("ProxyPoolToolkit.get_tools: calling get_best_proxy (async)")
30
+ return (await self.client.aget_best_proxy(anonymity=anonymity, city=city, country=country, protocol=protocol)).model_dump()
31
+
32
+ return [
33
+ StructuredTool.from_function(
34
+ func=_get_best_proxy,
35
+ coroutine=_aget_best_proxy,
36
+ name="get_best_proxy",
37
+ description="Returns one of the fastest verified-alive proxies matching the filter, randomized to spread load ($0.001, x402)",
38
+ ),
39
+ ]
@@ -0,0 +1,38 @@
1
+ """Pydantic request/response models for proxy-pool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, List, Optional
6
+
7
+ from pydantic import BaseModel
8
+
9
+
10
+ class ProxyView(BaseModel):
11
+ """A `SourceProxy` joined with its `CheckedProxy` result, if it has been checked at least once -- what every read tool actually filters/returns."""
12
+ anonymity: str
13
+ checked_alive: Optional[bool] = None # `None` if this service has never checked this proxy yet.
14
+ checked_at: Optional[int] = None
15
+ checked_consecutive_successes: int # See `CheckedProxy::consecutive_successes`'s own doc comment. `0` if never checked.
16
+ checked_ema_latency_ms: Optional[float] = None # See `CheckedProxy::ema_latency_ms`'s own doc comment.
17
+ checked_latency_ms: Optional[int] = None
18
+ checked_protocol: Optional[str] = None
19
+ city: str
20
+ country: str
21
+ id: str
22
+ ip: str
23
+ port: int
24
+ protocols: List[str]
25
+
26
+ @classmethod
27
+ def _dummy(cls) -> "ProxyView":
28
+ """A minimal, always-constructible instance -- test fixtures only, never real data."""
29
+ return cls(anonymity="test", checked_alive=None, checked_at=None, checked_consecutive_successes=0, checked_ema_latency_ms=None, checked_latency_ms=None, checked_protocol=None, city="test", country="test", id="test", ip="test", port=0, protocols=[])
30
+
31
+
32
+ class GetBestProxyResponse(BaseModel):
33
+ proxy: ProxyView
34
+
35
+ @classmethod
36
+ def _dummy(cls) -> "GetBestProxyResponse":
37
+ """A minimal, always-constructible instance -- test fixtures only, never real data."""
38
+ return cls(proxy=ProxyView._dummy())
File without changes
@@ -0,0 +1,45 @@
1
+ """Verifies every proxy-pool client method (sync and async) sends a request to the expected path/HTTP verb and parses a well-formed response into the expected type."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ import httpx
8
+
9
+ from xyz17711_proxy_pool import Client
10
+ from xyz17711_proxy_pool.models import * # noqa: F401,F403
11
+
12
+
13
+ def _mock_client(handler) -> Client:
14
+ """A Client wired to a fake transport for BOTH its sync and async HTTP clients -- no real network call ever happens in this test module. Found live: the async client is normally constructed LAZILY on first async use, so a test that only mocks `_http` (sync) silently lets an async test make a REAL network call instead of using the mock."""
15
+ client = Client("https://example.test")
16
+ client._http = httpx.Client(base_url="https://example.test", transport=httpx.MockTransport(handler))
17
+ client._ahttp = httpx.AsyncClient(base_url="https://example.test", transport=httpx.MockTransport(handler))
18
+ return client
19
+
20
+
21
+ def test_get_best_proxy_calls_the_expected_path():
22
+ def handler(request: httpx.Request) -> httpx.Response:
23
+ assert request.method == "GET"
24
+ assert request.url.path == "/v1/proxy-pool/get_best_proxy"
25
+ return httpx.Response(200, json=GetBestProxyResponse._dummy().model_dump(mode='json'))
26
+
27
+ client = _mock_client(handler)
28
+ result = client.get_best_proxy(anonymity=None, city=None, country=None, protocol=None)
29
+ assert isinstance(result, GetBestProxyResponse)
30
+
31
+
32
+ def test_aget_best_proxy_calls_the_expected_path():
33
+ def handler(request: httpx.Request) -> httpx.Response:
34
+ assert request.method == "GET"
35
+ assert request.url.path == "/v1/proxy-pool/get_best_proxy"
36
+ return httpx.Response(200, json=GetBestProxyResponse._dummy().model_dump(mode='json'))
37
+
38
+ client = _mock_client(handler)
39
+
40
+ async def run():
41
+ return await client.aget_best_proxy(anonymity=None, city=None, country=None, protocol=None)
42
+
43
+ result = asyncio.run(run())
44
+ assert isinstance(result, GetBestProxyResponse)
45
+
@@ -0,0 +1,43 @@
1
+ """Verifies every proxy-pool LangChain tool through the real StructuredTool (both `.invoke()` and `.ainvoke()`), not just the underlying client method -- catches schema-shape bugs a plain client-method test would miss. Requires the `langchain` extra."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ import httpx
8
+
9
+ from xyz17711_proxy_pool import Client
10
+ from xyz17711_proxy_pool.langchain_tools import ProxyPoolToolkit
11
+ from xyz17711_proxy_pool.models import * # noqa: F401,F403
12
+
13
+
14
+ def _mock_client(handler) -> Client:
15
+ client = Client("https://example.test")
16
+ client._http = httpx.Client(base_url="https://example.test", transport=httpx.MockTransport(handler))
17
+ client._ahttp = httpx.AsyncClient(base_url="https://example.test", transport=httpx.MockTransport(handler))
18
+ return client
19
+
20
+
21
+ def test_get_best_proxy_tool_invoke():
22
+ def handler(request: httpx.Request) -> httpx.Response:
23
+ return httpx.Response(200, json=GetBestProxyResponse._dummy().model_dump(mode='json'))
24
+
25
+ toolkit = ProxyPoolToolkit(client=_mock_client(handler))
26
+ tool = next(t for t in toolkit.get_tools() if t.name == "get_best_proxy")
27
+ result = tool.invoke({"anonymity": None, "city": None, "country": None, "protocol": None})
28
+ assert result is not None
29
+
30
+
31
+ def test_get_best_proxy_tool_ainvoke():
32
+ def handler(request: httpx.Request) -> httpx.Response:
33
+ return httpx.Response(200, json=GetBestProxyResponse._dummy().model_dump(mode='json'))
34
+
35
+ toolkit = ProxyPoolToolkit(client=_mock_client(handler))
36
+ tool = next(t for t in toolkit.get_tools() if t.name == "get_best_proxy")
37
+
38
+ async def run():
39
+ return await tool.ainvoke({"anonymity": None, "city": None, "country": None, "protocol": None})
40
+
41
+ result = asyncio.run(run())
42
+ assert result is not None
43
+