GeezerVison is showing github.com — test: guard private client hooks and pin client extras · proxymesh/python-proxy-headers@f5ed79d
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: .github/workflows/proxy_integration_tests.yml +21-12Lines changed: 21 additions & 12 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | | -# Integration tests against a real proxy (PROXY_URL). Add repository secret PROXY_URL | 2 | | -# under Settings → Secrets and variables → Actions, then mark this workflow as a required | 3 | | -# status check under branch protection (pull_request events only receive secrets for PRs | 4 | | -# from the same repository, not from forks). | | 1 | +# Pull-request checks. | | 2 | +# - unit: Python 3.10 and 3.15, client extras, header tests, the httpx guard, | | 3 | +# and private-hook checks (no PROXY_URL). | | 4 | +# - integration: real proxy via the PROXY_URL secret. pull_request events only | | 5 | +# receive secrets for PRs from this repository, not from forks. Add PROXY_URL | | 6 | +# under Settings → Secrets and variables → Actions, and mark this workflow as | | 7 | +# a required status check under branch protection. | | 8 | +# Runners are ubuntu-24.04. Do not switch these jobs to ubuntu-latest. | 5 | 9 |
| 6 | 10 | name: Proxy integration tests | 7 | 11 |
| @@ -14,6 +18,10 @@ permissions: | 14 | 18 | jobs: | 15 | 19 | unit: | 16 | 20 | runs-on: ubuntu-24.04 | | 21 | + strategy: | | 22 | + fail-fast: false | | 23 | + matrix: | | 24 | + python-version: ["3.10", "3.15"] | 17 | 25 | steps: | 18 | 26 | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | 19 | 27 | with: | @@ -22,20 +30,22 @@ jobs: | 22 | 30 | - name: Set up Python | 23 | 31 | uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 | 24 | 32 | with: | 25 | | - python-version: "3.x" | | 33 | + python-version: ${{ matrix.python-version }} | 26 | 34 |
| 27 | 35 | - name: Install system dependencies (pycurl) | 28 | 36 | run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev | 29 | 37 |
| 30 | | - - name: Install package and test dependencies | | 38 | + - name: Install package and compatible client extras | 31 | 39 | run: | | 32 | 40 | python -m pip install --upgrade pip | 33 | | - pip install -e . | 34 | | - pip install urllib3 requests aiohttp httpx pycurl | | 41 | + pip install -e ".[urllib3,requests,httpx,aiohttp,pycurl,cloudscraper,autoscraper]" | 35 | 42 |
| 36 | 43 | - name: Run header security and httpx guard tests | 37 | 44 | run: python test_header_security.py && python test_httpx_guard.py | 38 | 45 |
| | 46 | + - name: Run private-hook tests | | 47 | + run: python test_client_internals.py | | 48 | + | 39 | 49 | integration: | 40 | 50 | runs-on: ubuntu-24.04 | 41 | 51 |
| @@ -47,16 +57,15 @@ jobs: | 47 | 57 | - name: Set up Python | 48 | 58 | uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 | 49 | 59 | with: | 50 | | - python-version: "3.x" | | 60 | + python-version: "3.15" | 51 | 61 |
| 52 | 62 | - name: Install system dependencies (pycurl) | 53 | 63 | run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev | 54 | 64 |
| 55 | | - - name: Install package and test dependencies | | 65 | + - name: Install package and compatible client extras | 56 | 66 | run: | | 57 | 67 | python -m pip install --upgrade pip | 58 | | - pip install -e . | 59 | | - pip install urllib3 requests aiohttp httpx cloudscraper autoscraper pycurl | | 68 | + pip install -e ".[urllib3,requests,httpx,aiohttp,pycurl,cloudscraper,autoscraper]" | 60 | 69 |
| 61 | 70 | - name: Require PROXY_URL Actions secret | 62 | 71 | env: | |
Collapse file Copy file name to clipboardExpand all lines: CHANGELOG.md +3Lines changed: 3 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
| 3 | 3 | ## Unreleased | 4 | 4 |
| 5 | 5 | - Importing `python_proxy_headers.httpx_proxy` on httpx >= 1 raises `ImportError`. httpx 1.0 removed proxy support. The `[httpx]` extra is `httpx>=0.28,<1`. | | 6 | +- Optional extras pin the client ranges CI installs: `[urllib3]`, `[requests]`, `[httpx]`, `[aiohttp]`, `[pycurl]`, `[cloudscraper]`, and `[autoscraper]`. | | 7 | +- `requires-python` is `>=3.10`. Python 3.8 and 3.9 are no longer supported. Those clients require 3.10+, and the httpx adapter already uses `str | None`. | | 8 | +- Keywords, trove classifiers, and the changelog URL now point at this file. | 6 | 9 |
| 7 | 10 | ## 0.2.4 - 2026-11-09 | 8 | 11 |
| |
Collapse file Copy file name to clipboardExpand all lines: README.md +22-3Lines changed: 22 additions & 3 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -33,9 +33,21 @@ This library solves both problems for popular Python HTTP libraries. | 33 | 33 | pip install python-proxy-headers | 34 | 34 | ``` | 35 | 35 |
| 36 | | -Then install the HTTP library you want to use (e.g., `pip install requests`). | | 36 | +Or install the client extra you need. Each extra pins a range that CI tests: | 37 | 37 |
| 38 | | -> **Note:** This package has no dependencies by default - install only what you need. | | 38 | +```bash | | 39 | +pip install 'python-proxy-headers[requests]' | | 40 | +pip install 'python-proxy-headers[urllib3]' | | 41 | +pip install 'python-proxy-headers[httpx]' | | 42 | +pip install 'python-proxy-headers[aiohttp]' | | 43 | +pip install 'python-proxy-headers[pycurl]' | | 44 | +pip install 'python-proxy-headers[cloudscraper]' | | 45 | +pip install 'python-proxy-headers[autoscraper]' | | 46 | +``` | | 47 | + | | 48 | +The `httpx` extra is `httpx>=0.28,<1`. Importing `python_proxy_headers.httpx_proxy` on httpx 1.x raises `ImportError`. See the [httpx docs](https://python-proxy-headers.readthedocs.io/en/latest/httpx.html). Proxy header names are specific to your provider; ProxyMesh documents theirs at [docs.proxymesh.com](https://docs.proxymesh.com/). | | 49 | + | | 50 | +> **Note:** This package has no required dependencies. Install only the extra you need. | 39 | 51 |
| 40 | 52 | ## Quick Start | 41 | 53 |
| @@ -122,6 +134,11 @@ response = scraper.get('https://example.com') | 122 | 134 | A test harness is included to verify proxy header functionality: | 123 | 135 |
| 124 | 136 | ```bash | | 137 | +# Header checks and private-hook checks (no proxy credentials) | | 138 | +python test_header_security.py | | 139 | +python test_httpx_guard.py | | 140 | +python test_client_internals.py | | 141 | + | 125 | 142 | # Set your proxy | 126 | 143 | | 127 | 144 |
| @@ -142,8 +159,10 @@ For detailed documentation, API reference, and more examples: | 142 | 159 | - **Full Documentation:** [python-proxy-headers.readthedocs.io](https://python-proxy-headers.readthedocs.io/en/latest/) | 143 | 160 | - **Example Code:** [proxy-examples for Python](https://github.com/proxymesh/proxy-examples/tree/main/python) | 144 | 161 |
| 145 | | -## Related Projects | | 162 | +## Other languages | 146 | 163 |
| | 164 | +- **[javascript-proxy-headers](https://github.com/proxymesh/javascript-proxy-headers)** - Send and read custom proxy headers on HTTPS CONNECT for Node.js HTTP clients | | 165 | +- **[ruby-proxy-headers](https://github.com/proxymesh/ruby-proxy-headers)** - Send and read custom proxy headers on HTTPS CONNECT for Ruby HTTP clients | 147 | 166 | - **[scrapy-proxy-headers](https://github.com/proxymesh/scrapy-proxy-headers)** - Proxy header support for Scrapy | 148 | 167 |
| 149 | 168 | ## About | |
Collapse file Copy file name to clipboardExpand all lines: docs/index.rst +10-3Lines changed: 10 additions & 3 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -25,20 +25,27 @@ None of these modules provide good support for parsing custom response headers f | 25 | 25 |
| 26 | 26 | If you are looking for `Scrapy <https://scrapy.org/>`_ support, please see our `scrapy-proxy-headers <https://github.com/proxymesh/scrapy-proxy-headers>`_ project. | 27 | 27 |
| | 28 | +The same CONNECT header support in other languages: | | 29 | + | | 30 | +* `javascript-proxy-headers <https://github.com/proxymesh/javascript-proxy-headers>`_ | | 31 | +* `ruby-proxy-headers <https://github.com/proxymesh/ruby-proxy-headers>`_ | | 32 | + | 28 | 33 | Installation | 29 | 34 | ------------ | 30 | 35 |
| 31 | 36 | To use these extension modules, you must first do the following: | 32 | 37 |
| 33 | | -1. Install the package: | | 38 | +1. Install the package, optionally with the client you use: | 34 | 39 |
| 35 | 40 | .. code-block:: bash | 36 | 41 |
| 37 | 42 | pip install python-proxy-headers | | 43 | + pip install 'python-proxy-headers[requests]' | | 44 | + pip install 'python-proxy-headers[httpx]' | 38 | 45 |
| 39 | | -2. Install the appropriate package based on the Python module you want to use. | | 46 | +2. Or install the HTTP library yourself. Extras pin the ranges we test. The ``httpx`` extra is ``httpx>=0.28,<1``. Importing it on httpx 1.x raises ``ImportError``; see :doc:`httpx`. Proxy-specific headers are documented by your provider. ProxyMesh's are at `docs.proxymesh.com <https://docs.proxymesh.com/>`_. | 40 | 47 |
| 41 | | -This package does not have any dependencies because we don't know which module you want to use. | | 48 | +This package does not have any required dependencies because we don't know which module you want to use. | 42 | 49 |
| 43 | 50 | You can also find more example code in our `proxy-examples for python <https://github.com/proxymesh/proxy-examples/tree/main/python>`_. | 44 | 51 |
| |
Collapse file Copy file name to clipboardExpand all lines: pyproject.toml +37-5Lines changed: 37 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -10,22 +10,54 @@ authors = [ | 10 | 10 | ] | 11 | 11 | description = "Handle custom proxy headers for http & https requests in various python libraries" | 12 | 12 | readme = "README.md" | 13 | | -requires-python = ">=3.8" | | 13 | +requires-python = ">=3.10" | | 14 | +keywords = [ | | 15 | + "proxy", | | 16 | + "proxy-headers", | | 17 | + "https-proxy", | | 18 | + "connect", | | 19 | + "tunnel", | | 20 | + "requests", | | 21 | + "urllib3", | | 22 | + "httpx", | | 23 | + "aiohttp", | | 24 | + "web-scraping", | | 25 | + "proxymesh", | | 26 | +] | 14 | 27 | classifiers = [ | | 28 | + "Development Status :: 4 - Beta", | 15 | 29 | "Programming Language :: Python :: 3", | | 30 | + "Programming Language :: Python :: 3 :: Only", | | 31 | + "Programming Language :: Python :: 3.10", | | 32 | + "Programming Language :: Python :: 3.11", | | 33 | + "Programming Language :: Python :: 3.12", | | 34 | + "Programming Language :: Python :: 3.13", | | 35 | + "Programming Language :: Python :: 3.14", | | 36 | + "Programming Language :: Python :: 3.15", | 16 | 37 | "Operating System :: OS Independent", | 17 | | - "License :: OSI Approved :: MIT License", | 18 | | - "Intended Audience :: Developers", | 19 | | - "Topic :: Internet :: WWW/HTTP", | | 38 | + "License :: OSI Approved :: MIT License", | | 39 | + "Intended Audience :: Developers", | | 40 | + "Topic :: Internet :: WWW/HTTP", | | 41 | + "Topic :: Internet :: Proxy Servers", | 20 | 42 | "Topic :: Software Development :: Libraries :: Python Modules", | | 43 | + "Framework :: AsyncIO", | 21 | 44 | ] | 22 | 45 |
| | 46 | +# Lower bounds are the oldest minor whose private hooks still match the | | 47 | +# adapters. CI installs the newest release inside each range. httpx 1.x is | | 48 | +# excluded: 1.0.dev6 drops httpcore and proxy support (encode/httpx#947). | 23 | 49 | [project.optional-dependencies] | | 50 | +urllib3 = ["urllib3>=2.2,<3"] | | 51 | +requests = ["requests>=2.31,<3"] | 24 | 52 | httpx = ["httpx>=0.28,<1"] | | 53 | +aiohttp = ["aiohttp>=3.10,<4"] | | 54 | +pycurl = ["pycurl>=7.45,<8"] | | 55 | +cloudscraper = ["cloudscraper>=1.2.71,<2"] | | 56 | +autoscraper = ["autoscraper>=1.1.14,<2"] | 25 | 57 |
| 26 | 58 | [project.urls] | 27 | 59 | Homepage = "https://github.com/proxymesh/python-proxy-headers" | 28 | | -Changelog = "https://github.com/proxymesh/python-proxy-headers/commits/main/" | | 60 | +Changelog = "https://github.com/proxymesh/python-proxy-headers/blob/main/CHANGELOG.md" | 29 | 61 | Issues = "https://github.com/proxymesh/python-proxy-headers/issues" | 30 | 62 | Documentation = "https://python-proxy-headers.readthedocs.io/en/latest/" | 31 | 63 | ProxyMesh = "https://proxymesh.com" |
Collapse file Copy file name to clipboard +86Lines changed: 86 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +"""The private hooks this package overrides are still present upstream.""" | | 2 | + | | 3 | +import http.client | | 4 | +import inspect | | 5 | +import sys | | 6 | +import unittest | | 7 | + | | 8 | +from aiohttp.connector import TCPConnector | | 9 | +from httpcore._async.http_proxy import AsyncHTTPProxy, AsyncTunnelHTTPConnection | | 10 | +from httpcore._sync.http_proxy import HTTPProxy, TunnelHTTPConnection | | 11 | +from urllib3.connectionpool import HTTPSConnectionPool | | 12 | + | | 13 | +from python_proxy_headers.aiohttp_proxy import ProxyTCPConnector | | 14 | +from python_proxy_headers.httpx_proxy import ( | | 15 | + AsyncHTTPProxyHeaders, | | 16 | + AsyncProxyTunnelHTTPConnection, | | 17 | + HTTPProxyHeaders, | | 18 | + ProxyTunnelHTTPConnection, | | 19 | +) | | 20 | +from python_proxy_headers.urllib3_proxy_manager import ( | | 21 | + HTTPSProxyConnection, | | 22 | + HTTPSProxyConnectionPool, | | 23 | +) | | 24 | + | | 25 | + | | 26 | +class HookTests(unittest.TestCase): | | 27 | + def test_urllib3_prepare_proxy(self) -> None: | | 28 | + self.assertIsNot( | | 29 | + HTTPSProxyConnectionPool._prepare_proxy, | | 30 | + HTTPSConnectionPool._prepare_proxy, | | 31 | + ) | | 32 | + self.assertEqual( | | 33 | + list(inspect.signature(HTTPSProxyConnectionPool._prepare_proxy).parameters), | | 34 | + list(inspect.signature(HTTPSConnectionPool._prepare_proxy).parameters), | | 35 | + ) | | 36 | + self.assertTrue(callable(http.client._read_headers)) | | 37 | + if sys.version_info < (3, 12): | | 38 | + self.assertTrue(callable(HTTPSProxyConnection._tunnel)) | | 39 | + else: | | 40 | + self.assertTrue(callable(http.client.HTTPConnection._tunnel)) | | 41 | + self.assertTrue( | | 42 | + callable(http.client.HTTPConnection.get_proxy_response_headers) | | 43 | + ) | | 44 | + | | 45 | + def test_httpx_handle_request(self) -> None: | | 46 | + self.assertIsNot( | | 47 | + ProxyTunnelHTTPConnection.handle_request, | | 48 | + TunnelHTTPConnection.handle_request, | | 49 | + ) | | 50 | + self.assertIsNot( | | 51 | + AsyncProxyTunnelHTTPConnection.handle_async_request, | | 52 | + AsyncTunnelHTTPConnection.handle_async_request, | | 53 | + ) | | 54 | + self.assertIsNot( | | 55 | + HTTPProxyHeaders.create_connection, | | 56 | + HTTPProxy.create_connection, | | 57 | + ) | | 58 | + self.assertIsNot( | | 59 | + AsyncHTTPProxyHeaders.create_connection, | | 60 | + AsyncHTTPProxy.create_connection, | | 61 | + ) | | 62 | + params = inspect.signature(TunnelHTTPConnection.__init__).parameters | | 63 | + for name in ( | | 64 | + "proxy_origin", | | 65 | + "remote_origin", | | 66 | + "ssl_context", | | 67 | + "proxy_ssl_context", | | 68 | + "proxy_headers", | | 69 | + "keepalive_expiry", | | 70 | + "http1", | | 71 | + "http2", | | 72 | + "network_backend", | | 73 | + ): | | 74 | + self.assertIn(name, params) | | 75 | + | | 76 | + def test_aiohttp_create_proxy_connection(self) -> None: | | 77 | + self.assertEqual( | | 78 | + list( | | 79 | + inspect.signature(ProxyTCPConnector._create_proxy_connection).parameters | | 80 | + ), | | 81 | + list(inspect.signature(TCPConnector._create_proxy_connection).parameters), | | 82 | + ) | | 83 | + | | 84 | + | | 85 | +if __name__ == "__main__": | | 86 | + unittest.main() |
|