From 75121dec01e8dff14b9c7e77e689366e05295809 Mon Sep 17 00:00:00 2001 From: antazoey Date: Wed, 8 Jan 2025 09:58:24 -0600 Subject: [PATCH] fix: chain id cache --- .pre-commit-config.yaml | 4 ++-- ape_infura/provider.py | 41 ++++++++++++++++------------------------- pyproject.toml | 1 - setup.py | 4 ++-- tests/conftest.py | 15 --------------- tests/test_provider.py | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 54 insertions(+), 45 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 094dd21..fbd58c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,13 +22,13 @@ repos: additional_dependencies: [flake8-breakpoint, flake8-print, flake8-pydantic, flake8-type-checking] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.13.0 + rev: v1.14.1 hooks: - id: mypy additional_dependencies: [types-PyYAML, types-requests, types-setuptools, pydantic] - repo: https://github.com/executablebooks/mdformat - rev: 0.7.19 + rev: 0.7.21 hooks: - id: mdformat additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-pyproject] diff --git a/ape_infura/provider.py b/ape_infura/provider.py index 150e9d0..d4c7d23 100644 --- a/ape_infura/provider.py +++ b/ape_infura/provider.py @@ -1,15 +1,13 @@ import os import random -import time -from collections.abc import Callable from functools import cached_property from typing import Optional from ape.api import UpstreamProvider from ape.exceptions import ContractLogicError, ProviderError, VirtualMachineError -from ape.logging import logger +from ape.utils.rpc import request_with_retry from ape_ethereum.provider import Web3Provider -from requests import HTTPError, Session +from requests import Session from web3 import HTTPProvider, Web3 from web3.exceptions import ContractLogicError as Web3ContractLogicError from web3.exceptions import ExtraDataLengthError @@ -140,9 +138,21 @@ def ws_uri(self) -> Optional[str]: def connection_str(self) -> str: return self.uri - @property + @cached_property def chain_id(self): - return _run_with_retry(lambda: self._web3.eth.chain_id) + return request_with_retry( + lambda: self._get_chain_id(), + max_retries=_MAX_REQUEST_RETRIES, + min_retry_delay=_REQUEST_RETRY_DELAY * 1_000, + ) + + def _get_chain_id(self): + result = self.make_request("eth_chainId", []) + if isinstance(result, int): + return result + + # Is a hex. + return int(result, 16) def connect(self): session = _get_session() @@ -232,22 +242,3 @@ def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMa def _create_web3(http_provider: HTTPProvider) -> Web3: return Web3(http_provider) - - -def _run_with_retry( - func: Callable, max_retries: int = _MAX_REQUEST_RETRIES, retry_delay: int = _REQUEST_RETRY_DELAY -): - retries = 0 - while retries < max_retries: - try: - return func() - except HTTPError as err: - if err.response.status_code == 429: - logger.debug(f"429 Too Many Requests. Retrying in {retry_delay} seconds...") - time.sleep(retry_delay) - retries += 1 - retry_delay += retry_delay - else: - raise # Re-raise non-429 HTTP errors - - raise ProviderError(f"Exceeded maximum retries ({max_retries}).") diff --git a/pyproject.toml b/pyproject.toml index 9d66ab1..a9118ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ include = '\.pyi?$' [tool.pytest.ini_options] addopts = """ - -p no:ape_test --cov-branch --cov-report term --cov-report html diff --git a/setup.py b/setup.py index 6be267c..5062d48 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ ], "lint": [ "black>=24.10.0,<25", # Auto-formatter and linter - "mypy>=1.13.0,<2", # Static type analyzer + "mypy>=1.14.1,<2", # Static type analyzer "types-setuptools", # Needed for mypy type shed "flake8>=7.1.1,<8", # Style linter "flake8-breakpoint>=1.1.0,<2", # Detect breakpoints left in code @@ -27,7 +27,7 @@ "flake8-pydantic", # For detecting issues with Pydantic models "flake8-type-checking", # Detect imports to move in/out of type-checking blocks "isort>=5.13.2,<6", # Import sorting linter - "mdformat>=0.7.19", # Auto-formatter for markdown + "mdformat>=0.7.21", # Auto-formatter for markdown "mdformat-gfm>=0.3.5", # Needed for formatting GitHub-flavored markdown "mdformat-frontmatter>=0.4.1", # Needed for frontmatters-style headers in issue templates "mdformat-pyproject>=0.0.2", # Allows configuring in pyproject.toml diff --git a/tests/conftest.py b/tests/conftest.py index b82a585..5c83da5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,21 +6,6 @@ NETWORK_SKIPS = ("starknet",) -@pytest.fixture -def accounts(): - return ape.accounts - - -@pytest.fixture -def Contract(): - return ape.Contract - - -@pytest.fixture -def networks(): - return ape.networks - - # NOTE: Using a `str` as param for better pytest test-case name generation. @pytest.fixture( params=[f"{e}:{n}" for e, values in NETWORKS.items() if e not in NETWORK_SKIPS for n in values] diff --git a/tests/test_provider.py b/tests/test_provider.py index 9269621..b12a669 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -128,3 +128,37 @@ def test_api_secret(): session = _get_session() assert session.auth == ("", "123") del os.environ["WEB3_INFURA_PROJECT_SECRET"] + + +def test_chain_id(provider): + actual = provider.chain_id + assert actual is not None + assert isinstance(actual, int) + + # Test a couple of these just to show the cached + # chain ID does not fudge up across networks. + if provider.network.ecosystem.name == "ethereum": + if provider.network.name == "mainnet": + assert actual == 1 + elif provider.network.name == "sepolia": + assert actual == 11155111 + + +def test_chain_id_cached(mocker, networks): + """ + A test just showing we utilize a cached chain ID + to limit unnecessary requests. + """ + infura = networks.ethereum.sepolia.get_provider("infura") + infura.connect() + spy = mocker.spy(infura.web3.provider, "make_request") + + # Start off fresh for the sake of the test. + infura.__dict__.pop("chain_id") + + _ = infura.chain_id + _ = infura.chain_id # Call again! + _ = infura.chain_id # Once more! + + assert len(spy.call_args_list) == 1 + assert spy.call_args_list[0][0][0] == "eth_chainId"