Repository: ethereum/web3.py

Issue:
web3 import errors in Python 3.12
* Version: 6.13.0
* Python: 3.12, inside a venv
* OS: linux (but is probably applicable to other platforms as well)
* `pip freeze` output:

```
aiohttp==3.9.1
aiosignal==1.3.1
attrs==23.2.0
bitarray==2.9.2
certifi==2023.11.17
charset-normalizer==3.3.2
cytoolz==0.12.2
eth-abi==4.2.1
eth-account==0.10.0
eth-hash==0.5.2
eth-keyfile==0.7.0
eth-keys==0.4.0
eth-rlp==1.0.0
eth-typing==3.5.2
eth-utils==2.3.1
frozenlist==1.4.1
hexbytes==0.3.1
idna==3.6
jsonschema==4.20.0
jsonschema-specifications==2023.12.1
lru-dict==1.2.0
multidict==6.0.4
parsimonious==0.9.0
protobuf==4.25.1
pycryptodome==3.19.1
pyunormalize==15.1.0
referencing==0.32.1
regex==2023.12.25
requests==2.31.0
rlp==4.0.0
rpds-py==0.16.2
toolz==0.12.0
typing_extensions==4.9.0
urllib3==2.1.0
web3==6.13.0
websockets==12.0
yarl==1.9.4
```

### What was wrong?

In certain situations, web3 will raise ImportErrors on python 3.12 if the `setuptools` package is not installed. _In particular, this happens inside a  fresh Python 3.12 venv._ The `setuptools` package automatically installs the `pkg_resources` package, which is used in web3 [here](https://github.com/ethereum/web3.py/blob/8f853f5841fd62187bce0c9f17be75627104ca43/web3/__init__.py#L25). This used to work fine in older Python versions. However, according to the [new changes in 3.12](https://docs.python.org/3/whatsnew/3.12.html):

> gh-95299: Do not pre-install setuptools in virtual environments created with venv. This means that distutils, setuptools, pkg_resources, and easy_install will no longer available by default; to access these run pip install setuptools in the activated virtual environment.

This means that the pkg_resources package is no longer accessible which causes this error.

Among other things, this scenario can occur inside tox tests for projects that have the `web3` package installed and are configured to test against 3.12. This causes such tests to immediately fail because of the ImportError. The workaround, installing setuptools after the venv created, causes unnecessarily long test times, adding about 3 minutes to the run time.

### How can it be fixed?

Given that web3's use of setuptools/pkg_resources is limited to just getting the version number, this should be trivial to fix. Why not open the file with built-in functions such as `open()` and parse it for the version number? I don't think that `web3` should continue to depend on setuptools.

Discussion:

Patch:
diff --git a/newsfragments/3185.bugfix.rst b/newsfragments/3185.bugfix.rst
new file mode 100644
index 00000000..c018bd03
--- /dev/null
+++ b/newsfragments/3185.bugfix.rst
@@ -0,0 +1,1 @@
+Change ``fee_history`` default behavior. If ``reward_percentiles`` arg not included, pass it to the provider as an empty list instead of ``None``.
diff --git a/newsfragments/3187.bugfix.rst b/newsfragments/3187.bugfix.rst
new file mode 100644
index 00000000..60aafc96
--- /dev/null
+++ b/newsfragments/3187.bugfix.rst
@@ -0,0 +1,1 @@
+Use ``importlib.metadata`` for version info if python>=3.8
diff --git a/newsfragments/3187.internal.rst b/newsfragments/3187.internal.rst
new file mode 100644
index 00000000..27954a3e
--- /dev/null
+++ b/newsfragments/3187.internal.rst
@@ -0,0 +1,1 @@
+Add basic import and version tests for the ``web3`` module
diff --git a/web3/__init__.py b/web3/__init__.py
index ada63485..aa2ba141 100644
--- a/web3/__init__.py
+++ b/web3/__init__.py
@@ -1,5 +1,15 @@
-from eth_account import Account  # noqa: E402,
-import pkg_resources
+from eth_account import Account  # noqa: E402
+import sys
+
+if sys.version_info.major == 3 and sys.version_info.minor < 8:
+    import pkg_resources
+
+    __version__ = pkg_resources.get_distribution("web3").version
+else:
+    from importlib.metadata import version
+
+    __version__ = version("web3")
+
 
 from web3.main import (
     AsyncWeb3,
@@ -22,7 +32,6 @@ from web3.providers.websocket import (  # noqa: E402
     WebsocketProviderV2,
 )
 
-__version__ = pkg_resources.get_distribution("web3").version
 
 __all__ = [
     "__version__",
diff --git a/web3/eth/async_eth.py b/web3/eth/async_eth.py
index d8a096bf..62b4838e 100644
--- a/web3/eth/async_eth.py
+++ b/web3/eth/async_eth.py
@@ -234,6 +234,7 @@ class AsyncEth(BaseEth):
         newest_block: Union[BlockParams, BlockNumber],
         reward_percentiles: Optional[List[float]] = None,
     ) -> FeeHistory:
+        reward_percentiles = reward_percentiles or []
         return await self._fee_history(block_count, newest_block, reward_percentiles)
 
     # eth_call
diff --git a/web3/eth/eth.py b/web3/eth/eth.py
index 0f27bfce..8d7bd47c 100644
--- a/web3/eth/eth.py
+++ b/web3/eth/eth.py
@@ -226,6 +226,7 @@ class Eth(BaseEth):
         newest_block: Union[BlockParams, BlockNumber],
         reward_percentiles: Optional[List[float]] = None,
     ) -> FeeHistory:
+        reward_percentiles = reward_percentiles or []
         return self._fee_history(block_count, newest_block, reward_percentiles)
 
     # eth_call


Test patch:
diff --git a/tests/core/eth-module/test_eth_fee_history.py b/tests/core/eth-module/test_eth_fee_history.py
new file mode 100644
index 00000000..4d4b8563
--- /dev/null
+++ b/tests/core/eth-module/test_eth_fee_history.py
@@ -0,0 +1,22 @@
+import pytest
+from unittest.mock import (
+    MagicMock,
+    patch,
+)
+
+
+@pytest.mark.parametrize(
+    "fee_history_args, expected",
+    (
+        ((1, "latest"), (1, "latest", [])),
+        ((1, "latest", []), (1, "latest", [])),
+        ((1, "latest", None), (1, "latest", [])),
+        ((1, "latest", [50]), (1, "latest", [50])),
+    ),
+)
+@patch("web3.eth.Eth._fee_history", new=MagicMock())
+def test_eth_fee_history_reward_percentiles_default_is_empty_list(
+    w3, fee_history_args, expected
+) -> None:
+    w3.eth.fee_history(*fee_history_args)
+    w3.eth._fee_history.assert_called_with(*expected)
diff --git a/tests/core/web3-module/test_import_and_version.py b/tests/core/web3-module/test_import_and_version.py
new file mode 100644
index 00000000..7f976716
--- /dev/null
+++ b/tests/core/web3-module/test_import_and_version.py
@@ -0,0 +1,5 @@
+def test_import_and_version():
+    import web3
+
+    version = web3.__version__
+    assert isinstance(version, str)