web3 7.0.0b4__py3-none-any.whl → 7.0.0b6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. web3/_utils/batching.py +217 -0
  2. web3/_utils/caching.py +26 -2
  3. web3/_utils/compat/__init__.py +1 -0
  4. web3/_utils/contracts.py +5 -5
  5. web3/_utils/events.py +20 -20
  6. web3/_utils/filters.py +6 -6
  7. web3/_utils/method_formatters.py +0 -23
  8. web3/_utils/module_testing/__init__.py +0 -3
  9. web3/_utils/module_testing/eth_module.py +442 -373
  10. web3/_utils/module_testing/module_testing_utils.py +13 -0
  11. web3/_utils/module_testing/web3_module.py +438 -17
  12. web3/_utils/rpc_abi.py +0 -18
  13. web3/contract/async_contract.py +11 -11
  14. web3/contract/base_contract.py +19 -18
  15. web3/contract/contract.py +13 -13
  16. web3/contract/utils.py +112 -4
  17. web3/eth/async_eth.py +10 -8
  18. web3/eth/eth.py +7 -6
  19. web3/exceptions.py +75 -21
  20. web3/gas_strategies/time_based.py +2 -2
  21. web3/geth.py +0 -188
  22. web3/main.py +21 -13
  23. web3/manager.py +237 -74
  24. web3/method.py +29 -9
  25. web3/middleware/base.py +43 -0
  26. web3/middleware/filter.py +18 -6
  27. web3/middleware/signing.py +2 -2
  28. web3/module.py +47 -7
  29. web3/providers/async_base.py +55 -23
  30. web3/providers/base.py +59 -26
  31. web3/providers/eth_tester/defaults.py +0 -48
  32. web3/providers/eth_tester/main.py +36 -11
  33. web3/providers/eth_tester/middleware.py +3 -8
  34. web3/providers/ipc.py +23 -8
  35. web3/providers/legacy_websocket.py +26 -1
  36. web3/providers/persistent/async_ipc.py +60 -76
  37. web3/providers/persistent/persistent.py +134 -10
  38. web3/providers/persistent/request_processor.py +98 -14
  39. web3/providers/persistent/websocket.py +43 -66
  40. web3/providers/rpc/async_rpc.py +20 -2
  41. web3/providers/rpc/rpc.py +22 -2
  42. web3/providers/rpc/utils.py +1 -10
  43. web3/tools/benchmark/node.py +2 -8
  44. web3/types.py +8 -2
  45. {web3-7.0.0b4.dist-info → web3-7.0.0b6.dist-info}/LICENSE +1 -1
  46. {web3-7.0.0b4.dist-info → web3-7.0.0b6.dist-info}/METADATA +32 -21
  47. {web3-7.0.0b4.dist-info → web3-7.0.0b6.dist-info}/RECORD +49 -49
  48. web3/_utils/module_testing/go_ethereum_personal_module.py +0 -300
  49. {web3-7.0.0b4.dist-info → web3-7.0.0b6.dist-info}/WHEEL +0 -0
  50. {web3-7.0.0b4.dist-info → web3-7.0.0b6.dist-info}/top_level.txt +0 -0
@@ -5,8 +5,11 @@ import os
5
5
  from typing import (
6
6
  Any,
7
7
  Dict,
8
+ List,
8
9
  Optional,
10
+ Tuple,
9
11
  Union,
12
+ cast,
10
13
  )
11
14
 
12
15
  from eth_typing import (
@@ -25,6 +28,10 @@ from websockets.exceptions import (
25
28
  WebSocketException,
26
29
  )
27
30
 
31
+ from web3._utils.batching import (
32
+ BATCH_REQUEST_ID,
33
+ sort_batch_response_by_response_ids,
34
+ )
28
35
  from web3._utils.caching import (
29
36
  async_handle_request_caching,
30
37
  )
@@ -61,7 +68,6 @@ class WebSocketProvider(PersistentConnectionProvider):
61
68
  logger = logging.getLogger("web3.providers.WebSocketProvider")
62
69
  is_async: bool = True
63
70
 
64
- _max_connection_retries: int = 5
65
71
  _ws: Optional[WebSocketClientProtocol] = None
66
72
 
67
73
  def __init__(
@@ -116,47 +122,13 @@ class WebSocketProvider(PersistentConnectionProvider):
116
122
  ) from e
117
123
  return False
118
124
 
119
- async def connect(self) -> None:
120
- _connection_attempts = 0
121
- _backoff_rate_change = 1.75
122
- _backoff_time = 1.75
123
-
124
- while _connection_attempts != self._max_connection_retries:
125
- try:
126
- _connection_attempts += 1
127
- self._ws = await connect(self.endpoint_uri, **self.websocket_kwargs)
128
- self._message_listener_task = asyncio.create_task(
129
- self._message_listener()
130
- )
131
- break
132
- except WebSocketException as e:
133
- if _connection_attempts == self._max_connection_retries:
134
- raise ProviderConnectionError(
135
- f"Could not connect to endpoint: {self.endpoint_uri}. "
136
- f"Retries exceeded max of {self._max_connection_retries}."
137
- ) from e
138
- self.logger.info(
139
- f"Could not connect to endpoint: {self.endpoint_uri}. Retrying in "
140
- f"{round(_backoff_time, 1)} seconds.",
141
- exc_info=True,
142
- )
143
- await asyncio.sleep(_backoff_time)
144
- _backoff_time *= _backoff_rate_change
125
+ async def _provider_specific_connect(self) -> None:
126
+ self._ws = await connect(self.endpoint_uri, **self.websocket_kwargs)
145
127
 
146
- async def disconnect(self) -> None:
128
+ async def _provider_specific_disconnect(self) -> None:
147
129
  if self._ws is not None and not self._ws.closed:
148
130
  await self._ws.close()
149
131
  self._ws = None
150
- self.logger.debug(
151
- f'Successfully disconnected from endpoint: "{self.endpoint_uri}'
152
- )
153
-
154
- try:
155
- self._message_listener_task.cancel()
156
- await self._message_listener_task
157
- except (asyncio.CancelledError, StopAsyncIteration):
158
- pass
159
- self._request_processor.clear_caches()
160
132
 
161
133
  @async_handle_request_caching
162
134
  async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
@@ -176,34 +148,39 @@ class WebSocketProvider(PersistentConnectionProvider):
176
148
 
177
149
  return response
178
150
 
179
- async def _message_listener(self) -> None:
180
- self.logger.info(
181
- "WebSocket listener background task started. Storing all messages in "
182
- "appropriate request processor queues / caches to be processed."
151
+ async def make_batch_request(
152
+ self, requests: List[Tuple[RPCEndpoint, Any]]
153
+ ) -> List[RPCResponse]:
154
+ request_data = self.encode_batch_rpc_request(requests)
155
+
156
+ if self._ws is None:
157
+ raise ProviderConnectionError(
158
+ "Connection to websocket has not been initiated for the provider."
159
+ )
160
+
161
+ await asyncio.wait_for(
162
+ self._ws.send(request_data), timeout=self.request_timeout
163
+ )
164
+
165
+ response = cast(
166
+ List[RPCResponse],
167
+ await self._get_response_for_request_id(BATCH_REQUEST_ID),
183
168
  )
184
- while True:
185
- # the use of sleep(0) seems to be the most efficient way to yield control
186
- # back to the event loop to share the loop with other tasks.
169
+ return response
170
+
171
+ async def _provider_specific_message_listener(self) -> None:
172
+ async for raw_message in self._ws:
187
173
  await asyncio.sleep(0)
188
174
 
189
- try:
190
- async for raw_message in self._ws:
191
- await asyncio.sleep(0)
192
-
193
- response = json.loads(raw_message)
194
- subscription = response.get("method") == "eth_subscription"
195
- await self._request_processor.cache_raw_response(
196
- response, subscription=subscription
197
- )
198
- except Exception as e:
199
- if not self.silence_listener_task_exceptions:
200
- loop = asyncio.get_event_loop()
201
- for task in asyncio.all_tasks(loop=loop):
202
- task.cancel()
203
- raise e
204
-
205
- self.logger.error(
206
- "Exception caught in listener, error logging and keeping "
207
- "listener background task alive."
208
- f"\n error={e.__class__.__name__}: {e}"
209
- )
175
+ response = json.loads(raw_message)
176
+ if isinstance(response, list):
177
+ response = sort_batch_response_by_response_ids(response)
178
+
179
+ subscription = (
180
+ response.get("method") == "eth_subscription"
181
+ if not isinstance(response, list)
182
+ else False
183
+ )
184
+ await self._request_processor.cache_raw_response(
185
+ response, subscription=subscription
186
+ )
@@ -4,9 +4,11 @@ from typing import (
4
4
  Any,
5
5
  Dict,
6
6
  Iterable,
7
+ List,
7
8
  Optional,
8
9
  Tuple,
9
10
  Union,
11
+ cast,
10
12
  )
11
13
 
12
14
  from aiohttp import (
@@ -37,6 +39,9 @@ from web3.types import (
37
39
  RPCResponse,
38
40
  )
39
41
 
42
+ from ..._utils.batching import (
43
+ sort_batch_response_by_response_ids,
44
+ )
40
45
  from ..._utils.caching import (
41
46
  async_handle_request_caching,
42
47
  )
@@ -61,6 +66,7 @@ class AsyncHTTPProvider(AsyncJSONBaseProvider):
61
66
  exception_retry_configuration: Union[
62
67
  ExceptionRetryConfiguration, Empty
63
68
  ] = empty,
69
+ **kwargs: Any,
64
70
  ) -> None:
65
71
  if endpoint_uri is None:
66
72
  self.endpoint_uri = get_default_http_endpoint()
@@ -70,7 +76,7 @@ class AsyncHTTPProvider(AsyncJSONBaseProvider):
70
76
  self._request_kwargs = request_kwargs or {}
71
77
  self._exception_retry_configuration = exception_retry_configuration
72
78
 
73
- super().__init__()
79
+ super().__init__(**kwargs)
74
80
 
75
81
  async def cache_async_session(self, session: ClientSession) -> ClientSession:
76
82
  return await _async_cache_and_return_session(self.endpoint_uri, session)
@@ -123,7 +129,7 @@ class AsyncHTTPProvider(AsyncJSONBaseProvider):
123
129
  except tuple(self.exception_retry_configuration.errors):
124
130
  if i < self.exception_retry_configuration.retries - 1:
125
131
  await asyncio.sleep(
126
- self.exception_retry_configuration.backoff_factor
132
+ self.exception_retry_configuration.backoff_factor * 2**i
127
133
  )
128
134
  continue
129
135
  else:
@@ -147,3 +153,15 @@ class AsyncHTTPProvider(AsyncJSONBaseProvider):
147
153
  f"Method: {method}, Response: {response}"
148
154
  )
149
155
  return response
156
+
157
+ async def make_batch_request(
158
+ self, batch_requests: List[Tuple[RPCEndpoint, Any]]
159
+ ) -> List[RPCResponse]:
160
+ self.logger.debug(f"Making batch request HTTP - uri: `{self.endpoint_uri}`")
161
+ request_data = self.encode_batch_rpc_request(batch_requests)
162
+ raw_response = await async_make_post_request(
163
+ self.endpoint_uri, request_data, **self.get_request_kwargs()
164
+ )
165
+ self.logger.debug("Received batch response HTTP.")
166
+ responses_list = cast(List[RPCResponse], self.decode_rpc_response(raw_response))
167
+ return sort_batch_response_by_response_ids(responses_list)
web3/providers/rpc/rpc.py CHANGED
@@ -5,9 +5,11 @@ from typing import (
5
5
  Any,
6
6
  Dict,
7
7
  Iterable,
8
+ List,
8
9
  Optional,
9
10
  Tuple,
10
11
  Union,
12
+ cast,
11
13
  )
12
14
 
13
15
  from eth_typing import (
@@ -35,6 +37,9 @@ from web3.types import (
35
37
  RPCResponse,
36
38
  )
37
39
 
40
+ from ..._utils.batching import (
41
+ sort_batch_response_by_response_ids,
42
+ )
38
43
  from ..._utils.caching import (
39
44
  handle_request_caching,
40
45
  )
@@ -65,6 +70,7 @@ class HTTPProvider(JSONBaseProvider):
65
70
  exception_retry_configuration: Union[
66
71
  ExceptionRetryConfiguration, Empty
67
72
  ] = empty,
73
+ **kwargs: Any,
68
74
  ) -> None:
69
75
  if endpoint_uri is None:
70
76
  self.endpoint_uri = get_default_http_endpoint()
@@ -77,7 +83,7 @@ class HTTPProvider(JSONBaseProvider):
77
83
  if session:
78
84
  cache_and_return_session(self.endpoint_uri, session)
79
85
 
80
- super().__init__()
86
+ super().__init__(**kwargs)
81
87
 
82
88
  def __str__(self) -> str:
83
89
  return f"RPC connection {self.endpoint_uri}"
@@ -130,7 +136,9 @@ class HTTPProvider(JSONBaseProvider):
130
136
  )
131
137
  except tuple(self.exception_retry_configuration.errors) as e:
132
138
  if i < self.exception_retry_configuration.retries - 1:
133
- time.sleep(self.exception_retry_configuration.backoff_factor)
139
+ time.sleep(
140
+ self.exception_retry_configuration.backoff_factor * 2**i
141
+ )
134
142
  continue
135
143
  else:
136
144
  raise e
@@ -153,3 +161,15 @@ class HTTPProvider(JSONBaseProvider):
153
161
  f"Method: {method}, Response: {response}"
154
162
  )
155
163
  return response
164
+
165
+ def make_batch_request(
166
+ self, batch_requests: List[Tuple[RPCEndpoint, Any]]
167
+ ) -> List[RPCResponse]:
168
+ self.logger.debug(f"Making batch request HTTP, uri: `{self.endpoint_uri}`")
169
+ request_data = self.encode_batch_rpc_request(batch_requests)
170
+ raw_response = make_post_request(
171
+ self.endpoint_uri, request_data, **self.get_request_kwargs()
172
+ )
173
+ self.logger.debug("Received batch response HTTP.")
174
+ responses_list = cast(List[RPCResponse], self.decode_rpc_response(raw_response))
175
+ return sort_batch_response_by_response_ids(responses_list)
@@ -58,15 +58,6 @@ REQUEST_RETRY_ALLOWLIST = [
58
58
  "eth_sign",
59
59
  "eth_signTypedData",
60
60
  "eth_sendRawTransaction",
61
- "personal_importRawKey",
62
- "personal_newAccount",
63
- "personal_listAccounts",
64
- "personal_listWallets",
65
- "personal_lockAccount",
66
- "personal_unlockAccount",
67
- "personal_ecRecover",
68
- "personal_sign",
69
- "personal_signTypedData",
70
61
  ]
71
62
 
72
63
 
@@ -93,7 +84,7 @@ class ExceptionRetryConfiguration(BaseModel):
93
84
  self,
94
85
  errors: Sequence[Type[BaseException]] = None,
95
86
  retries: int = 5,
96
- backoff_factor: float = 0.5,
87
+ backoff_factor: float = 0.125,
97
88
  method_allowlist: Sequence[str] = None,
98
89
  ):
99
90
  super().__init__(
@@ -24,10 +24,7 @@ from web3.tools.benchmark.utils import (
24
24
  kill_proc_gracefully,
25
25
  )
26
26
 
27
- GETH_FIXTURE_ZIP = "geth-1.13.11-fixture.zip"
28
-
29
- # use same coinbase value as in `web3.py/tests/integration/generate_fixtures/common.py`
30
- COINBASE = "0xdc544d1aa88ff8bbd2f2aec754b1f1e99e1812fd"
27
+ GETH_FIXTURE_ZIP = "geth-1.13.14-fixture.zip"
31
28
 
32
29
 
33
30
  class GethBenchmarkFixture:
@@ -84,7 +81,7 @@ class GethBenchmarkFixture:
84
81
  "--dev.period",
85
82
  "100",
86
83
  "--datadir",
87
- str(datadir),
84
+ datadir,
88
85
  "--nodiscover",
89
86
  "--http",
90
87
  "--http.port",
@@ -92,9 +89,6 @@ class GethBenchmarkFixture:
92
89
  "--http.api",
93
90
  "admin,eth,net,web3",
94
91
  "--ipcdisable",
95
- "--allow-insecure-unlock",
96
- "--miner.etherbase",
97
- COINBASE[2:],
98
92
  "--password",
99
93
  os.path.join(datadir, "keystore", "pw.txt"),
100
94
  )
web3/types.py CHANGED
@@ -9,6 +9,7 @@ from typing import (
9
9
  NewType,
10
10
  Optional,
11
11
  Sequence,
12
+ Tuple,
12
13
  Type,
13
14
  TypedDict,
14
15
  TypeVar,
@@ -43,8 +44,9 @@ if TYPE_CHECKING:
43
44
  )
44
45
 
45
46
 
46
- TReturn = TypeVar("TReturn")
47
+ TFunc = TypeVar("TFunc", bound=Callable[..., Any])
47
48
  TParams = TypeVar("TParams")
49
+ TReturn = TypeVar("TReturn")
48
50
  TValue = TypeVar("TValue")
49
51
 
50
52
  BlockParams = Literal["latest", "earliest", "pending", "safe", "finalized"]
@@ -295,7 +297,7 @@ RPCId = Optional[Union[int, str]]
295
297
 
296
298
 
297
299
  class RPCResponse(TypedDict, total=False):
298
- error: Union[RPCError, str]
300
+ error: RPCError
299
301
  id: RPCId
300
302
  jsonrpc: Literal["2.0"]
301
303
  result: Any
@@ -318,7 +320,11 @@ class CreateAccessListResponse(TypedDict):
318
320
 
319
321
 
320
322
  MakeRequestFn = Callable[[RPCEndpoint, Any], RPCResponse]
323
+ MakeBatchRequestFn = Callable[[List[Tuple[RPCEndpoint, Any]]], List[RPCResponse]]
321
324
  AsyncMakeRequestFn = Callable[[RPCEndpoint, Any], Coroutine[Any, Any, RPCResponse]]
325
+ AsyncMakeBatchRequestFn = Callable[
326
+ [List[Tuple[RPCEndpoint, Any]]], Coroutine[Any, Any, List[RPCResponse]]
327
+ ]
322
328
 
323
329
 
324
330
  class FormattersDict(TypedDict, total=False):
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016 The Ethereum Foundation
3
+ Copyright (c) 2016-2024 The Ethereum Foundation
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: web3
3
- Version: 7.0.0b4
4
- Summary: web3.py
3
+ Version: 7.0.0b6
4
+ Summary: web3: A Python library for interacting with Ethereum
5
5
  Home-page: https://github.com/ethereum/web3.py
6
6
  Author: The Ethereum Foundation
7
7
  Author-email: snakecharmers@ethereum.org
@@ -16,12 +16,13 @@ Classifier: Programming Language :: Python :: 3.8
16
16
  Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
- Requires-Python: >=3.8
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.8, <4
20
21
  Description-Content-Type: text/markdown
21
22
  License-File: LICENSE
22
23
  Requires-Dist: aiohttp >=3.7.4.post0
23
24
  Requires-Dist: eth-abi >=5.0.1
24
- Requires-Dist: eth-account >=0.12.0
25
+ Requires-Dist: eth-account >=0.12.2
25
26
  Requires-Dist: eth-hash[pycryptodome] >=0.5.1
26
27
  Requires-Dist: eth-typing >=4.0.0
27
28
  Requires-Dist: eth-utils >=4.0.0
@@ -33,38 +34,48 @@ Requires-Dist: websockets >=10.0.0
33
34
  Requires-Dist: pyunormalize >=15.0.0
34
35
  Requires-Dist: pywin32 >=223 ; platform_system == "Windows"
35
36
  Provides-Extra: dev
36
- Requires-Dist: eth-tester[py-evm] <0.12.0b1,>=0.11.0b1 ; extra == 'dev'
37
- Requires-Dist: py-geth >=4.1.0 ; extra == 'dev'
38
- Requires-Dist: sphinx >=5.3.0 ; extra == 'dev'
39
- Requires-Dist: sphinx-rtd-theme >=1.0.0 ; extra == 'dev'
40
- Requires-Dist: towncrier <22,>=21 ; extra == 'dev'
41
37
  Requires-Dist: build >=0.9.0 ; extra == 'dev'
42
- Requires-Dist: bumpversion ; extra == 'dev'
38
+ Requires-Dist: bumpversion >=0.5.3 ; extra == 'dev'
43
39
  Requires-Dist: flaky >=3.7.0 ; extra == 'dev'
44
40
  Requires-Dist: hypothesis >=3.31.2 ; extra == 'dev'
41
+ Requires-Dist: ipython ; extra == 'dev'
45
42
  Requires-Dist: pre-commit >=3.4.0 ; extra == 'dev'
46
- Requires-Dist: pytest-asyncio <0.23,>=0.18.1 ; extra == 'dev'
43
+ Requires-Dist: pytest-asyncio <0.23,>=0.21.2 ; extra == 'dev'
47
44
  Requires-Dist: pytest-mock >=1.10 ; extra == 'dev'
48
- Requires-Dist: pytest-watch >=4.2 ; extra == 'dev'
49
- Requires-Dist: pytest-xdist >=1.29 ; extra == 'dev'
50
- Requires-Dist: pytest >=7.0.0 ; extra == 'dev'
51
45
  Requires-Dist: setuptools >=38.6.0 ; extra == 'dev'
52
- Requires-Dist: tox >=3.18.0 ; extra == 'dev'
46
+ Requires-Dist: tox >=4.0.0 ; extra == 'dev'
53
47
  Requires-Dist: tqdm >4.32 ; extra == 'dev'
54
48
  Requires-Dist: twine >=1.13 ; extra == 'dev'
49
+ Requires-Dist: wheel ; extra == 'dev'
50
+ Requires-Dist: sphinx >=6.0.0 ; extra == 'dev'
51
+ Requires-Dist: sphinx-autobuild >=2021.3.14 ; extra == 'dev'
52
+ Requires-Dist: sphinx-rtd-theme >=1.0.0 ; extra == 'dev'
53
+ Requires-Dist: towncrier <22,>=21 ; extra == 'dev'
54
+ Requires-Dist: eth-tester[py-evm] <0.12.0b1,>=0.11.0b1 ; extra == 'dev'
55
+ Requires-Dist: py-geth >=4.1.0 ; extra == 'dev'
56
+ Requires-Dist: pytest-asyncio <0.23,>=0.18.1 ; extra == 'dev'
57
+ Requires-Dist: pytest-xdist >=2.4.0 ; extra == 'dev'
58
+ Requires-Dist: pytest >=7.0.0 ; extra == 'dev'
55
59
  Provides-Extra: docs
56
- Requires-Dist: sphinx >=5.3.0 ; extra == 'docs'
60
+ Requires-Dist: sphinx >=6.0.0 ; extra == 'docs'
61
+ Requires-Dist: sphinx-autobuild >=2021.3.14 ; extra == 'docs'
57
62
  Requires-Dist: sphinx-rtd-theme >=1.0.0 ; extra == 'docs'
58
63
  Requires-Dist: towncrier <22,>=21 ; extra == 'docs'
59
- Provides-Extra: tester
60
- Requires-Dist: eth-tester[py-evm] <0.12.0b1,>=0.11.0b1 ; extra == 'tester'
61
- Requires-Dist: py-geth >=4.1.0 ; extra == 'tester'
64
+ Provides-Extra: test
65
+ Requires-Dist: eth-tester[py-evm] <0.12.0b1,>=0.11.0b1 ; extra == 'test'
66
+ Requires-Dist: py-geth >=4.1.0 ; extra == 'test'
67
+ Requires-Dist: pytest-asyncio <0.23,>=0.18.1 ; extra == 'test'
68
+ Requires-Dist: pytest-mock >=1.10 ; extra == 'test'
69
+ Requires-Dist: pytest-xdist >=2.4.0 ; extra == 'test'
70
+ Requires-Dist: pytest >=7.0.0 ; extra == 'test'
62
71
 
63
72
  # web3.py
64
73
 
65
- [![Documentation Status](https://readthedocs.org/projects/web3py/badge/?version=latest)](https://web3py.readthedocs.io/en/latest/?badge=latest)
66
- [![Discord](https://img.shields.io/discord/809793915578089484?color=blue&label=chat&logo=discord&logoColor=white)](https://discord.gg/GHryRvPB84)
74
+ [![Join the conversation on Discord](https://img.shields.io/discord/809793915578089484?color=blue&label=chat&logo=discord&logoColor=white)](https://discord.gg/GHryRvPB84)
67
75
  [![Build Status](https://circleci.com/gh/ethereum/web3.py.svg?style=shield)](https://circleci.com/gh/ethereum/web3.py)
76
+ [![PyPI version](https://badge.fury.io/py/web3.svg)](https://badge.fury.io/py/web3)
77
+ [![Python versions](https://img.shields.io/pypi/pyversions/web3.svg)](https://pypi.python.org/pypi/web3)
78
+ [![Docs build](https://readthedocs.org/projects/web3py/badge/?version=latest)](https://web3py.readthedocs.io/en/latest/?badge=latest)
68
79
 
69
80
  A Python library for interacting with Ethereum.
70
81