diff --git a/.github/workflows/python-test-versions.yml b/.github/workflows/python-test-versions.yml index 2535157f..f842a398 100644 --- a/.github/workflows/python-test-versions.yml +++ b/.github/workflows/python-test-versions.yml @@ -9,13 +9,16 @@ on: pull_request: branches: [master] +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"] poetry-version: ["2.3.2"] steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 008455d3..0ab16b40 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks default_language_version: # `python3` (any 3.x) so contributors on any of the supported Python - # versions (3.10-3.14) can run pre-commit without installing an extra + # versions (3.10-3.15) can run pre-commit without installing an extra # interpreter. Pinning a specific minor breaks the others. python: python3 fail_fast: false diff --git a/CHANGELOG.md b/CHANGELOG.md index e2531c4d..1ba51874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Python 3.15 on the supported matrix (now 3.10-3.15). CPython 3.15.0 + shipped on 2026-10-09. The published image stays on `python:3.14-slim`; + the executable and publish workflows still build with 3.14. + +### Fixed +- User-facing dependency floors now match `setup.py`. The docs homepage + still said aiohttp >= 3.8.0, aiodns >= 3.0.0, and maxminddb >= 2.0.0. + `docs/requirements.txt` and `environment.yml` carried older, mismatched floors + (aiohttp >= 3.12.0, aiodns >= 3.4.0, maxminddb >= 2.7.0). + +### Security +- Raise the aiohttp constraint floor to `^3.14.4` (`setup.py`: `>=3.14.4`). + `^3.14.1` still allowed 3.14.1–3.14.3, which miss the October 2026 fixes + shipped in 3.14.4: GHSA-2g87-pp6c-29x5, GHSA-9xw5-w5jj-pvcg, and + GHSA-5wr8-gcqf-gqf3. OSV rates them HIGH (CVSS 4.0); aiohttp's GitHub + advisories label them medium, medium, and low. `poetry.lock` stays on 3.14.5. + ## [2.0.0b3] - 2026-05-09 🌐 **Full IPv6 support — first-class across the stack** diff --git a/CLAUDE.md b/CLAUDE.md index cabe157f..4a93ad72 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,8 +7,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ProxyBroker2 is an async proxy finder, checker, and server. It discovers public proxies from 50+ sources, validates them against judge servers, and can operate as a rotating proxy server. **Repository**: `bluet/proxybroker2` (GitHub) -**Python**: 3.10-3.14 -**Key Dependencies**: aiohttp 3.12.0+, aiodns 3.4.0+, attrs 25.3.0+, asyncio +**Python**: 3.10-3.15 +**Key Dependencies**: aiohttp >=3.14.4, aiodns >=4.0.4, attrs >=26.1.0,<27, maxminddb >=3.1.1, cachetools >=7.1.4, click >=8.4.1, PyYAML >=6.0.3 ## Common Development Commands diff --git a/README.md b/README.md index 4eaec35d..3e144302 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ProxyBroker2 *🚀 Production-Ready Async Proxy Management* -**The modernized, actively-maintained successor to ProxyBroker (v0.3.2, abandoned 2018) — Python 3.10-3.14, comprehensive test coverage, first-class IPv6.** +**The modernized, actively-maintained successor to ProxyBroker (v0.3.2, abandoned 2018) — Python 3.10-3.15, comprehensive test coverage, first-class IPv6.** [![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors-) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fbluet%2Fproxybroker2.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fbluet%2Fproxybroker2?ref=badge_shield) @@ -41,7 +41,7 @@ ProxyBroker2 is the actively-maintained successor to the original ProxyBroker (v ### 🚀 What you get over v0.3.2 - **Active maintenance** — regular security updates and feature work -- **Modern Python support** — Python 3.10-3.14 (v0.3.2 was Python 3.5+ only) +- **Modern Python support** — Python 3.10-3.15 (v0.3.2 was Python 3.5+ only) - **Zero critical bugs** — fixed deadlocks, memory leaks, signal handler leaks, race conditions that existed in v0.3.2 - **First-class IPv6** — RFC 5952 canonical form, SOCKS5 `ATYP=0x04`, bracketed URI authority. v0.3.2 was IPv4-only. - **Custom providers** — drop YAML/JSON config files into a folder, no code required (see [Custom Proxy Providers](#-custom-proxy-providers) below) @@ -72,7 +72,7 @@ $ docker run --rm bluet/proxybroker2 --help Requirements ------------ -- **Python 3.10-3.14** +- **Python 3.10-3.15** That's it — all Python dependencies are installed automatically by pip / Poetry / uv. diff --git a/docs/requirements.txt b/docs/requirements.txt index 1fab1026..ea41eab3 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,6 +2,10 @@ sphinx>=8.0 myst-parser>=4.0 alabaster>=1.0.0 linkify-it-py>=2.0.0 -aiohttp>=3.12.0 -aiodns>=3.4.0 -maxminddb>=2.7.0 +aiohttp>=3.14.4 +aiodns>=4.0.4 +attrs>=26.1.0,<27.0.0 +maxminddb>=3.1.1 +cachetools>=7.1.4 +click>=8.4.1 +pyyaml>=6.0.3 diff --git a/docs/source/index.rst b/docs/source/index.rst index aaedc0cd..9b6ddba4 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,7 +14,7 @@ ProxyBroker2 :target: https://github.com/bluet/proxybroker2/releases .. image:: https://img.shields.io/github/actions/workflow/status/bluet/proxybroker2/python-test-versions.yml?style=flat-square :target: https://github.com/bluet/proxybroker2/actions -.. image:: https://img.shields.io/badge/python-3.10--3.13-blue.svg?style=flat-square +.. image:: https://img.shields.io/badge/python-3.10--3.15-blue.svg?style=flat-square :target: https://github.com/bluet/proxybroker2 .. image:: https://img.shields.io/github/license/bluet/proxybroker2.svg?style=flat-square :target: https://github.com/bluet/proxybroker2/blob/master/LICENSE @@ -25,7 +25,7 @@ ProxyBroker2 is a **production-ready** async proxy finder, checker, and server t * ✅ **Zero Critical Bugs** - All signal handler leaks, deadlocks, and heap corruption fixed * ✅ **100% Test Coverage** - All 131 tests passing with comprehensive validation -* ✅ **Modern Python Support** - Full compatibility with Python 3.10-3.13 +* ✅ **Modern Python Support** - Full compatibility with Python 3.10-3.15 * ✅ **Production Ready** - Thoroughly tested and optimized for real-world usage .. image:: _static/index_find_example.gif @@ -46,10 +46,17 @@ Features Requirements ------------ -* Python **3.10** or higher -* `aiohttp `_ >= 3.8.0 -* `aiodns `_ >= 3.0.0 -* `maxminddb `_ >= 2.0.0 +* Python **3.10** or higher (tested on 3.10–3.15) +* `aiohttp `_ >= 3.14.4 +* `aiodns `_ >= 4.0.4 +* `attrs `_ >= 26.1.0, < 27 +* `maxminddb `_ >= 3.1.1 +* `cachetools `_ >= 7.1.4 +* `click `_ >= 8.4.1 +* `PyYAML `_ >= 6.0.3 + +These are the minimum versions ``pip`` will accept. Poetry uses the same floors +and caps each dependency before the next major version. Installation diff --git a/environment.yml b/environment.yml index 9d752f2f..f897fd0a 100644 --- a/environment.yml +++ b/environment.yml @@ -8,9 +8,10 @@ dependencies: - sphinx>=8.0 - myst-parser>=4.0 - alabaster>=1.0.0 - - aiohttp>=3.12.0 - - aiodns>=3.4.0 - - maxminddb>=2.7.0 - - attrs>=25.3.0 - - cachetools>=5.5.2 - - click>=8.2.1 + - aiohttp>=3.14.4 + - aiodns>=4.0.4 + - maxminddb>=3.1.1 + - attrs>=26.1.0,<27.0.0 + - cachetools>=7.1.4 + - click>=8.4.1 + - pyyaml>=6.0.3 diff --git a/poetry.lock b/poetry.lock index 294c6a4f..54fd5ec3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1708,4 +1708,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "9ba21fdacf260e0ad8b112886c4cfa9c88336c45f0851675ac29206c64533561" +content-hash = "bce99e0e0ed60f6b84dd770b45c2d3ca5fc76ea185104d646f681ac3341c23d0" diff --git a/pyproject.toml b/pyproject.toml index 1e8ad8cf..945b0457 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ classifiers = [ 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: 3.14', + 'Programming Language :: Python :: 3.15', 'Operating System :: POSIX', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', @@ -32,15 +33,17 @@ classifiers = [ # Lower bounds bumped from prior version to clear known vulnerabilities at # the constraint floor (snyk scans the lower bound when no specific version # is locked). Notable: -# - click ^8.3.3 fixes SNYK-PYTHON-CLICK-16347201 (HIGH, command injection -# in click.edit() filename param) -# - aiohttp ^3.13.5 fixes multiple HIGH vulns (SSRF on Windows static -# handler, memory exhaustion in multipart/Request.post/ZLibDecompressor, -# request smuggling) +# - click ^8.4.1. 8.3.3 fixes SNYK-PYTHON-CLICK-16347201 (HIGH, command +# injection in click.edit() filename param); the floor tracks the lock. +# - aiohttp ^3.14.4. No current OSV advisory is fixed only after 3.14.4. +# 3.14.1–3.14.3 still miss the October 2026 fixes GHSA-2g87-pp6c-29x5, +# GHSA-9xw5-w5jj-pvcg, and GHSA-5wr8-gcqf-gqf3. OSV rates them HIGH +# (CVSS 4.0); aiohttp's GitHub advisories label them medium, medium, low. +# poetry.lock resolves 3.14.5. # Other deps bumped to match poetry.lock current resolution for consistency. [tool.poetry.dependencies] python = "^3.10" -aiohttp = "^3.14.1" +aiohttp = "^3.14.4" aiodns = "^4.0.4" attrs = ">=26.1.0,<27.0.0" maxminddb = "^3.1.1" diff --git a/setup.py b/setup.py index e622739a..9301cf25 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def get_version(): INFO["long_description"] = f.read() REQUIRES = [ - "aiohttp>=3.14.1", + "aiohttp>=3.14.4", "aiodns>=4.0.4", "attrs>=26.1.0,<27.0.0", "maxminddb>=3.1.1", @@ -76,6 +76,7 @@ def get_version(): "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", diff --git a/tests/test_integration.py b/tests/test_integration.py index 29edf9b1..647bc37d 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -17,39 +17,45 @@ class TestUserWorkflows: """Test real user workflows based on examples/ directory.""" @pytest.mark.asyncio - async def test_basic_find_workflow(self): - """Test the basic find workflow from examples/basic.py. - - Users depend on this workflow pattern working consistently. + async def test_basic_find_workflow(self, mocker): + """find() accepts the example kwargs and finishes an empty run. + + ``examples/basic.py`` stops when the proxy queue yields ``None``. + ``find()`` discovers this host's external IP before it creates the + checker. That probe has no overall deadline (each candidate gets + the full timeout), so a live call can sit there longer than any + fixed poll when CI cannot reach the IP services. The probe and + the judge/provider fan-out are covered elsewhere; this test stubs + them and uses ``providers=[]``, which is the supported "no bundled + feeds" contract. """ proxies = asyncio.Queue() broker = Broker( - proxies, timeout=2, max_conn=5, max_tries=1, stop_broker_on_sigint=False + proxies, + timeout=2, + max_conn=5, + max_tries=1, + stop_broker_on_sigint=False, + providers=[], + ) + discovered = frozenset({"203.0.113.10"}) + mocker.patch.object( + broker._resolver, + "get_real_ext_ips", + new=mocker.AsyncMock(return_value=discovered), ) + # Default judges are real URLs. ``check_judges`` is what dials them. + mocker.patch("proxybroker.checker.Checker.check_judges", new=mocker.AsyncMock()) - # Test that the basic API contract works - we don't need to complete the operation - # Just verify the API accepts the expected parameters without errors. + await broker.find(types=["HTTP", "HTTPS"], limit=1) try: - find_task = asyncio.create_task( - broker.find(types=["HTTP", "HTTPS"], limit=1) - ) - # Wait for the broker to actually initialize its checker rather - # than relying on a fixed sleep (which races on slow CI runners). - for _ in range(50): # up to 5s - if broker._checker is not None: - break - await asyncio.sleep(0.1) - find_task.cancel() - try: - await find_task - except asyncio.CancelledError: - pass - except Exception as e: - # If it fails immediately, that indicates an API contract issue - pytest.fail(f"find() API contract broken: {e}") - - # Verify broker was properly configured for the find operation. - assert broker._checker is not None # Checker should be created for find() + assert broker._checker is not None + assert broker._checker._real_ext_ips == discovered + # ``_grab`` pushes this sentinel after an empty provider pass. + # A 2s cap only covers local scheduling, not network probes. + assert await asyncio.wait_for(proxies.get(), timeout=2) is None + finally: + broker.stop() @pytest.mark.asyncio async def test_proxy_pool_workflow(self):