Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ workflows:
- common:
matrix:
parameters:
python_minor_version: ["10", "11", "12", "13"]
python_minor_version: ["10", "11", "12", "13", "14"]
tox_env: [
"lint",
"core",
Expand All @@ -234,7 +234,7 @@ workflows:
- geth:
matrix:
parameters:
python_minor_version: ["10", "11", "12", "13"]
python_minor_version: ["10", "11", "12", "13", "14"]
tox_env: [
"integration-goethereum-ipc",
"integration-goethereum-ipc_async",
Expand All @@ -252,7 +252,7 @@ workflows:
- windows-wheel:
matrix:
parameters:
python_minor_version: ["10", "11", "12", "13"]
python_minor_version: ["10", "11", "12", "13", "14"]
name: "py3<< matrix.python_minor_version >>-windows-wheel"


Expand Down
1 change: 1 addition & 0 deletions newsfragments/3779.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Upgrade websockets requirement to >=14.0.
1 change: 1 addition & 0 deletions newsfragments/3779.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for Python 3.14
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"towncrier>=24,<25",
],
"test": [
"pytest-asyncio>=0.18.1,<0.23",
"pytest-asyncio>=0.18.1",
"pytest-mock>=1.10",
"pytest-xdist>=2.4.0",
"pytest>=7.0.0",
Expand Down Expand Up @@ -78,7 +78,7 @@
"requests>=2.23.0",
"typing-extensions>=4.0.1",
"types-requests>=2.0.0",
"websockets>=10.0.0,<16.0.0",
"websockets>=14.0.0",
"pyunormalize>=15.0.0",
],
python_requires=">=3.10, <4",
Expand All @@ -99,5 +99,6 @@
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
],
)
3 changes: 2 additions & 1 deletion tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import inspect
import socket

from web3._utils.threads import (
Expand Down Expand Up @@ -65,7 +66,7 @@ async def _async_wait_for_transaction_fixture_logic(async_w3, txn_hash, timeout=
def async_partial(f, *args, **kwargs):
async def f2(*args2, **kwargs2):
result = f(*args, *args2, **kwargs, **kwargs2)
if asyncio.iscoroutinefunction(f):
if inspect.iscoroutinefunction(f):
result = await result
return result

Expand Down
9 changes: 5 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tox]
envlist=
py{310,311,312,313}-{ens,core,lint,wheel}
py{310,311,312,313}-integration-{goethereum,ethtester}
py{310,311,312,313,314}-{ens,core,lint,wheel}
py{310,311,312,313,314}-integration-{goethereum,ethtester}
docs
benchmark
windows-wheel
Expand Down Expand Up @@ -48,8 +48,9 @@ basepython =
py311: python3.11
py312: python3.12
py313: python3.13
py314: python3.14

[testenv:py{310,311,312,313}-lint]
[testenv:py{310,311,312,313,314}-lint]
deps=pre-commit
extras=dev
commands=
Expand All @@ -64,7 +65,7 @@ commands=
python {toxinidir}/web3/tools/benchmark/main.py --num-calls 100


[testenv:py{310,311,312,313}-wheel]
[testenv:py{310,311,312,313,314}-wheel]
deps=
wheel
build[virtualenv]
Expand Down
6 changes: 2 additions & 4 deletions web3/_utils/caching/caching_utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from asyncio import (
iscoroutinefunction,
)
import collections
import hashlib
import inspect
import threading
from typing import (
TYPE_CHECKING,
Expand Down Expand Up @@ -330,7 +328,7 @@ async def _async_should_cache_response(
cache_validator = ASYNC_INTERNAL_VALIDATION_MAP[method]
return (
await cache_validator(provider, params, result)
if iscoroutinefunction(cache_validator)
if inspect.iscoroutinefunction(cache_validator)
else cache_validator(provider, params, result)
)
return True
Expand Down
44 changes: 28 additions & 16 deletions web3/_utils/module_testing/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
from asyncio import (
iscoroutinefunction,
)
import copy
import inspect
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -113,12 +110,11 @@ def __init__(
"AsyncMakeRequestFn", "MakeRequestFn"
] = w3.provider.make_request

self._mock_request_counter = 1

def _build_request_id(self) -> int:
request_id = (
next(copy.deepcopy(self.w3.provider.request_counter))
if hasattr(self.w3.provider, "request_counter")
else 1
)
request_id = self._mock_request_counter
self._mock_request_counter += 1
return request_id

def __enter__(self) -> "Self":
Expand All @@ -144,7 +140,11 @@ def _mock_request_handler(

if all(
method not in mock_dict
for mock_dict in (self.mock_errors, self.mock_results, self.mock_responses)
for mock_dict in (
self.mock_errors,
self.mock_results,
self.mock_responses,
)
):
return self._make_request(method, params)

Expand Down Expand Up @@ -224,7 +224,7 @@ async def _async_build_mock_result(

if callable(mock_return):
mock_return = mock_return(method, params)
elif iscoroutinefunction(mock_return):
elif inspect.iscoroutinefunction(mock_return):
# this is the "correct" way to mock the async make_request
mock_return = await mock_return(method, params)

Expand All @@ -239,7 +239,7 @@ async def _async_build_mock_result(
if callable(mock_return):
# handle callable to make things easier since we're mocking
mock_return = mock_return(method, params)
elif iscoroutinefunction(mock_return):
elif inspect.iscoroutinefunction(mock_return):
# this is the "correct" way to mock the async make_request
mock_return = await mock_return(method, params)

Expand All @@ -249,7 +249,7 @@ async def _async_build_mock_result(
error = self.mock_errors[method]
if callable(error):
error = error(method, params)
elif iscoroutinefunction(error):
elif inspect.iscoroutinefunction(error):
error = await error(method, params)
mocked_result = merge(response_dict, self._create_error_object(error))

Expand All @@ -265,7 +265,11 @@ async def _async_mock_request_handler(
self._make_request = cast("AsyncMakeRequestFn", self._make_request)
if all(
method not in mock_dict
for mock_dict in (self.mock_errors, self.mock_results, self.mock_responses)
for mock_dict in (
self.mock_errors,
self.mock_results,
self.mock_responses,
)
):
return await self._make_request(method, params)
mocked_result = await self._async_build_mock_result(method, params)
Expand All @@ -289,7 +293,11 @@ async def _async_mock_send_handler(
) -> "RPCRequest":
if all(
method not in mock_dict
for mock_dict in (self.mock_errors, self.mock_results, self.mock_responses)
for mock_dict in (
self.mock_errors,
self.mock_results,
self.mock_responses,
)
):
return await self._send_request(method, params)
else:
Expand All @@ -304,7 +312,11 @@ async def _async_mock_recv_handler(
request_id = rpc_request["id"]
if all(
method not in mock_dict
for mock_dict in (self.mock_errors, self.mock_results, self.mock_responses)
for mock_dict in (
self.mock_errors,
self.mock_results,
self.mock_responses,
)
):
return await self._recv_for_request(request_id)
mocked_result = await self._async_build_mock_result(
Expand Down
6 changes: 3 additions & 3 deletions web3/providers/async_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
)

if TYPE_CHECKING:
from websockets.legacy.client import (
WebSocketClientProtocol,
from websockets.asyncio.client import (
ClientConnection,
)

from web3 import ( # noqa: F401
Expand Down Expand Up @@ -169,7 +169,7 @@ async def disconnect(self) -> None:
)

# WebSocket typing
_ws: "WebSocketClientProtocol"
_ws: "ClientConnection"

# IPC typing
_reader: asyncio.StreamReader | None
Expand Down
13 changes: 8 additions & 5 deletions web3/providers/persistent/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
from toolz import (
merge,
)
from websockets.asyncio.client import (
ClientConnection,
connect,
)
from websockets.exceptions import (
ConnectionClosedOK,
WebSocketException,
)
from websockets.legacy.client import (
WebSocketClientProtocol,
connect,
from websockets.protocol import (
State,
)

from web3.exceptions import (
Expand Down Expand Up @@ -69,7 +72,7 @@ def __init__(
)
super().__init__(**kwargs)
self.use_text_frames = use_text_frames
self._ws: WebSocketClientProtocol | None = None
self._ws: ClientConnection | None = None

if not any(
self.endpoint_uri.startswith(prefix)
Expand Down Expand Up @@ -133,7 +136,7 @@ async def _provider_specific_connect(self) -> None:

async def _provider_specific_disconnect(self) -> None:
# this should remain idempotent
if self._ws is not None and not self._ws.closed:
if self._ws is not None and self._ws.state == State.OPEN:
await self._ws.close()
self._ws = None

Expand Down