Skip to content

Commit

Permalink
➕ Add Python 3.10 support (ethereum#2175)
Browse files Browse the repository at this point in the history
* ➕ Add Python 3.10 support to CI

* Dropped support for all parities

* Change docker image to use 3.10

* Update pytest-asyncio plugin

* Mark async fixture as such, clean up pytest DeprecationWarnings

Signed-off-by: Harmouch101 <[email protected]>

Co-authored-by: Felipe Selmo <[email protected]>
Co-authored-by: kclowes <[email protected]>
Signed-off-by: Harmouch101 <[email protected]>
  • Loading branch information
3 people committed Feb 28, 2022
1 parent 26c6d8f commit 33ea2ab
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 47 deletions.
75 changes: 74 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -421,10 +421,75 @@ jobs:
environment:
TOXENV: py39-wheel-cli

#
# Python 3.10
#
py310-core:
<<: *common
docker:
- image: circleci/python:3.10
environment:
TOXENV: py310-core

py310-ens:
<<: *common
docker:
- image: circleci/python:3.10
environment:
TOXENV: py310-ens

py310-ethpm:
<<: *ethpm_steps
docker:
- image: circleci/python:3.10
environment:
TOXENV: py310-ethpm
# Please don't use this key for any shenanigans
WEB3_INFURA_PROJECT_ID: 7707850c2fb7465ebe6f150d67182e22

py310-integration-goethereum-ipc:
<<: *geth_steps
docker:
- image: circleci/python:3.10
environment:
TOXENV: py310-integration-goethereum-ipc
GETH_VERSION: v1.10.11

py310-integration-goethereum-http:
<<: *geth_steps
docker:
- image: circleci/python:3.10
environment:
TOXENV: py310-integration-goethereum-http
GETH_VERSION: v1.10.11

py310-integration-goethereum-ws:
<<: *geth_steps
docker:
- image: circleci/python:3.10
environment:
TOXENV: py310-integration-goethereum-ws
GETH_VERSION: v1.10.11

py310-integration-ethtester-pyevm:
<<: *common
docker:
- image: circleci/python:3.10
environment:
TOXENV: py310-integration-ethtester
ETHEREUM_TESTER_CHAIN_BACKEND: eth_tester.backends.PyEVMBackend

py310-wheel-cli:
<<: *common
docker:
- image: circleci/python:3.10
environment:
TOXENV: py310-wheel-cli

benchmark:
<<: *geth_steps
docker:
- image: circleci/python:3.9
- image: circleci/python:3.10
environment:
TOXENV: benchmark
GETH_VERSION: v1.10.13
Expand All @@ -437,6 +502,7 @@ workflows:
- py37-core
- py38-core
- py39-core
- py310-core
- lint
- docs
- benchmark
Expand All @@ -462,3 +528,10 @@ workflows:
- py39-integration-goethereum-ws
- py39-integration-ethtester-pyevm
- py39-wheel-cli
- py310-ens
- py310-ethpm
- py310-integration-goethereum-ipc
- py310-integration-goethereum-http
- py310-integration-goethereum-ws
- py310-integration-ethtester-pyevm
- py310-wheel-cli
1 change: 1 addition & 0 deletions newsfragments/2175.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for Python 3.10
1 change: 1 addition & 0 deletions newsfragments/2344.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds a depreaction warning for the optional ``loop`` parameter of ``web3.providers.websocket``.
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
addopts= -v --showlocals --durations 10
python_paths= .
xfail_strict=true
asyncio_mode=strict

[pytest-watch]
runner= pytest --failed-first --maxfail=1 --no-success-flaky-report
9 changes: 5 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"contextlib2>=0.5.4",
"py-geth>=3.6.0,<4",
"py-solc>=0.4.0",
"pytest>=4.4.0,<5.0.0",
"pytest>=6.2.5,<7",
"sphinx>=3.0,<4",
"sphinx_rtd_theme>=0.1.9",
"toposort>=1.4",
Expand All @@ -38,8 +38,8 @@
"bumpversion",
"flaky>=3.7.0,<4",
"hypothesis>=3.31.2,<6",
"pytest>=4.4.0,<5.0.0",
"pytest-asyncio>=0.10.0,<0.11",
"pytest>=6.2.5,<7",
"pytest-asyncio>=0.18.1,<0.19",
"pytest-mock>=1.10,<2",
"pytest-pythonpath>=0.3",
"pytest-watch>=4.2,<5",
Expand Down Expand Up @@ -92,7 +92,7 @@
"typing-extensions>=3.7.4.1,<5;python_version<'3.8'",
"websockets>=10.0.0,<11",
],
python_requires='>=3.7,<3.10',
python_requires='>=3.7,<3.11',
extras_require=extras_require,
py_modules=['web3', 'ens', 'ethpm'],
entry_points={"pytest11": ["pytest_ethereum = web3.tools.pytest_ethereum.plugins"]},
Expand All @@ -110,5 +110,6 @@
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
],
)
34 changes: 28 additions & 6 deletions tests/core/providers/test_websocket_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
ValidationError,
)
from web3.providers.websocket import (
PersistentWebSocket,
WebsocketProvider,
)

Expand All @@ -28,7 +29,7 @@
)


@pytest.yield_fixture
@pytest.fixture
def start_websocket_server(open_port):
event_loop = asyncio.new_event_loop()

Expand All @@ -39,7 +40,7 @@ async def empty_server(websocket, path):
await websocket.send(data)

asyncio.set_event_loop(event_loop)
server = websockets.serve(empty_server, '127.0.0.1', open_port)
server = websockets.serve(empty_server, "127.0.0.1", open_port)
event_loop.run_until_complete(server)
event_loop.run_forever()

Expand All @@ -51,11 +52,11 @@ async def empty_server(websocket, path):
event_loop.call_soon_threadsafe(event_loop.stop)


@pytest.fixture()
@pytest.fixture
def w3(open_port, start_websocket_server):
# need new event loop as the one used by server is already running
event_loop = asyncio.new_event_loop()
endpoint_uri = 'ws://127.0.0.1:{}'.format(open_port)
endpoint_uri = "ws://127.0.0.1:{}".format(open_port)
event_loop.run_until_complete(wait_for_ws(endpoint_uri))
provider = WebsocketProvider(endpoint_uri, websocket_timeout=0.01)
return Web3(provider)
Expand All @@ -67,7 +68,28 @@ def test_websocket_provider_timeout(w3):


def test_restricted_websocket_kwargs():
invalid_kwargs = {'uri': 'ws://127.0.0.1:8546'}
re_exc_message = r'.*found: {0}*'.format(set(invalid_kwargs.keys()))
invalid_kwargs = {"uri": "ws://127.0.0.1:8546"}
re_exc_message = r".*found: {0}*".format(set(invalid_kwargs.keys()))
with pytest.raises(ValidationError, match=re_exc_message):
WebsocketProvider(websocket_kwargs=invalid_kwargs)


def test_event_loop_argument_deprecated():
event_loop = asyncio.new_event_loop()
endpoint_uri = "ws://127.0.0.1:8546"
websocket_kwargs = {}
match = (
"The loop parameter is deprecated and was removed from websocket "
"provider as of web3 v5. Consider instantiating this class without passing this argument instead."
)
with pytest.warns(
expected_warning=DeprecationWarning,
match=match,
):
WebsocketProvider(endpoint_uri, websocket_timeout=0.01, loop=event_loop)
with pytest.warns(
expected_warning=DeprecationWarning,
match=match,
):
PersistentWebSocket(endpoint_uri, websocket_kwargs, loop=event_loop)
event_loop.close()
2 changes: 1 addition & 1 deletion tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def revert_contract_factory(web3):
return contract_factory


@pytest.yield_fixture(scope="module")
@pytest.fixture(scope="module")
def event_loop(request):
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/go_ethereum/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def unlockable_account_dual_type(unlockable_account, address_conversion_func):
return address_conversion_func(unlockable_account)


@pytest.yield_fixture
@pytest.fixture
def unlocked_account_dual_type(web3, unlockable_account_dual_type, unlockable_account_pw):
web3.geth.personal.unlock_account(unlockable_account_dual_type, unlockable_account_pw)
yield unlockable_account_dual_type
Expand Down
4 changes: 3 additions & 1 deletion tests/integration/go_ethereum/test_goethereum_http.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pytest

import pytest_asyncio

from tests.utils import (
get_open_port,
)
Expand Down Expand Up @@ -94,7 +96,7 @@ def web3(geth_process, endpoint_uri):
return _web3


@pytest.fixture(scope="module")
@pytest_asyncio.fixture(scope="module")
async def async_w3(geth_process, endpoint_uri):
await wait_for_aiohttp(endpoint_uri)
_web3 = Web3(
Expand Down
6 changes: 4 additions & 2 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import asyncio
import socket
import time
import warnings

import websockets


def get_open_port():
sock = socket.socket()
sock.bind(('127.0.0.1', 0))
sock.bind(("127.0.0.1", 0))
port = sock.getsockname()[1]
sock.close()
return str(port)


async def wait_for_ws(endpoint_uri, timeout=10):
start = time.time()
while time.time() < start + timeout:
stop = start + timeout
while time.time() < stop:
try:
async with websockets.connect(uri=endpoint_uri):
pass
Expand Down
17 changes: 12 additions & 5 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[tox]
envlist=
py{37,38,39}-ens
py{37,38,39}-ethpm
py{37,38,39}-core
py{37,38,39}-integration-{goethereum,ethtester}
py{37,38,39,310}-ens
py{37,38,39,310}-ethpm
py{37,38,39,310}-core
py{37,38,39,310}-integration-{goethereum,ethtester}
lint
docs
benchmark
py{37,38,39}-wheel-cli
py{37,38,39,310}-wheel-cli

[isort]
combine_as_imports=True
Expand Down Expand Up @@ -52,6 +52,7 @@ basepython =
py37: python3.7
py38: python3.8
py39: python3.9
py310: python3.10

[testenv:lint]
basepython=python
Expand Down Expand Up @@ -97,6 +98,12 @@ whitelist_externals={[common-wheel-cli]whitelist_externals}
commands={[common-wheel-cli]commands}
skip_install=true

[testenv:py310-wheel-cli]
deps={[common-wheel-cli]deps}
whitelist_externals={[common-wheel-cli]whitelist_externals}
commands={[common-wheel-cli]commands}
skip_install=true

[common-wheel-cli-windows]
deps=wheel
whitelist_externals=
Expand Down
19 changes: 19 additions & 0 deletions web3/_utils/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,22 @@ def wrapper(*args: Any, **kwargs: Any) -> Callable[..., Any]:
return to_wrap(*args, **kwargs)
return cast(TFunc, wrapper)
return decorator


class DeprecationMetaClass(type):
""" A custom class that intercepts the __call__ method to decide whether
or not to raize a warning against the loop argument.
"""
def __call__(cls, *args, **kwargs):
new_kwargs = {key: val for key, val in kwargs.items() if key != 'loop'}
if 'loop' in kwargs:
warnings.warn(
"The loop parameter is deprecated and was removed from "
"websocket provider as of web3 v5. Consider instantiating "
"this class without passing this argument instead.",
category=DeprecationWarning,
stacklevel=2,
)
obj = cls.__new__(cls, *args, **new_kwargs)
obj.__init__(*args, **new_kwargs)
return obj
2 changes: 1 addition & 1 deletion web3/_utils/module_testing/web3_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def test_solidityKeccak(
self, web3: "Web3", types: Sequence[TypeStr], values: Sequence[Any], expected: HexBytes
) -> None:
if isinstance(expected, type) and issubclass(expected, Exception):
with pytest.raises(expected):
with pytest.raises(expected): # type: ignore
web3.solidityKeccak(types, values)
return

Expand Down
Loading

0 comments on commit 33ea2ab

Please sign in to comment.