Skip to content

Commit 2fa320d

Browse files
Python 3.11 support (#1384)
* Changes for Python 3.11 support * Updated README.md for versioning info * Update `httpx==0.27.0` to avoid `cgi` deprecation warning from pytest on Python 3.11 * Make tests work for 3.11 * Declare support for 3.11 * Use 3.11-alpine for Docker images * Preserve pylint version for `python_version <= 3.10` * Preserve httpx version for <= 3.10 * `httpx` usage fix in tests for <=3.10 * Adjust pylint and pytest for >= 3.11 * Use 3.11.8, bad-option-value and httpx proxies fix * tox for 3.11 * Fix for `TOXENV: py` * -vv for pytest * Downgrade to `pytest-asyncio==0.21.1` * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * remove asyncio_mode=strict * try with `pytest-cov==4.1.0` for 3.11 * bump coverage for 3.11 * Try `3.11` in GitHub workflow which installs >3.11.8 unavailable via pyenv yet * Revert back to `-v` --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 81510a0 commit 2fa320d

File tree

20 files changed

+109
-32
lines changed

20 files changed

+109
-32
lines changed

.github/workflows/test-library.yml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,8 +446,9 @@ jobs:
446446
# NOTE: The latest and the lowest supported Pythons are prioritized
447447
# NOTE: to improve the responsiveness. It's nice to see the most
448448
# NOTE: important results first.
449-
- '3.10'
449+
- '3.11'
450450
- 3.6
451+
- '3.10'
451452
- 3.9
452453
- 3.8
453454
- 3.7
@@ -463,7 +464,7 @@ jobs:
463464
env:
464465
PY_COLORS: 1
465466
TOX_PARALLEL_NO_SPINNER: 1
466-
TOXENV: python
467+
TOXENV: py
467468

468469
steps:
469470
- name: Switch to using Python v${{ matrix.python }}
@@ -500,7 +501,15 @@ jobs:
500501
steps.calc-cache-key-py.outputs.py-hash-key
501502
}}-
502503
${{ runner.os }}-pip-
503-
- name: Install tox
504+
- name: Install tox for >= 3.11
505+
if: matrix.python == '3.11'
506+
run: >-
507+
python -m
508+
pip install
509+
--user
510+
tox==4.14.2
511+
- name: Install tox for < 3.11
512+
if: matrix.python != '3.11'
504513
run: >-
505514
python -m
506515
pip install

.pylintrc

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ disable=raw-checker-failed,
131131
useless-return,
132132
useless-super-delegation,
133133
wrong-import-order,
134+
# Required because to support 3.11
135+
# we added unnecessary-dunder-call which is not supported for <=3.11
136+
# see https://github.com/abhinavsingh/proxy.py/actions/runs/8671404475/job/23780537848?pr=1384
137+
bad-option-value
134138

135139
# Enable the message, report, category or checker with the given id(s). You can
136140
# either give multiple identifier separated by comma (,) or put this option
@@ -419,7 +423,7 @@ contextmanager-decorators=contextlib.contextmanager
419423
# List of members which are set dynamically and missed by pylint inference
420424
# system, and so shouldn't trigger E1101 when accessed. Python regular
421425
# expressions are accepted.
422-
generated-members=
426+
generated-members=os,io
423427

424428
# Tells whether missing members accessed in mixin class should be ignored. A
425429
# mixin class is detected if its name ends with "mixin" (case insensitive).
@@ -446,7 +450,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local
446450
# (useful for modules/projects where namespaces are manipulated during runtime
447451
# and thus existing member attributes cannot be deduced by static analysis). It
448452
# supports qualified module names, as well as Unix pattern matching.
449-
ignored-modules=
453+
ignored-modules=abc
450454

451455
# Show a hint with possible names when a member name was not found. The aspect
452456
# of finding the hint is based on edit distance.
@@ -605,5 +609,5 @@ preferred-modules=
605609

606610
# Exceptions that will emit a warning when being caught. Defaults to
607611
# "BaseException, Exception".
608-
overgeneral-exceptions=BaseException,
609-
Exception
612+
overgeneral-exceptions=builtins.BaseException,
613+
builtins.Exception

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.10-alpine as base
1+
FROM python:3.11-alpine as base
22

33
LABEL com.abhinavsingh.name="abhinavsingh/proxy.py" \
44
com.abhinavsingh.description="⚡ Fast • 🪶 Lightweight • 0️⃣ Dependency • 🔌 Pluggable • \

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ lib-mypy:
120120
tox -e lint -- mypy --all-files
121121

122122
lib-pytest:
123-
$(PYTHON) -m tox -e python -- -v
123+
$(PYTHON) -m tox -e py -- -v
124124

125125
lib-test: lib-clean lib-check lib-lint lib-pytest
126126

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
[![iOS, iOS Simulator](https://img.shields.io/static/v1?label=tested%20with&message=iOS%20%F0%9F%93%B1%20%7C%20iOS%20Simulator%20%F0%9F%93%B1&color=darkgreen&style=for-the-badge)](https://abhinavsingh.com/proxy-py-a-lightweight-single-file-http-proxy-server-in-python/)
1414

1515
[![pypi version](https://img.shields.io/pypi/v/proxy.py?style=flat-square)](https://pypi.org/project/proxy.py/)
16-
[![Python 3.x](https://img.shields.io/static/v1?label=Python&message=3.6%20%7C%203.7%20%7C%203.8%20%7C%203.9%20%7C%203.10&color=blue&style=flat-square)](https://www.python.org/)
16+
[![Python 3.x](https://img.shields.io/static/v1?label=Python&message=3.6%20%7C%203.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11&color=blue&style=flat-square)](https://www.python.org/)
1717
[![Checked with mypy](https://img.shields.io/static/v1?label=MyPy&message=checked&color=blue&style=flat-square)](http://mypy-lang.org/)
1818

1919
[![doc](https://img.shields.io/readthedocs/proxypy/latest?style=flat-square&color=darkgreen)](https://proxypy.readthedocs.io/)
@@ -2366,7 +2366,7 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
23662366
[--filtered-client-ips FILTERED_CLIENT_IPS]
23672367
[--filtered-url-regex-config FILTERED_URL_REGEX_CONFIG]
23682368

2369-
proxy.py v2.4.4rc5.dev36+g6c9d0315.d20240411
2369+
proxy.py v2.4.4rc6.dev11+gac1f05d7.d20240413
23702370

23712371
options:
23722372
-h, --help show this help message and exit
@@ -2489,8 +2489,8 @@ options:
24892489
Default: None. Signing certificate to use for signing
24902490
dynamically generated HTTPS certificates. If used,
24912491
must also pass --ca-key-file and --ca-signing-key-file
2492-
--ca-file CA_FILE Default: /Users/abhinavsingh/Dev/proxy.py/.venv/lib/py
2493-
thon3.10/site-packages/certifi/cacert.pem. Provide
2492+
--ca-file CA_FILE Default: /Users/abhinavsingh/Dev/proxy.py/.venv3118/li
2493+
b/python3.11/site-packages/certifi/cacert.pem. Provide
24942494
path to custom CA bundle for peer certificate
24952495
verification
24962496
--ca-signing-key-file CA_SIGNING_KEY_FILE

benchmark/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
aiohttp==3.8.1
1+
aiohttp==3.8.2
22
# Blacksheep depends upon essentials_openapi which is pinned to pyyaml==5.4.1
33
# and pyyaml>5.3.1 is broken for cython 3
44
# See https://github.com/yaml/pyyaml/issues/724#issuecomment-1638587228

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@
321321
(_py_class_role, 'HostPort'),
322322
(_py_class_role, 'TcpOrTlsSocket'),
323323
(_py_class_role, 're.Pattern'),
324+
(_py_class_role, 'proxy.core.base.tcp_server.T'),
325+
(_py_class_role, 'proxy.common.types.RePattern'),
324326
(_py_obj_role, 'proxy.core.work.threadless.T'),
325327
(_py_obj_role, 'proxy.core.work.work.T'),
326328
(_py_obj_role, 'proxy.core.base.tcp_server.T'),

examples/web_scraper.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
:license: BSD, see LICENSE for more details.
1010
"""
1111
import time
12+
from abc import abstractmethod
13+
from typing import Any
1214

1315
from proxy import Proxy
1416
from proxy.core.work import Work
@@ -52,6 +54,11 @@ async def handle_events(
5254
Return True to shutdown work."""
5355
return False
5456

57+
@staticmethod
58+
@abstractmethod
59+
def create(*args: Any) -> TcpClientConnection:
60+
raise NotImplementedError()
61+
5562

5663
if __name__ == '__main__':
5764
with Proxy(

proxy/common/types.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import queue
1515
import socket
1616
import ipaddress
17-
from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Union
17+
from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Union, TypeVar
1818

1919

2020
if TYPE_CHECKING: # pragma: no cover
@@ -34,8 +34,8 @@
3434
HostPort = Tuple[str, int]
3535

3636
if sys.version_info.minor == 6:
37-
RePattern = Any
37+
RePattern = TypeVar('RePattern', bound=Any)
3838
elif sys.version_info.minor in (7, 8):
39-
RePattern = re.Pattern # type: ignore
39+
RePattern = TypeVar('RePattern', bound=re.Pattern) # type: ignore
4040
else:
41-
RePattern = re.Pattern[Any] # type: ignore
41+
RePattern = TypeVar('RePattern', bound=re.Pattern[Any]) # type: ignore

proxy/core/base/tcp_server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,8 @@ def _optionally_wrap_socket(self, conn: socket.socket) -> TcpOrTlsSocket:
240240
conn = wrap_socket(conn, self.flags.keyfile, self.flags.certfile)
241241
self.work._conn = conn
242242
return conn
243+
244+
@staticmethod
245+
@abstractmethod
246+
def create(*args: Any) -> T:
247+
raise NotImplementedError()

proxy/core/work/fd/fd.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
:license: BSD, see LICENSE for more details.
1010
"""
1111
import socket
12+
import asyncio
1213
import logging
14+
from abc import abstractmethod
1315
from typing import Any, TypeVar, Optional
1416

1517
from ...event import eventNames
@@ -47,3 +49,16 @@ def work(self, *args: Any) -> None:
4749
exc_info=e,
4850
)
4951
self._cleanup(fileno)
52+
53+
@property
54+
@abstractmethod
55+
def loop(self) -> Optional[asyncio.AbstractEventLoop]:
56+
raise NotImplementedError()
57+
58+
@abstractmethod
59+
def receive_from_work_queue(self) -> bool:
60+
raise NotImplementedError()
61+
62+
@abstractmethod
63+
def work_queue_fileno(self) -> Optional[int]:
64+
raise NotImplementedError()

proxy/core/work/local.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import queue
1212
import asyncio
1313
import contextlib
14+
from abc import abstractmethod
1415
from typing import Any, Optional
1516

1617
from .threadless import Threadless
@@ -40,3 +41,7 @@ def receive_from_work_queue(self) -> bool:
4041
return True
4142
self.work(work)
4243
return False
44+
45+
@abstractmethod
46+
def work(self, *args: Any) -> None:
47+
raise NotImplementedError()

proxy/core/work/remote.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
:license: BSD, see LICENSE for more details.
1010
"""
1111
import asyncio
12+
from abc import abstractmethod
1213
from typing import Any, Optional
1314
from multiprocessing import connection
1415

@@ -37,3 +38,7 @@ def close_work_queue(self) -> None:
3738
def receive_from_work_queue(self) -> bool:
3839
self.work(self.work_queue.recv())
3940
return False
41+
42+
@abstractmethod
43+
def work(self, *args: Any) -> None:
44+
raise NotImplementedError()

proxy/http/server/plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def before_routing(self, request: HttpParser) -> Optional[HttpParser]:
169169

170170
def handle_route(self, request: HttpParser, pattern: RePattern) -> Url:
171171
"""Implement this method if you have configured dynamic routes."""
172-
pass
172+
raise NotImplementedError()
173173

174174
def regexes(self) -> List[str]:
175175
"""Helper method to return list of route regular expressions."""

proxy/plugin/proxy_pool.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ def handle_upstream_chunk(self, chunk: memoryview) -> Optional[memoryview]:
186186
"""Will never be called since we didn't establish an upstream connection."""
187187
if not self.upstream:
188188
return chunk
189+
# pylint: disable=broad-exception-raised
189190
raise Exception("This should have never been called")
190191

191192
def on_upstream_connection_close(self) -> None:

proxy/testing/test_case.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def setUpClass(cls) -> None:
4242
cls.PROXY.flags.plugins[b'HttpProxyBasePlugin'].append(
4343
CacheResponsesPlugin,
4444
)
45+
# pylint: disable=unnecessary-dunder-call
4546
cls.PROXY.__enter__()
4647
assert cls.PROXY.acceptors
4748
cls.wait_for_server(cls.PROXY.flags.port)

requirements-testing.txt

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11
wheel==0.37.1
22
python-coveralls==2.9.3
3-
coverage==6.2
3+
coverage==6.2; python_version < '3.11'
4+
coverage==7.4.4; python_version >= '3.11'
45
flake8==4.0.1
5-
pytest==7.0.1
6-
pytest-cov==3.0.0
7-
pytest-xdist == 2.5.0
8-
pytest-mock==3.6.1
9-
pytest-asyncio==0.16.0
6+
# pytest for Python<3.11
7+
pytest==7.0.1; python_version < '3.11'
8+
pytest-cov==3.0.0; python_version < '3.11'
9+
pytest-xdist==2.5.0; python_version < '3.11'
10+
pytest-mock==3.6.1; python_version < '3.11'
11+
pytest-asyncio==0.16.0; python_version < '3.11'
12+
# pytest for Python>=3.11
13+
pytest==8.1.1; python_version >= '3.11'
14+
pytest-cov==5.0.0; python_version >= '3.11'
15+
pytest-xdist==3.5.0; python_version >= '3.11'
16+
pytest-mock==3.14.0; python_version >= '3.11'
17+
pytest-asyncio==0.21.1; python_version >= '3.11'
1018
autopep8==1.6.0
1119
mypy==0.971
1220
py-spy==0.3.12
13-
tox==3.28.0
21+
tox==3.28.0; python_version < '3.11'
22+
tox==4.14.2; python_version >= '3.11'
1423
mccabe==0.6.1
15-
pylint==2.13.7
24+
pylint==2.13.7; python_version < '3.11'
25+
pylint==3.1.0; python_version >= '3.11'
1626
rope==1.1.1
1727
# Required by test_http2.py
18-
httpx==0.22.0
28+
httpx==0.22.0; python_version < '3.11'
29+
httpx==0.27.0; python_version >= '3.11'
1930
h2==4.1.0
2031
hpack==4.0.0
2132
hyperframe==6.0.1

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ classifiers =
6565
Programming Language :: Python :: 3.8
6666
Programming Language :: Python :: 3.9
6767
Programming Language :: Python :: 3.10
68+
Programming Language :: Python :: 3.11
6869

6970
Topic :: Internet
7071
Topic :: Internet :: Proxy Servers

tests/http/proxy/test_http2.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
:copyright: (c) 2013-present by Abhinav Singh and contributors.
99
:license: BSD, see LICENSE for more details.
1010
"""
11+
import sys
12+
from typing import Any, Dict
13+
1114
import httpx
1215

1316
from proxy import TestCase
@@ -17,14 +20,23 @@ class TestHttp2WithProxy(TestCase):
1720

1821
def test_http2_via_proxy(self) -> None:
1922
assert self.PROXY
23+
proxy_url = 'http://localhost:%d' % self.PROXY.flags.port
24+
proxies: Dict[str, Any] = (
25+
{
26+
'proxies': {
27+
'all://': proxy_url,
28+
},
29+
}
30+
# For Python>=3.11, proxies keyword is deprecated by httpx
31+
if sys.version_info < (3, 11, 0)
32+
else {'proxy': proxy_url}
33+
)
2034
response = httpx.get(
2135
'https://www.google.com',
2236
headers={'accept': 'application/json'},
2337
verify=httpx.create_ssl_context(http2=True),
2438
timeout=httpx.Timeout(timeout=5.0),
25-
proxies={
26-
'all://': 'http://localhost:%d' % self.PROXY.flags.port,
27-
},
39+
**proxies,
2840
)
2941
self.assertEqual(response.status_code, 200)
3042

tox.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,6 @@ deps =
262262
pre-commit
263263
pylint >= 2.5.3
264264
pylint-pytest < 1.1.0
265-
pytest-mock == 3.6.1
266265
-r docs/requirements.in
267266
-r requirements-tunnel.txt
268267
-r requirements-testing.txt

0 commit comments

Comments
 (0)