From a92e5d1afe424503d480e977f1993ebd05cae401 Mon Sep 17 00:00:00 2001
From: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com>
Date: Fri, 21 Aug 2026 15:56:08 -0400
Subject: [PATCH 1/2] chore: upgrade linting tools (#2805)
---
.github/workflows/test.yaml | 338 +++++++++---------
.gitignore | 3 +
.pre-commit-config.yaml | 8 +-
docs/userguides/accounts.md | 37 +-
docs/userguides/clis.md | 59 +--
docs/userguides/compile.md | 12 +-
docs/userguides/console.md | 9 +-
docs/userguides/contract_metadata.md | 9 +-
docs/userguides/contracts.md | 15 +-
docs/userguides/dependencies.md | 4 +-
docs/userguides/developing_plugins.md | 28 +-
docs/userguides/logging.md | 3 +
docs/userguides/networks.md | 3 +
docs/userguides/proxy.md | 12 +-
docs/userguides/scripts.md | 13 +-
docs/userguides/testing.md | 43 ++-
docs/userguides/trace.md | 4 +-
docs/userguides/transactions.md | 15 +-
pyproject.toml | 16 +-
src/ape/_cli.py | 22 +-
src/ape/api/accounts.py | 2 +-
src/ape/api/address.py | 7 +-
src/ape/api/compiler.py | 6 +-
src/ape/api/config.py | 4 +-
src/ape/api/networks.py | 34 +-
src/ape/api/providers.py | 8 +-
src/ape/api/transactions.py | 6 +-
src/ape/cli/choices.py | 2 +-
src/ape/cli/options.py | 2 +-
src/ape/contracts/base.py | 24 +-
src/ape/exceptions.py | 31 +-
src/ape/logging.py | 32 +-
src/ape/managers/_contractscache.py | 64 ++--
src/ape/managers/_deploymentscache.py | 4 +-
src/ape/managers/accounts.py | 11 +-
src/ape/managers/chain.py | 37 +-
src/ape/managers/compilers.py | 10 +-
src/ape/managers/config.py | 2 +-
src/ape/managers/converters.py | 17 +-
src/ape/managers/networks.py | 28 +-
src/ape/managers/plugins.py | 4 +-
src/ape/managers/project.py | 115 +++---
src/ape/managers/query.py | 2 +-
src/ape/plugins/_utils.py | 4 +-
src/ape/pytest/contextmanagers.py | 16 +-
src/ape/pytest/fixtures.py | 21 +-
src/ape/pytest/plugin.py | 27 +-
src/ape/pytest/runners.py | 9 +-
src/ape/types/coverage.py | 22 +-
src/ape/types/events.py | 19 +-
src/ape/types/private_mempool.py | 2 +-
src/ape/types/signatures.py | 6 +-
src/ape/types/trace.py | 4 +-
src/ape/types/units.py | 2 +-
src/ape/utils/__init__.py | 2 +-
src/ape/utils/_github.py | 10 +-
src/ape/utils/abi.py | 2 +-
src/ape/utils/basemodel.py | 8 +-
src/ape/utils/misc.py | 23 +-
src/ape/utils/os.py | 5 +-
src/ape/utils/process.py | 9 +-
src/ape/utils/trace.py | 27 +-
src/ape_accounts/_cli.py | 4 +-
src/ape_accounts/accounts.py | 14 +-
src/ape_cache/query.py | 10 +-
src/ape_compile/_cli.py | 2 +-
src/ape_compile/config.py | 6 +-
src/ape_console/_cli.py | 2 +-
src/ape_console/config.py | 3 +-
src/ape_ethereum/__init__.py | 1 -
src/ape_ethereum/_converters.py | 5 +-
src/ape_ethereum/ecosystem.py | 60 ++--
src/ape_ethereum/provider.py | 135 +++----
src/ape_ethereum/trace.py | 91 ++---
src/ape_ethereum/transactions.py | 17 +-
src/ape_networks/_cli.py | 8 +-
src/ape_networks/config.py | 5 +-
src/ape_node/provider.py | 25 +-
src/ape_pm/_cli.py | 8 +-
src/ape_pm/compiler.py | 2 +-
src/ape_pm/dependency.py | 19 +-
src/ape_pm/project.py | 15 +-
src/ape_run/_cli.py | 2 +-
src/ape_test/_cli.py | 2 +-
src/ape_test/_watch.py | 8 +-
src/ape_test/accounts.py | 4 +-
src/ape_test/config.py | 8 +-
src/ape_test/provider.py | 14 +-
tests/conftest.py | 4 +-
tests/functional/conftest.py | 2 +-
tests/functional/geth/test_chain.py | 1 +
tests/functional/geth/test_network_manager.py | 5 +-
tests/functional/geth/test_provider.py | 99 ++---
tests/functional/geth/test_trace.py | 8 +-
tests/functional/test_accounts.py | 49 ++-
tests/functional/test_block_container.py | 40 ++-
tests/functional/test_chain.py | 14 +-
tests/functional/test_compilers.py | 4 +-
tests/functional/test_config.py | 35 +-
tests/functional/test_contract_event.py | 8 +-
.../test_contract_method_handler.py | 10 +-
tests/functional/test_contracts_cache.py | 31 ++
.../test_default_sender_context_manager.py | 10 +-
tests/functional/test_dependencies.py | 2 +-
tests/functional/test_ecosystem.py | 7 +-
tests/functional/test_exceptions.py | 6 +-
tests/functional/test_fixtures.py | 4 +-
tests/functional/test_network_manager.py | 31 +-
tests/functional/test_plugins.py | 32 +-
tests/functional/test_project.py | 14 +-
.../test_reverts_context_manager.py | 28 +-
tests/functional/test_test.py | 2 +-
tests/functional/utils/test_abi.py | 2 +-
tests/integration/cli/conftest.py | 3 +-
.../projects/only-dependencies/__init__.py | 1 +
.../dependency_in_project_only/__init__.py | 1 +
.../cli/projects/script/scripts/error_cli.py | 2 +-
.../cli/projects/script/scripts/error_main.py | 2 +-
.../projects/script/scripts/error_no_def.py | 2 +-
.../cli/projects/test/tests/test_networks.py | 2 +-
tests/integration/cli/test_accounts.py | 28 +-
tests/integration/cli/test_init.py | 4 +-
tests/integration/cli/test_pm.py | 14 +-
tests/integration/cli/test_test.py | 4 +-
tests/unit/api/test_api_init.py | 4 +-
125 files changed, 1196 insertions(+), 1112 deletions(-)
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index aac30e8b27..07042b8120 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -16,167 +16,177 @@ concurrency:
cancel-in-progress: true
jobs:
- linting:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v5
-
- - name: Setup Python
- uses: astral-sh/setup-uv@v7
- with:
- python-version: "3.10"
- enable-cache: true
- cache-dependency-glob: pyproject.toml
-
- - name: Run Ruff Lint
- run: uv run --only-group style ruff check .
-
- - name: Run Ruff Format
- run: uv run --only-group style ruff format --check .
-
- - name: Run mdformat
- run: uv run --only-group style mdformat --check *.md docs/**/*.md
-
- type-check:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v5
-
- - name: Setup Python
- uses: astral-sh/setup-uv@v7
- with:
- python-version: "3.10"
- enable-cache: true
- cache-dependency-glob: pyproject.toml
-
- - name: Run MyPy
- run: uv run --group lint --group test mypy .
-
- test:
- runs-on: ${{ matrix.os }}
-
- strategy:
- matrix:
- os: [ubuntu-latest, macos-latest] # eventually add `windows-latest`
- python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
-
- env:
- COVERAGE_ARGS: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11' && '--cov=src --cov-append' || '--no-cov' }}
- GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- steps:
- - uses: actions/checkout@v5
-
- - name: Setup Python
- uses: astral-sh/setup-uv@v7
- with:
- python-version: ${{ matrix.python-version }}
- enable-cache: true
- cache-dependency-glob: pyproject.toml
-
- - name: Install Test Environment
- run: uv sync --no-default-groups --group test
-
- - name: Install Geth
- uses: ApeWorX/geth-action@v1
-
- - name: Cache Solidity Compilers
- uses: actions/cache@v5
- with:
- path: ~/.solcx
- key: ${{ runner.os }}-solcx-${{ hashFiles('pyproject.toml', 'tests/**/*.sol') }}
- restore-keys: |
- ${{ runner.os }}-solcx-
-
- - name: Install Solidity Compilers
- run: |
- uv run --no-sync python -c "
- import re
- from pathlib import Path
-
- from ape.utils import pragma_str_to_specifier_set
- from solcx import get_installable_solc_versions, install_solc
-
- def get_pragma_spec_from_path(path):
- source = path.read_text(encoding='utf8')
- pragma_match = next(
- re.finditer(r'(?:\n|^)\s*pragma\s*solidity\s*([^;\n]*)', source),
- None,
- )
- return pragma_str_to_specifier_set(pragma_match.groups()[0]) if pragma_match else None
-
- def select_version(pragma_spec, options):
- choices = sorted(pragma_spec.filter(options), reverse=True)
- return choices[0] if choices else None
-
- installable_versions = get_installable_solc_versions()
- versions = set()
- for path in Path('tests').rglob('*.sol'):
- pragma_spec = get_pragma_spec_from_path(path)
- if pragma_spec and (version := select_version(pragma_spec, installable_versions)):
- versions.add(str(version))
-
- versions = sorted(versions)
- print('Installing Solidity compilers:', ', '.join(versions) or 'none')
- for version in versions:
- install_solc(version, show_progress=False)
- "
-
- - name: Cache Ape Packages
- uses: actions/cache@v5
- with:
- path: ~/.ape/packages
- key: ${{ runner.os }}-ape-packages-${{ hashFiles('pyproject.toml') }}
- restore-keys: |
- ${{ runner.os }}-ape-packages-
-
- - name: Install Project Dependencies
- run: uv run --no-sync ape pm install
-
- - name: Run Functional Tests
- run: |
- uv run --no-sync ape test \
- -s -v ERROR \
- -n auto --dist loadgroup \
- -m "not hypothesis" \
- $COVERAGE_ARGS \
- tests/functional
-
- - name: Run Integration Tests
- run: |
- uv run --no-sync ape test \
- -s -v ERROR \
- -n auto --dist loadgroup \
- -m "not hypothesis" \
- $COVERAGE_ARGS \
- tests/integration
-
- - name: Run Performance Tests
- run: |
- uv run --no-sync ape test \
- -s -v ERROR \
- tests/performance
-
- fuzzing:
- runs-on: ubuntu-latest
-
- strategy:
- fail-fast: true
-
- steps:
- - uses: actions/checkout@v5
-
- - name: Setup Python
- uses: astral-sh/setup-uv@v7
- with:
- python-version: "3.10"
- enable-cache: true
- cache-dependency-glob: pyproject.toml
-
- - name: Install Test Environment
- run: uv sync --no-default-groups --group test
-
- - name: Run Tests
- run: uv run --no-sync ape test -s -m "hypothesis" --no-cov
+ linting:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Setup Python
+ uses: astral-sh/setup-uv@v7
+ with:
+ python-version: "3.10"
+ enable-cache: true
+ cache-dependency-glob: pyproject.toml
+
+ - name: Run Ruff
+ run: |
+ echo "=== Ruff Version ==="
+ uv run --only-group style ruff --version
+ echo "=== Ruff Configuration ==="
+ uv run --only-group style ruff config
+ echo "=== Running Ruff Check ==="
+ uv run --only-group style ruff check .
+ echo "=== Running Ruff Format ==="
+ uv run --only-group style ruff format --check .
+
+ - name: Run Mdformat
+ run: |
+ echo "=== Mdformat Version ==="
+ uv run --only-group style mdformat --version
+ echo "=== Running Mdformat ==="
+ uv run --only-group style mdformat --check *.md docs/**/*.md
+
+ type-check:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Setup Python
+ uses: astral-sh/setup-uv@v7
+ with:
+ python-version: "3.10"
+ enable-cache: true
+ cache-dependency-glob: pyproject.toml
+
+ - name: Run MyPy
+ run: |
+ echo "=== Mypy Version ==="
+ uv run --group lint --group test mypy --version
+ echo "=== Running Mypy ==="
+ uv run --group lint --group test mypy .
+
+ test:
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest] # eventually add `windows-latest`
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
+
+ env:
+ COVERAGE_ARGS: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11' && '--cov=src --cov-append' || '--no-cov' }}
+ GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Setup Python
+ uses: astral-sh/setup-uv@v7
+ with:
+ python-version: ${{ matrix.python-version }}
+ enable-cache: true
+ cache-dependency-glob: pyproject.toml
+
+ - name: Install Test Environment
+ run: uv sync --no-default-groups --group test
+
+ - name: Install Geth
+ uses: ApeWorX/geth-action@v1
+
+ - name: Cache Solidity Compilers
+ uses: actions/cache@v5
+ with:
+ path: ~/.solcx
+ key: ${{ runner.os }}-solcx-${{ hashFiles('pyproject.toml', 'tests/**/*.sol') }}
+ restore-keys: |
+ ${{ runner.os }}-solcx-
+
+ - name: Install Solidity Compilers
+ run: |
+ uv run --no-sync python -c "
+ import re
+ from pathlib import Path
+
+ from ape.utils import pragma_str_to_specifier_set
+ from solcx import get_installable_solc_versions, install_solc
+
+ def get_pragma_spec_from_path(path):
+ source = path.read_text(encoding='utf8')
+ pragma_match = next(
+ re.finditer(r'(?:\n|^)\s*pragma\s*solidity\s*([^;\n]*)', source),
+ None,
+ )
+ return pragma_str_to_specifier_set(pragma_match.groups()[0]) if pragma_match else None
+
+ def select_version(pragma_spec, options):
+ choices = sorted(pragma_spec.filter(options), reverse=True)
+ return choices[0] if choices else None
+
+ installable_versions = get_installable_solc_versions()
+ versions = set()
+ for path in Path('tests').rglob('*.sol'):
+ pragma_spec = get_pragma_spec_from_path(path)
+ if pragma_spec and (version := select_version(pragma_spec, installable_versions)):
+ versions.add(str(version))
+
+ versions = sorted(versions)
+ print('Installing Solidity compilers:', ', '.join(versions) or 'none')
+ for version in versions:
+ install_solc(version, show_progress=False)
+ "
+
+ - name: Cache Ape Packages
+ uses: actions/cache@v5
+ with:
+ path: ~/.ape/packages
+ key: ${{ runner.os }}-ape-packages-${{ hashFiles('pyproject.toml') }}
+ restore-keys: |
+ ${{ runner.os }}-ape-packages-
+
+ - name: Install Project Dependencies
+ run: uv run --no-sync ape pm install
+
+ - name: Run Functional Tests
+ run: |
+ uv run --no-sync ape test \
+ -s -v ERROR \
+ -n auto --dist loadgroup \
+ -m "not hypothesis" \
+ $COVERAGE_ARGS \
+ tests/functional
+
+ - name: Run Integration Tests
+ run: |
+ uv run --no-sync ape test \
+ -s -v ERROR \
+ -n auto --dist loadgroup \
+ -m "not hypothesis" \
+ $COVERAGE_ARGS \
+ tests/integration
+
+ - name: Run Performance Tests
+ run: |
+ uv run --no-sync ape test \
+ -s -v ERROR \
+ tests/performance
+
+ fuzzing:
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: true
+
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Setup Python
+ uses: astral-sh/setup-uv@v7
+ with:
+ python-version: "3.10"
+ enable-cache: true
+ cache-dependency-glob: pyproject.toml
+
+ - name: Run Tests
+ run: uv run --only-group test ape test -s -m "hypothesis" --no-cov
diff --git a/.gitignore b/.gitignore
index 63dc15a5ec..bb5fdd6738 100644
--- a/.gitignore
+++ b/.gitignore
@@ -83,6 +83,9 @@ ipython_config.py
# pyenv
.python-version
+# uv pkg mngr
+uv.lock
+
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index da0dc1f106..cc7d6bb7cc 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,18 +1,18 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v5.0.0
+ rev: v6.0.0
hooks:
- id: check-yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.14.10 # Latest ruff version
+ rev: v0.15.12 # Latest ruff version
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v1.16.1
+ rev: v2.3.1
hooks:
- id: mypy
additional_dependencies: [
@@ -27,7 +27,7 @@ repos:
]
- repo: https://github.com/executablebooks/mdformat
- rev: 0.7.22
+ rev: 1.0.0
hooks:
- id: mdformat
additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-pyproject]
diff --git a/docs/userguides/accounts.md b/docs/userguides/accounts.md
index 21c9f27ad0..ec6ee44071 100644
--- a/docs/userguides/accounts.md
+++ b/docs/userguides/accounts.md
@@ -51,7 +51,9 @@ You can also change settings at run-time, such as the mnemonic:
```python
from ape import accounts
-accounts.test_accounts.mnemonic = "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"
+accounts.test_accounts.mnemonic = (
+ "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"
+)
print(accounts.test_accounts[0])
# 0x627306090abaB3A6e1400e9345bC60c78a8BEf57
```
@@ -91,14 +93,14 @@ account.balance += int(1e18) # Gives `account` 1 Ether
In order to eliminate the usage of sender in contract calls, you can use `use_sender` context manager.
```python
-with accounts.use_sender(0): # Use first account from test mnemonic
- contract.myFunction(1)
+with accounts.use_sender(0): # Use first account from test mnemonic
+ contract.myFunction(1)
-with accounts.use_sender("
"): # Impersonate an account
- contract.myFunction(1)
+with accounts.use_sender(""): # Impersonate an account
+ contract.myFunction(1)
-with accounts.use_sender(a): # a is a `TestAccountAPI` object
- contract.myFunction(1)
+with accounts.use_sender(a): # a is a `TestAccountAPI` object
+ contract.myFunction(1)
```
## Live Network Accounts
@@ -165,8 +167,8 @@ from ape_accounts import generate_account
account, mnemonic = generate_account("my-account", "mySecureP@ssphrase")
-print(f'Save your mnemonic: {mnemonic}')
-print(f'Your new account address is: {account.address}')
+print(f"Save your mnemonic: {mnemonic}")
+print(f"Your new account address is: {account.address}")
```
See the [documentation for `generate_account()`](../methoddocs/ape_accounts.html#ape_accounts.generate_account) for more options.
@@ -209,7 +211,7 @@ mnemonic = "test test test test test test test test test test test junk"
account = import_account_from_mnemonic(alias, passphrase, mnemonic)
-print(f'Your imported account address is: {account.address}')
+print(f"Your imported account address is: {account.address}")
```
Or using a raw private key [using `import_account_from_private_key()`](../methoddocs/ape_accounts.html#ape_accounts.import_account_from_private_key):
@@ -224,7 +226,7 @@ private_key = os.urandom(32).hex()
account = import_account_from_private_key(alias, passphrase, private_key)
-print(f'Your imported account address is: {account.address}')
+print(f"Your imported account address is: {account.address}")
```
#### Exporting Accounts
@@ -252,16 +254,16 @@ In order to reduce repetition of adding `sender` in your contract calls, you can
```python
with accounts.use_sender(0):
- contract.myFunction(1)
+ contract.myFunction(1)
with accounts.use_sender(""):
- contract.myFunction(1)
+ contract.myFunction(1)
with accounts.use_sender(""):
- contract.myFunction(1)
+ contract.myFunction(1)
-with accounts.use_sender(a): # a is a `AccountAPI` object
- contract.myFunction(1)
+with accounts.use_sender(a): # a is a `AccountAPI` object
+ contract.myFunction(1)
```
## Signing Messages
@@ -304,10 +306,12 @@ from eip712.messages import EIP712Message, EIP712Domain
from eth_pydantic_types import abi
from pydantic import BaseModel
+
class Person(BaseModel):
name: abi.string
wallet: abi.address
+
class Mail(EIP712Message):
eip712_domain = EIP712Domain(
name="Ether Mail",
@@ -319,6 +323,7 @@ class Mail(EIP712Message):
sender: Person
receivers: list[Person]
+
alice = Person(name="Alice", wallet="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826")
bob = Person("Bob", "0xB0B0b0b0b0b0B000000000000000000000000000")
charlie = Person("Charlie", "0xC0C0c0c0c0c0C000000000000000000000000000")
diff --git a/docs/userguides/clis.md b/docs/userguides/clis.md
index 2d4cce2ba9..8e081bcbfc 100644
--- a/docs/userguides/clis.md
+++ b/docs/userguides/clis.md
@@ -40,17 +40,20 @@ In Ape, it is easy to extend the CLI context object and use the extended version
from ape.cli import ApeCliContextObject, ape_cli_context
import click
+
class MyManager:
- """My custom manager."""
+ """My custom manager."""
+
class CustomContext(ApeCliContextObject):
- my_manager: MyManager = MyManager()
- """Add new managers to your custom context"""
-
- @property
- def signer(self):
- """Utilize existing managers in your custom context."""
- return self.account_manager.load("my_account")
+ my_manager: MyManager = MyManager()
+ """Add new managers to your custom context"""
+
+ @property
+ def signer(self):
+ """Utilize existing managers in your custom context."""
+ return self.account_manager.load("my_account")
+
@click.command()
@ape_cli_context(obj_type=CustomContext)
@@ -82,19 +85,21 @@ Use `ecosystem`, `network`, and `provider` argument names in your command implem
import click
from ape.cli import network_option
+
@click.command()
@network_option()
def cmd(provider):
- # This command only needs the provider.
- click.echo(provider.name)
+ # This command only needs the provider.
+ click.echo(provider.name)
+
@click.command()
@network_option()
def cmd_2(ecosystem, network, provider):
- # This command uses all parts of the parsed network choice.
- click.echo(ecosystem.name)
- click.echo(network.name)
- click.echo(provider.name)
+ # This command uses all parts of the parsed network choice.
+ click.echo(ecosystem.name)
+ click.echo(network.name)
+ click.echo(provider.name)
```
The [ConnectedProviderCommand](../methoddocs/cli.html#ape.cli.commands.ConnectedProviderCommand) automatically uses the `--network` option and connects to the network before any of your code executes and then disconnects afterward.
@@ -105,22 +110,26 @@ Additionally, specify `ecosystem`, `network`, or `provider` in your command func
import click
from ape.cli import ConnectedProviderCommand
+
@click.group()
def cli():
pass
+
@cli.command(cls=ConnectedProviderCommand)
def cmd_1(network, provider):
- click.echo(network.name)
- click.echo(provider.is_connected) # True
+ click.echo(network.name)
+ click.echo(provider.is_connected) # True
+
@cli.command(cls=ConnectedProviderCommand)
def cmd_2(provider):
- click.echo(provider.is_connected) # True
+ click.echo(provider.is_connected) # True
+
@cli.command(cls=ConnectedProviderCommand)
def cmd_3():
- click.echo("Using params from ConnectedProviderCommand is optional")
+ click.echo("Using params from ConnectedProviderCommand is optional")
```
## Account Tools
@@ -168,8 +177,8 @@ from ape.cli import select_account
@click.command()
def cmd():
- account = select_account("Select an account to use")
- click.echo(f"You selected {account.address}.")
+ account = select_account("Select an account to use")
+ click.echo(f"You selected {account.address}.")
```
Similarly, there are a couple custom arguments for aliases alone that are useful when making CLIs for account creation.
@@ -207,15 +216,18 @@ from ape_accounts.accounts import KeyfileAccount
# NOTE: This is just an example and not anything specific or recommended.
APPLICATION_PREFIX = ""
+
@click.command()
@existing_alias_argument(account_type=KeyfileAccount)
def cli_0(alias):
- pass
+ pass
+
@click.command()
@existing_alias_argument(account_type=lambda a: a.alias.startswith(APPLICATION_PREFIX))
def cli_1(alias):
- pass
+ pass
+
# Select from the given accounts directly.
my_accounts = [accounts.load("me"), accounts.load("me2")]
@@ -235,10 +247,11 @@ import click
from ape.cli import contract_file_paths_argument
+
@click.command()
@contract_file_paths_argument()
def cli(file_paths: set[Path]):
- # Loop through all source files given (or all source files in the project).
+ # Loop through all source files given (or all source files in the project).
for path in file_paths:
click.echo(f"Source found: {path}")
```
diff --git a/docs/userguides/compile.md b/docs/userguides/compile.md
index 67bfdf7460..2108b981fb 100644
--- a/docs/userguides/compile.md
+++ b/docs/userguides/compile.md
@@ -112,9 +112,7 @@ from pathlib import Path
from ape import compilers
settings = {"vyper": {"version": "0.3.7"}, "solidity": {"version": "0.8.0"}}
-compilers.compile(
- ["path/to/contract.vy", "path/to/contract.sol"], settings=settings
-)
+compilers.compile(["path/to/contract.vy", "path/to/contract.sol"], settings=settings)
# Or, more explicitly:
vyper = compilers.get_compiler("vyper", settings=settings["vyper"])
@@ -136,10 +134,10 @@ CODE = """
"""
container = compilers.compile_source(
- "vyper",
- CODE,
- settings={"vyper": {"version": "0.3.7"}},
- contractName="MyContract",
+ "vyper",
+ CODE,
+ settings={"vyper": {"version": "0.3.7"}},
+ contractName="MyContract",
)
owner = accounts.test_accounts[0]
diff --git a/docs/userguides/console.md b/docs/userguides/console.md
index e7b04e4d79..dc62b195d9 100644
--- a/docs/userguides/console.md
+++ b/docs/userguides/console.md
@@ -59,6 +59,7 @@ An example file might look something like this:
from ape import networks
from eth_utils import encode_hex, decode_hex
+
def latest(key):
return getattr(networks.active_provider.get_block("latest"), key)
```
@@ -66,11 +67,11 @@ def latest(key):
Then both imported util functions and `latest()` will be available when you launch the console.
```python
-In [1]: latest('number')
+In[1]: latest("number")
Out[1]: 14388241
-In [2]: encode_hex(latest('hash'))
-Out[2]: '0x68f768988e9bd4be971d527f72483f321975fa52aff9692b6d0e0af71fb77aaf'
+In[2]: encode_hex(latest("hash"))
+Out[2]: "0x68f768988e9bd4be971d527f72483f321975fa52aff9692b6d0e0af71fb77aaf"
```
### Init Function
@@ -85,7 +86,7 @@ def ape_init_extras(chain):
Then `web3` will be available to use immediately.
```python
-In [1]: web3.eth.chain_id
+In[1]: web3.eth.chain_id
Out[1]: 1
```
diff --git a/docs/userguides/contract_metadata.md b/docs/userguides/contract_metadata.md
index 7b25c1f30d..34f51b73ad 100644
--- a/docs/userguides/contract_metadata.md
+++ b/docs/userguides/contract_metadata.md
@@ -52,6 +52,7 @@ def is_creator(contract, address) -> bool:
metadata = contract.creation_metadata
return metadata and metadata.deployer == address
+
# Historical queries at contract inception
creation_block = contract.creation_metadata.block
initial_balance = contract.balance(block_number=creation_block)
@@ -111,13 +112,7 @@ metadata = chain.contracts.get_creation_metadata(address)
# Store custom metadata
chain.contracts.cache_contract_creation(
- address,
- ContractCreation(
- txn_hash="0xabc...",
- block=12345,
- deployer="0xdef...",
- factory=None
- )
+ address, ContractCreation(txn_hash="0xabc...", block=12345, deployer="0xdef...", factory=None)
)
```
diff --git a/docs/userguides/contracts.md b/docs/userguides/contracts.md
index 2a99d8fdf8..a97f1a0ec6 100644
--- a/docs/userguides/contracts.md
+++ b/docs/userguides/contracts.md
@@ -99,7 +99,6 @@ You can also use the [at() method](../methoddocs/contracts.html#ape.contracts.ba
from ape import project
contract = project.MyContract.at("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")
-
```
By default, Ape will detect if the contract is a proxy and use the implementation contract's interface. See the [Proxy Contracts guide](./proxy.html) for more details on proxy handling.
@@ -154,8 +153,7 @@ contract = Contract(address, abi="abi.json")
# Using a Python dictionary from JSON:
contract = Contract(
- address,
- abi=[{"name":"foo","type":"fallback", "stateMutability":"nonpayable"}]
+ address, abi=[{"name": "foo", "type": "fallback", "stateMutability": "nonpayable"}]
)
```
@@ -172,8 +170,9 @@ You could then refer back to it like so:
```python
from ape import project, chain
+
def main():
- my_contract = chain.contracts.get_deployments(project.MyContract)[-1]
+ my_contract = chain.contracts.get_deployments(project.MyContract)[-1]
```
or
@@ -461,7 +460,7 @@ import ape
usdc = ape.Contract("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
# ABI type for a hex method ID
-assert usdc.identifier_lookup['0x70a08231'].selector == 'balanceOf(address)'
+assert usdc.identifier_lookup["0x70a08231"].selector == "balanceOf(address)"
# Also, selectors from method and event signatures
assert usdc.selector_identifiers["balances(address)"] == "0x27e235e3"
@@ -486,9 +485,13 @@ Here is an example of how you can use the multicall module:
import ape
from ape_ethereum import multicall
-ADDRESSES = ("0xF4b8A02D4e8D76070bD7092B54D2cBbe90fa72e9", "0x80067013d7F7aF4e86b3890489AcAFe79F31a4Cb")
+ADDRESSES = (
+ "0xF4b8A02D4e8D76070bD7092B54D2cBbe90fa72e9",
+ "0x80067013d7F7aF4e86b3890489AcAFe79F31a4Cb",
+)
POOLS = [ape.project.IPool.at(a) for a in ADDRESSES]
+
def main():
# Use multi-call.
call = multicall.Call()
diff --git a/docs/userguides/dependencies.md b/docs/userguides/dependencies.md
index 411e158592..26bcf1a86f 100644
--- a/docs/userguides/dependencies.md
+++ b/docs/userguides/dependencies.md
@@ -209,7 +209,7 @@ You can also use Python to install dependencies, using `**kwargs` as the same fi
from ape import project
project.dependencies.install(
- github="OpenZeppelin/openzeppelin-contracts", name="openzeppelin", version="4.9.6"
+ github="OpenZeppelin/openzeppelin-contracts", name="openzeppelin", version="4.9.6"
)
```
@@ -367,7 +367,7 @@ from ape import accounts, project
# NOTE: This will compile the dependency
dependency_project = project.dependencies["my_dependency"]["1.0.0"]
-dependency_contract = dependency_project.DependencyContractType
+dependency_contract = dependency_project.DependencyContractType
my_account = accounts.load("alias")
deployed_contract = my_account.deploy(dependency_contract, "argument")
print(deployed_contract.address)
diff --git a/docs/userguides/developing_plugins.md b/docs/userguides/developing_plugins.md
index 250dd23f3a..4893d9dd3a 100644
--- a/docs/userguides/developing_plugins.md
+++ b/docs/userguides/developing_plugins.md
@@ -44,9 +44,9 @@ from web3 import Web3, HTTPProvider
class MyProvider(ProviderAPI):
_web3: Web3 = None # type: ignore
-
+
def connect(self):
- self._web3 = Web3(HTTPProvider(str("https://localhost:1337")))
+ self._web3 = Web3(HTTPProvider(str("https://localhost:1337")))
"""Implement rest of abstract methods"""
```
@@ -58,12 +58,13 @@ Once you have finished implementing your API classes, you need to register them
```python
from ape import plugins
+
# Here, we register our provider plugin so we can use it in 'ape'.
@plugins.register(plugins.ProviderPlugin)
def providers():
# NOTE: By keeping this import local, we avoid slower plugin load times.
from ape_my_plugin.provider import MyProvider
-
+
# NOTE: 'MyProvider' defined in a prior code-block.
yield "ethereum", "local", MyProvider
```
@@ -87,6 +88,7 @@ To create a CLI plugin, create any type of `click` command (such as a `click.gro
```python
import click
+
@click.group
def cli():
"""My custom commands."""
@@ -103,11 +105,13 @@ Then, register it using `entrypoints`, which is a built-in python registry of it
```python
...
-entry_points={
- "ape_cli_subcommands": [
- "ape_myplugin=ape_myplugin._cli:cli",
- ],
-},
+entry_points = (
+ {
+ "ape_cli_subcommands": [
+ "ape_myplugin=ape_myplugin._cli:cli",
+ ],
+ },
+)
...
```
@@ -125,10 +129,11 @@ Follow [this guide](./clis.html) to learn more about what you can do with the ut
import click
from ape.cli import existing_alias_argument
+
@click.command()
@existing_alias_argument()
def my_cmd(alias):
- click.echo(f"{alias} is an existing account!")
+ click.echo(f"{alias} is an existing account!")
```
## Using Plugins
@@ -176,8 +181,10 @@ import click
def cli():
pass
+
""" Or """
+
@click.command()
def cli():
"""Utilities for my plugin"""
@@ -200,7 +207,8 @@ logger.info("This is a log message")
```python
from ape.cli import ape_cli_context
+
@ape_cli_context()
def my_command(cli_ctx):
- cli_ctx.logger.info("my log message")
+ cli_ctx.logger.info("my log message")
```
diff --git a/docs/userguides/logging.md b/docs/userguides/logging.md
index 515f7c8bb5..1830a8d548 100644
--- a/docs/userguides/logging.md
+++ b/docs/userguides/logging.md
@@ -54,6 +54,7 @@ You can also import and use the logger in your own Python scripts or commands:
```python
from ape.logging import logger, LogLevel
+
def main():
logger.info("You have entered `main()`.")
logger.set_level(LogLevel.WARNING)
@@ -64,6 +65,7 @@ You can also use the `.at_level()` context-manager to temporarily change the log
```python
from ape.logging import logger, LogLevel
+
def main():
with logger.at_level(LogLevel.WARNING):
# An operation where you want to ensure WARN-level logs appear.
@@ -75,6 +77,7 @@ You can also disable the logger in Python:
```python
from ape.logging import logger, LogLevel
+
def main():
logger.disable() # Turns off logging entirely.
with logger.disabled():
diff --git a/docs/userguides/networks.md b/docs/userguides/networks.md
index cfc7c8318f..300ff42576 100644
--- a/docs/userguides/networks.md
+++ b/docs/userguides/networks.md
@@ -603,6 +603,7 @@ For example, if you are using a script with a default network connection, you ca
```python
from ape import chain, networks
+
def main():
start_provider = chain.provider.name
with networks.ethereum.mainnet.use_provider("node") as provider:
@@ -616,6 +617,7 @@ Jump between networks to simulate multi-chain behavior.
import click
from ape import networks
+
@click.command()
def cli():
with networks.polygon.mainnet.use_provider("node"):
@@ -675,6 +677,7 @@ We can switch to a forked network by doing this:
```python
from ape import networks
+
def main():
with networks.fork(provider_name="foundry"):
...
diff --git a/docs/userguides/proxy.md b/docs/userguides/proxy.md
index c394cb243b..571fde47cf 100644
--- a/docs/userguides/proxy.md
+++ b/docs/userguides/proxy.md
@@ -60,10 +60,7 @@ contract = project.MyImplementation.at(
)
# Or disable proxy detection entirely if you know it's not a proxy
-contract = project.MyContract.at(
- "0xAddress",
- detect_proxy=False
-)
+contract = project.MyContract.at("0xAddress", detect_proxy=False)
```
You can also use the `Contract` factory with proxy parameters:
@@ -76,11 +73,8 @@ contract = Contract("0xAddress", detect_proxy=False)
# Provide custom proxy info
contract = Contract(
- "0xProxyAddress",
- proxy_info=ProxyInfoAPI(
- target="0xImplementationAddress",
- type_name=ProxyType.STANDARD
- )
+ "0xProxyAddress",
+ proxy_info=ProxyInfoAPI(target="0xImplementationAddress", type_name=ProxyType.STANDARD),
)
```
diff --git a/docs/userguides/scripts.md b/docs/userguides/scripts.md
index 85ea67750f..84e4c3bcce 100644
--- a/docs/userguides/scripts.md
+++ b/docs/userguides/scripts.md
@@ -13,6 +13,7 @@ Follow [this guide](./clis.html) to learn more about what you can do with the ut
```python
import click
+
@click.command()
def cli():
print("Hello world!")
@@ -46,16 +47,17 @@ from ape.cli import ConnectedProviderCommand
@click.command(cls=ConnectedProviderCommand)
def cli(ecosystem, network, provider):
click.echo(f"Connected to {ecosystem.name}:{network.name} using provider '{provider.name}'.")
-
+
# Access chain and other managers automatically
from ape import chain
+
click.echo(f"Current block: {chain.blocks.height}")
+
@click.command(cls=ConnectedProviderCommand)
def cli(network, provider):
click.echo(f"You are connected to network '{network.name}'.")
click.echo(provider.chain_id)
-
```
## Multi-Network Commands
@@ -122,14 +124,12 @@ Here is an example of a multi-chain script:
import click
from ape.cli import ape_cli_context
+
@click.command()
@ape_cli_context()
def cli(cli_ctx):
# There is no connection yet at this point.
- testnets = {
- "ethereum": ["sepolia"],
- "polygon": ["amoy"]
- }
+ testnets = {"ethereum": ["sepolia"], "polygon": ["amoy"]}
nm = cli_ctx.network_manager
for ecosystem_name, networks in testnets.items():
@@ -172,6 +172,7 @@ To demonstrate, use the following script:
from ape import networks
import click
+
def main():
ecosystem_name = networks.provider.network.ecosystem.name
network_name = networks.provider.network.name
diff --git a/docs/userguides/testing.md b/docs/userguides/testing.md
index a2da06452e..1cce727b21 100644
--- a/docs/userguides/testing.md
+++ b/docs/userguides/testing.md
@@ -51,20 +51,24 @@ import ape
import pytest
import re
+
# SETUP PHASE
# NOTE: More on fixtures is discussed in later sections of this guide!
@pytest.fixture
def owner(accounts):
return accounts[0]
+
@pytest.fixture
def not_owner(accounts):
return accounts[1]
+
@pytest.fixture
def my_contract(owner, project):
return owner.deploy(project.MyContract)
+
def test_authorization(my_contract, owner, not_owner):
# INVOCATION PHASE
my_contract.set_owner(sender=owner)
@@ -73,11 +77,11 @@ def test_authorization(my_contract, owner, not_owner):
# ASSERTION PHASE
with ape.reverts("!authorized"):
my_contract.authorized_method(sender=not_owner)
-
+
# You can also test against custom error types
with ape.reverts(my_contract.Unauthorized):
my_contract.authorized_method(sender=not_owner)
-
+
# Or match error patterns with regex
with ape.reverts(re.compile(r"^!auth.*")):
my_contract.authorized_method(sender=not_owner)
@@ -111,6 +115,7 @@ For code readability and sustainability, create your own fixtures using the `acc
```python
import pytest
+
@pytest.fixture
def owner(accounts):
return accounts[0]
@@ -121,8 +126,7 @@ def receiver(accounts):
return accounts[1]
-def test_my_method(owner, receiver):
- ...
+def test_my_method(owner, receiver): ...
```
You can configure your accounts by changing the `mnemonic`, `number_of_accounts`, and `balance` in the `test` section of your `ape-config.yaml` file:
@@ -208,10 +212,12 @@ You also have access to the `project` you are testing. You will need this to dep
```python
import pytest
+
@pytest.fixture
def owner(accounts):
return accounts[0]
+
@pytest.fixture
def my_contract(project, owner):
# ^ use the 'project' fixture from the 'ape-test' plugin
@@ -252,6 +258,7 @@ Then, during `test_my_contract_1()`, instead of deploying again, it uses the cac
```python
import pytest
+
@pytest.fixture(scope="session")
def my_contract(accounts, project):
owner = accounts[0]
@@ -260,12 +267,14 @@ def my_contract(accounts, project):
contract.initialize(sender=owner)
return contract
-def test_something_else():
- ...
+
+def test_something_else(): ...
+
def test_my_contract_0(my_contract):
my_contract.myMethod()
+
def test_my_contract_1(my_contract):
my_contract.myMethod()
```
@@ -293,6 +302,7 @@ If you are using chain-isolation and have a higher-scoped fixture that you know
import ape
from ape_tokens import tokens
+
@ape.fixture(scope="session", chain_isolation=False, params=("WETH", "DAI", "BAT"))
def token_addresses(request):
return tokens[request].address
@@ -408,6 +418,7 @@ From our earlier example we can see this in action:
```python
import ape
+
def test_authorization(my_contract, owner, not_owner):
my_contract.set_owner(sender=owner)
assert owner == my_contract.owner()
@@ -451,6 +462,7 @@ Take for example:
```python
# @version 0.4.3
+
@external
def check_value(_value: uint256) -> bool:
assert _value != 0 # dev: invalid value
@@ -462,6 +474,7 @@ We can explicitly cause a transaction revert and check the failed line by supply
```python
import ape
+
def test_authorization(my_contract, owner):
with ape.reverts(dev_message="dev: invalid value"):
my_contract.check_value(sender=owner)
@@ -516,6 +529,7 @@ Similarly, if you require dev assertions for non-reentrant functions you must be
def _foo_internal(): # dev: correct location
pass
+
@external
@nonreentrant
def foo():
@@ -535,18 +549,22 @@ We can ensure unauthorized withdraws are disallowed by writing the following tes
import ape
import pytest
+
@pytest.fixture
def owner(accounts):
return accounts[0]
+
@pytest.fixture
def hacker(accounts):
return accounts[1]
+
@pytest.fixture
def contract(owner, project):
return owner.deploy(project.MyContract)
+
def test_unauthorized_withdraw(contract, hacker):
with ape.reverts(contract.Unauthorized, unauth_address=hacker.address):
contract.withdraw(sender=hacker)
@@ -557,6 +575,7 @@ You can also use custom error types from the contract container (from `ape.proje
```python
import ape
+
def test_unauthorized(contract, hacker, project):
with ape.reverts(project.MyContract.Unauthorized, unauth_address=hacker.address):
contract.withdraw(sender=hacker)
@@ -568,6 +587,7 @@ Here is an example of what that may look like:
```python
import ape
+
def test_error_on_deploy(account, project):
with ape.reverts(project.Token.MyCustomError):
ape.project.HasError.deploy(sender=account)
@@ -582,14 +602,15 @@ The address will only exist for transactions that were published (e.g. not for f
```python
import ape
+
def test_error_on_deploy(account):
# NOTE: We are using `as rev` here to capture the revert info
# so we can attempt to lookup the contract later.
with ape.reverts() as rev:
ape.project.HasError.deploy(sender=account)
-
+
assert rev.value.address is not None, "Receipt never found, contract never cached"
-
+
# Grab the cached instance using the error's address
# and assert the custom error this way.
contract = ape.Contract(rev.value.address)
@@ -605,10 +626,12 @@ To run an entire test using a specific network / provider combination, use the `
```python
import pytest
+
@pytest.mark.use_network("fantom:local:test")
def test_my_fantom_test(chain):
assert chain.provider.network.ecosystem.name == "fantom"
+
@pytest.mark.use_network("ethereum:local:test")
def test_my_ethereum_test(chain):
assert chain.provider.network.ecosystem.name == "ethereum"
@@ -627,7 +650,7 @@ def test_my_multichain_test(networks):
# You can also use the context manager like this:
with networks.parse_network_choice("fantom:local:test") as provider:
- assert provider.network.ecosystem.name == "fantom"
+ assert provider.network.ecosystem.name == "fantom"
```
You can also set the network context in a pytest fixture.
@@ -636,11 +659,13 @@ This is useful if certain fixtures must run in certain networks.
```python
import pytest
+
@pytest.fixture
def stark_contract(networks, project):
with networks.parse_network_choice("starknet:local"):
yield project.MyStarknetContract.deploy()
+
def test_starknet_thing(stark_contract, stark_account):
# Uses the starknet connection via the stark_contract fixture
receipt = stark_contract.my_method(sender=stark_account)
diff --git a/docs/userguides/trace.md b/docs/userguides/trace.md
index df0c402ee0..50b52b1617 100644
--- a/docs/userguides/trace.md
+++ b/docs/userguides/trace.md
@@ -26,7 +26,9 @@ To learn more about networks in Ape, see the [networks guide](./networks.html).
```python
from ape import chain
-tx = chain.provider.get_receipt('0xb7d7f1d5ce7743e821d3026647df486f517946ef1342a1ae93c96e4a8016eab7')
+tx = chain.provider.get_receipt(
+ "0xb7d7f1d5ce7743e821d3026647df486f517946ef1342a1ae93c96e4a8016eab7"
+)
# Show the steps the transaction took.
tx.show_trace()
diff --git a/docs/userguides/transactions.md b/docs/userguides/transactions.md
index 800b14bd80..28e106f4d7 100644
--- a/docs/userguides/transactions.md
+++ b/docs/userguides/transactions.md
@@ -47,6 +47,7 @@ The following example demonstrates a simple deployment script:
```python
from ape import accounts, project
+
def deploy():
account = accounts.load("MyAccount")
# Assume you have a contract named `MyContract` in your project's contracts folder.
@@ -64,9 +65,9 @@ ape console --network ethereum:sepolia:alchemy
This will launch an IPython shell:
```python
-In [1]: dev = accounts.load("dev")
-In [2]: token = dev.deploy(project.Token)
-In [3]: token.contract_method_defined_in_contract()
+In[1]: dev = accounts.load("dev")
+In[2]: token = dev.deploy(project.Token)
+In[3]: token.contract_method_defined_in_contract()
```
For an in depth tutorial on how to deploy, please visit [ApeAcademy](https://academy.apeworx.io/).
@@ -104,14 +105,18 @@ Calling certain methods on a deployed-contract is one way to transact.
```python
contract = deploy() # Example from above, that returns a contract instance.
-contract.fundMyContract(value="1 gwei", sender=sender) # Assuming there is a method named 'fundMyContract' on MyContract.
+contract.fundMyContract(
+ value="1 gwei", sender=sender
+) # Assuming there is a method named 'fundMyContract' on MyContract.
```
In the example above, the call to `fundMyContract()` invokes a dynamic-fee transaction.
To have more control of the fee-values, you can specify the `max_fee`, the `max_priority_fee`, or both.
```python
-contract.fundMyContract(value="1 gwei", max_priority_fee="50 gwei", max_fee="100 gwei", sender=sender)
+contract.fundMyContract(
+ value="1 gwei", max_priority_fee="50 gwei", max_fee="100 gwei", sender=sender
+)
```
The `max_priority_fee` cannot exceed the `max_fee`, as the `max_fee` includes both the base fee and the priority fee.
diff --git a/pyproject.toml b/pyproject.toml
index 3c33fd8674..64fa338216 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -127,21 +127,21 @@ test = [ # `test` GitHub Action jobs use this
"ape-solidity>=0.8.5,<0.9", # Needed for compiling test contracts
]
style = [ # `linting` GitHub Action job uses this
- "ruff>=0.12.0", # Unified linter and formatter
- "mdformat>=0.7.22", # 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
+ "ruff>=0.15", # Unified linter and formatter
+ "mdformat>=1.0", # Auto-formatter for markdown
+ "mdformat-gfm", # Needed for formatting GitHub-flavored markdown
+ "mdformat-frontmatter", # Needed for frontmatters-style headers in issue templates
+ "mdformat-pyproject", # Allows configuring in pyproject.toml
]
lint = [ # `type-check` GitHub Action job uses this
{include-group = "style"},
- "mypy>=1.16.1,<1.17.0", # Static type analyzer
+ "mypy>=2.3,<3", # Static type analyzer
"types-PyYAML", # Needed due to mypy typeshed
"types-requests", # Needed due to mypy typeshed
"types-setuptools", # Needed due to mypy typeshed
- "pandas-stubs>=2.2.1.240316", # Needed due to mypy typeshed
+ "pandas-stubs", # Needed due to mypy typeshed
"types-toml", # Needed due to mypy typeshed
- "types-SQLAlchemy>=1.4.49", # Needed due to mypy typeshed
+ "types-SQLAlchemy", # Needed due to mypy typeshed
"types-python-dateutil", # Needed due to mypy typeshed
]
docs = [ # `docs` GitHub Action jobs use this
diff --git a/src/ape/_cli.py b/src/ape/_cli.py
index c2efb35466..8a5c3b9406 100644
--- a/src/ape/_cli.py
+++ b/src/ape/_cli.py
@@ -64,7 +64,7 @@ def format_commands(self, ctx, formatter) -> None:
# NOTE: Avoid issue with empty sections (now Plugins installed)
continue
- with formatter.section(gettext(f"{section_name} Commands")):
+ with formatter.section(gettext(f"{section_name} Commands")): # noqa: INT001
formatter.write_dl(deflist)
def invoke(self, ctx) -> Any:
@@ -97,10 +97,9 @@ def invoke(self, ctx) -> Any:
raise Abort.from_ape_exception(err) from err
def _suggest_cmd(self, ctx, usage_error):
- if usage_error.message is None:
- raise usage_error
-
- elif not (match := re.match("No such command '(.*)'.", usage_error.message)):
+ if usage_error.message is None or not (
+ match := re.match("No such command '(.*)'.", usage_error.message)
+ ):
raise usage_error
groups = match.groups()
@@ -111,11 +110,10 @@ def _suggest_cmd(self, ctx, usage_error):
suggested_commands = difflib.get_close_matches(
bad_arg, self.list_commands(ctx), cutoff=_DIFFLIB_CUT_OFF
)
- if suggested_commands:
- if bad_arg not in suggested_commands:
- usage_error.message = (
- f"No such command '{bad_arg}'. Did you mean {' or '.join(suggested_commands)}?"
- )
+ if suggested_commands and bad_arg not in suggested_commands:
+ usage_error.message = (
+ f"No such command '{bad_arg}'. Did you mean {' or '.join(suggested_commands)}?"
+ )
raise usage_error
@@ -139,13 +137,13 @@ def get_command(self, ctx, name) -> click.Command | None:
try:
return ep.load()
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.warn_from_exception(err, f"Unable to load CLI endpoint for plugin 'ape_{name}'")
# NOTE: don't return anything so Click displays proper error
return None
-@click.command(cls=ApeCLI, context_settings=dict(help_option_names=["-h", "--help"]))
+@click.command(cls=ApeCLI, context_settings={"help_option_names": ["-h", "--help"]})
@ape_cli_context()
@click.version_option(message="%(version)s", package_name="eth-ape")
@click.option(
diff --git a/src/ape/api/accounts.py b/src/ape/api/accounts.py
index d7df62f45b..8aef97f4c0 100644
--- a/src/ape/api/accounts.py
+++ b/src/ape/api/accounts.py
@@ -381,7 +381,7 @@ def deploy(
self.local_project.deployments.track(instance)
try:
self.provider.network.publish_contract(address)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.error(f"Contract was deployed but explorer verification failed: {err}")
instance.base_path = contract.base_path or self.local_project.path
diff --git a/src/ape/api/address.py b/src/ape/api/address.py
index d29694ba27..5ace06571e 100644
--- a/src/ape/api/address.py
+++ b/src/ape/api/address.py
@@ -209,10 +209,9 @@ def as_transaction(self, **kwargs) -> "TransactionAPI":
tx = self.provider.network.ecosystem.create_transaction(
receiver=self.address, **converted_kwargs
)
- if sender := kwargs.get("sender"):
- if hasattr(sender, "prepare_transaction"):
- prepared = sender.prepare_transaction(tx)
- return (sender.sign_transaction(prepared) or prepared) if sign else prepared
+ if (sender := kwargs.get("sender")) and hasattr(sender, "prepare_transaction"):
+ prepared = sender.prepare_transaction(tx)
+ return (sender.sign_transaction(prepared) or prepared) if sign else prepared
return tx
diff --git a/src/ape/api/compiler.py b/src/ape/api/compiler.py
index dcbd6a31c8..328a8e72cd 100644
--- a/src/ape/api/compiler.py
+++ b/src/ape/api/compiler.py
@@ -4,6 +4,8 @@
from pathlib import Path
from typing import TYPE_CHECKING
+from pydantic import Field
+
from ape.exceptions import APINotImplementedError, ContractLogicError
from ape.utils.basemodel import BaseInterfaceModel
from ape.utils.misc import log_instead_of_fail, raises_not_implemented
@@ -32,7 +34,7 @@ class CompilerAPI(BaseInterfaceModel):
this API.
"""
- compiler_settings: dict = {}
+ compiler_settings: dict = Field(default_factory=dict)
"""
Adhoc compiler settings.
"""
@@ -195,7 +197,7 @@ def supports_source_tracing(self) -> bool:
self.trace_source(None, None, None) # type: ignore
except APINotImplementedError:
return False
- except Exception:
+ except Exception: # noqa: BLE001
# Task failed successfully.
return True
diff --git a/src/ape/api/config.py b/src/ape/api/config.py
index 940b4a3af4..e9e58a3f03 100644
--- a/src/ape/api/config.py
+++ b/src/ape/api/config.py
@@ -113,7 +113,7 @@ def _find_plugin_config_problems(
if plugin_name in raw_global_data:
try:
cls.model_validate(raw_global_data[plugin_name])
- except Exception:
+ except Exception: # noqa: BLE001
if problems := cls._find_plugin_config_problems_from_file(
err, ape.config_manager.DATA_FOLDER
):
@@ -425,7 +425,7 @@ def deployments(self) -> dict[str, dict[str, list[DeploymentConfig]]]:
raise ConfigError(f"Invalid ecosystem '{ecosystem_name}' in deployments config.")
ecosystem = self.network_manager.ecosystems[ecosystem_name]
- for network_name, network_deploys in ecosystem_deploys.items():
+ for network_name in ecosystem_deploys:
if network_name not in ecosystem.networks:
raise ConfigError(
f"Invalid network '{ecosystem_name}:{network_name}' in deployments config."
diff --git a/src/ape/api/networks.py b/src/ape/api/networks.py
index dcb0c78767..e9caed2d41 100644
--- a/src/ape/api/networks.py
+++ b/src/ape/api/networks.py
@@ -8,7 +8,7 @@
from eth_pydantic_types import HexBytes
from eth_utils import keccak
from evmchains import PUBLIC_CHAIN_META
-from pydantic import model_validator
+from pydantic import Field, model_validator
from ape.exceptions import (
CustomError,
@@ -77,8 +77,8 @@ def _validate_type_name(cls, model):
# Not sure.
try:
model["type_name"] = f"{_type}"
- except Exception:
- pass
+ except Exception as err: # noqa: BLE001
+ logger.debug(str(err))
return model
@@ -110,7 +110,7 @@ class EcosystemAPI(ExtraAttributesMixin, BaseInterfaceModel):
# TODO: In 0.9, make @property that returns value from config,
# and use REQUEST_HEADER as plugin-defined constants.
- request_header: dict = {}
+ request_header: dict = Field(default_factory=dict)
"""A shareable HTTP header for network requests."""
fee_token_symbol: str
@@ -280,7 +280,7 @@ def _networks_from_evmchains(self) -> dict[str, "NetworkAPI"]:
if network_name not in self._networks_from_plugins
}
forked_networks: dict[str, ForkedNetworkAPI] = {}
- for network_name, network in networks.items():
+ for network_name in networks:
if network_name.endswith("-fork"):
# Already a fork.
continue
@@ -392,10 +392,9 @@ def default_network_name(self) -> str:
return network
networks = self.networks
- if network := self.config.get("default_network"):
+ if (network := self.config.get("default_network")) in networks:
# Default found in config. Ensure is an installed network.
- if network in networks:
- return network
+ return network
if LOCAL_NETWORK_NAME in networks:
# Default to the LOCAL_NETWORK_NAME, at last resort.
@@ -751,9 +750,10 @@ class ProviderContextManager(ManagerAccessMixin):
...
"""
- connected_providers: dict[str, "ProviderAPI"] = {}
- provider_stack: list[str] = []
- disconnect_map: dict[str, bool] = {}
+ # NOTE: pydantic v2 allows mutable defaults
+ connected_providers: ClassVar[dict[str, "ProviderAPI"]] = {}
+ provider_stack: ClassVar[list[str]] = []
+ disconnect_map: ClassVar[dict[str, bool]] = {}
# We store a provider object at the class level for use when disconnecting
# due to an exception, when interactive mode is set. If we don't hold on
@@ -861,7 +861,7 @@ def disconnect_all(self):
provider.disconnect()
self.network_manager.active_provider = None
- self.connected_providers = {}
+ ProviderContextManager.connected_providers = {}
def _connect_provider(provider: "ProviderAPI") -> "ProviderAPI":
@@ -888,7 +888,7 @@ class NetworkAPI(BaseInterfaceModel):
# TODO: In 0.9, make @property that returns value from config,
# and use REQUEST_HEADER as plugin-defined constants.
- request_header: dict = {}
+ request_header: dict = Field(default_factory=dict)
"""A shareable network HTTP header."""
# See ``.default_provider`` which is the proper field.
@@ -908,11 +908,11 @@ def __repr__(self) -> str:
f"{self.choice} chain_id={self.chain_id}" if chain_id is not None else self.choice
)
return f"<{content}>"
- except Exception:
+ except Exception: # noqa: BLE001
# Don't allow repr to fail.
try:
name = self.name
- except Exception:
+ except Exception: # noqa: BLE001
name = None
return f"<{name}>" if name else f"{type(self)}"
@@ -1176,7 +1176,7 @@ def _get_plugin_providers(self):
def _get_plugin_provider_names(self) -> Iterator[str]:
for _, plugin_tuple in self._get_plugin_providers():
- ecosystem_name, network_name, provider_class = plugin_tuple
+ _ecosystem_name, _network_name, provider_class = plugin_tuple
yield provider_class.__module__.split(".")[0].replace("_", "-").replace("ape-", "")
def get_provider(
@@ -1309,7 +1309,7 @@ def default_provider_name(self) -> str | None:
elif len(self.providers) > 0:
# No default set anywhere - use the first installed.
- return list(self.providers)[0]
+ return next(iter(self.providers))
# There are no providers at all for this network.
return None
diff --git a/src/ape/api/providers.py b/src/ape/api/providers.py
index 042eeb07a5..2aca92c9ab 100644
--- a/src/ape/api/providers.py
+++ b/src/ape/api/providers.py
@@ -256,12 +256,12 @@ class ProviderAPI(BaseInterfaceModel):
network: NetworkAPI
"""A reference to the network this provider provides."""
- provider_settings: dict = {}
+ provider_settings: dict = Field(default_factory=dict)
"""The settings for the provider, as overrides to the configuration."""
# TODO: In 0.9, make @property that returns value from config,
# and use REQUEST_HEADER as plugin-defined constants.
- request_header: dict = {}
+ request_header: dict = Field(default_factory=dict)
"""A header to set on HTTP/RPC requests."""
block_page_size: int = 100
@@ -383,7 +383,7 @@ def connection_id(self) -> str | None:
try:
chain_id = self.chain_id
- except Exception:
+ except Exception: # noqa: BLE001
if chain_id := self.settings.get("chain_id"):
pass
@@ -1176,7 +1176,7 @@ def _disconnect_atexit(self):
try:
self.disconnect()
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.error(f"Error while disconnecting: {err}")
def disconnect(self):
diff --git a/src/ape/api/transactions.py b/src/ape/api/transactions.py
index 45f91f8979..bcdc66b8a1 100644
--- a/src/ape/api/transactions.py
+++ b/src/ape/api/transactions.py
@@ -4,7 +4,7 @@
from collections.abc import Iterator
from datetime import datetime as datetime_type
from functools import cached_property
-from typing import IO, TYPE_CHECKING, Any, NoReturn
+from typing import IO, TYPE_CHECKING, Any
from eth_pydantic_types import HexBytes, HexStr
from eth_utils import humanize_hexstr, is_hex, to_hex, to_int
@@ -329,7 +329,7 @@ class ReceiptAPI(ExtraAttributesMixin, BaseInterfaceModel):
contract_address: AddressType | None = None
block_number: HexInt
gas_used: HexInt
- logs: list[dict] = []
+ logs: list[dict] = Field(default_factory=list)
status: HexInt
txn_hash: HexStr
transaction: TransactionAPI
@@ -483,7 +483,7 @@ def decode_logs(
list[:class:`~ape.types.ContractLog`]
"""
- def raise_for_status(self) -> NoReturn | None:
+ def raise_for_status(self) -> None:
"""
Handle provider-specific errors regarding a non-successful
:class:`~api.providers.TransactionStatusEnum`.
diff --git a/src/ape/cli/choices.py b/src/ape/cli/choices.py
index d031ea3529..2ad51e7602 100644
--- a/src/ape/cli/choices.py
+++ b/src/ape/cli/choices.py
@@ -133,7 +133,7 @@ def convert(self, value: Any, param: Parameter | None, ctx: Context | None) -> A
return self.choices[choice_index]
- except Exception:
+ except Exception: # noqa: BLE001
return self.fail_from_invalid_choice(param)
def fail_from_invalid_choice(self, param):
diff --git a/src/ape/cli/options.py b/src/ape/cli/options.py
index be5618b4e6..c501622601 100644
--- a/src/ape/cli/options.py
+++ b/src/ape/cli/options.py
@@ -270,7 +270,7 @@ def callback(ctx, param, value):
keep_as_choice_str = param.type.base_type is str
try:
provider_obj = _get_provider(value, default, keep_as_choice_str)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
raise click.BadOptionUsage("--network", str(err), ctx)
if provider_obj:
diff --git a/src/ape/contracts/base.py b/src/ape/contracts/base.py
index f28f3ec9af..5a7be464f1 100644
--- a/src/ape/contracts/base.py
+++ b/src/ape/contracts/base.py
@@ -269,7 +269,7 @@ def decode_input(self, calldata: bytes) -> tuple[str, dict[str, Any]]:
decoded_calldata = self.provider.network.ecosystem.decode_calldata(
abi, HexBytes(calldata)
)
- except Exception:
+ except Exception: # noqa: BLE001, S112
continue
if decoded_calldata:
@@ -423,10 +423,9 @@ def as_transaction(self, *args, **kwargs) -> "TransactionAPI":
transaction = contract_transaction.serialize_transaction(*args, **kwargs)
self.provider.prepare_transaction(transaction)
- if sender := kwargs.get("sender"):
- if isinstance(sender, AccountAPI):
- prepped_tx = sender.prepare_transaction(transaction)
- return (sender.sign_transaction(prepped_tx) or prepped_tx) if sign else prepped_tx
+ if (sender := kwargs.get("sender")) and isinstance(sender, AccountAPI):
+ prepped_tx = sender.prepare_transaction(transaction)
+ return (sender.sign_transaction(prepped_tx) or prepped_tx) if sign else prepped_tx
return transaction
@@ -799,7 +798,7 @@ def range(
contract = None
try:
contract = self.chain_manager.contracts.instance_at(contract_address)
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
# Determine the start block from contract creation metadata
@@ -1196,7 +1195,7 @@ def address(self) -> "AddressType":
@cached_property
def _view_methods_(self) -> dict[str, ContractCallHandler]:
- view_methods: dict[str, list[MethodABI]] = dict()
+ view_methods: dict[str, list[MethodABI]] = {}
for abi in self.contract_type.view_methods:
if abi.name in view_methods:
@@ -1215,7 +1214,7 @@ def _view_methods_(self) -> dict[str, ContractCallHandler]:
@cached_property
def _mutable_methods_(self) -> dict[str, ContractTransactionHandler]:
- mutable_methods: dict[str, list[MethodABI]] = dict()
+ mutable_methods: dict[str, list[MethodABI]] = {}
for abi in self.contract_type.mutable_methods:
if abi.name in mutable_methods:
@@ -1348,9 +1347,8 @@ def get_error_by_signature(self, signature: str) -> type[CustomError]:
raise err
for contract_err in options:
- if abi := getattr(contract_err, "abi", None):
- if abi.signature == signature:
- return contract_err
+ if (abi := getattr(contract_err, "abi", None)) and abi.signature == signature:
+ return contract_err
raise err
@@ -1703,7 +1701,7 @@ def deploy(self, *args, publish: bool = False, **kwargs) -> ContractInstance:
self.local_project.deployments.track(instance)
try:
self.provider.network.publish_contract(address)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.error(f"Contract was deployed but explorer verification failed: {err}")
instance.base_path = self.base_path or self.local_project.contracts_folder
@@ -1726,7 +1724,7 @@ def _cache_wrap(self, function: Callable) -> "ReceiptAPI":
try:
# Try enrichment again now that the contract type is cached.
new_err = self.compiler_manager.enrich_error(err)
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
if new_err:
diff --git a/src/ape/exceptions.py b/src/ape/exceptions.py
index d143a11200..8b794ef502 100644
--- a/src/ape/exceptions.py
+++ b/src/ape/exceptions.py
@@ -233,10 +233,10 @@ def address(self) -> "AddressType | None":
return addr
receiver = getattr(self.txn, "receiver", None)
- if receiver in (None, "0x0000000000000000000000000000000000000000"):
- # Check if deploy
- if addr := getattr(self.txn, "contract_address", None):
- return addr
+ if receiver in (None, "0x0000000000000000000000000000000000000000") and (
+ addr := getattr(self.txn, "contract_address", None)
+ ):
+ return addr
return receiver
@@ -289,11 +289,10 @@ def source_traceback(self, value):
self._source_traceback = value
def _get_ape_traceback(self) -> TracebackType | None:
- if src_tb := self.source_traceback:
- # Create a custom Pythonic traceback using lines from the sources
- # found from analyzing the trace of the transaction.
- if py_tb := _get_custom_python_traceback(self, src_tb, project=self._project):
- return py_tb
+ if (src_tb := self.source_traceback) and (
+ py_tb := _get_custom_python_traceback(self, src_tb, project=self._project)
+ ):
+ return py_tb
return None
@@ -340,7 +339,7 @@ def __init__(
try:
# Attempt to use dev message as main exception message.
self.message = dev
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
@property
@@ -351,7 +350,7 @@ def revert_message(self):
def revert_message(self, value):
self.message = value
if args := self.args:
- self.args = tuple([value, *args[1:]])
+ self.args = (value, *args[1:])
@property
def dev_message(self) -> str | None:
@@ -936,7 +935,7 @@ def _get_ape_traceback_from_tx(txn: FailedTxn) -> "SourceTraceback | None":
try:
receipt: ReceiptAPI = txn if isinstance(txn, ReceiptAPI) else txn.receipt # type: ignore
- except Exception:
+ except Exception: # noqa: BLE001
# Receipt not real enough, maybe was a re-played call.
return None
@@ -972,7 +971,7 @@ def _get_custom_python_traceback(
# TODO: Add support for manifest-projects.
return None
- _, exc_value, tb = sys.exc_info()
+ _, _exc_value, tb = sys.exc_info()
depth = None
idx = len(ape_traceback) - 1
frames = []
@@ -1004,7 +1003,7 @@ def _get_custom_python_traceback(
if exec_item.source_path is None:
# File is not local. Create a temporary file in its place.
# This is necessary for tracebacks to work in Python.
- temp_file = tempfile.NamedTemporaryFile(prefix="unknown_contract_")
+ temp_file = tempfile.NamedTemporaryFile(prefix="unknown_contract_") # noqa: SIM115
filename = temp_file.name
else:
filename = str(exec_item.source_path)
@@ -1017,8 +1016,8 @@ def _get_custom_python_traceback(
# Execute the new code to get a new (fake) tb with contract source info.
try:
- exec(py_code, {"__ape_exception__": err}, {})
- except BaseException:
+ exec(py_code, {"__ape_exception__": err}, {}) # noqa: S102
+ except BaseException: # noqa: BLE001
real_tb = sys.exc_info()[2]
fake_tb = getattr(real_tb, "tb_next", None)
if isinstance(fake_tb, TracebackType):
diff --git a/src/ape/logging.py b/src/ape/logging.py
index f15a3f6992..8f1bc236fb 100644
--- a/src/ape/logging.py
+++ b/src/ape/logging.py
@@ -6,7 +6,7 @@
from contextlib import contextmanager
from enum import IntEnum
from pathlib import Path
-from typing import IO, TYPE_CHECKING, Any
+from typing import IO, TYPE_CHECKING, Any, ClassVar
from urllib.parse import urlparse, urlunparse
import click
@@ -43,18 +43,18 @@ def success(self, message, *args, **kws):
CLICK_STYLE_KWARGS = {
- LogLevel.ERROR: dict(fg="bright_red"),
- LogLevel.WARNING: dict(fg="bright_yellow"),
- LogLevel.SUCCESS: dict(fg="bright_green"),
- LogLevel.INFO: dict(fg="blue"),
- LogLevel.DEBUG: dict(fg="blue"),
+ LogLevel.ERROR: {"fg": "bright_red"},
+ LogLevel.WARNING: {"fg": "bright_yellow"},
+ LogLevel.SUCCESS: {"fg": "bright_green"},
+ LogLevel.INFO: {"fg": "blue"},
+ LogLevel.DEBUG: {"fg": "blue"},
}
CLICK_ECHO_KWARGS = {
- LogLevel.ERROR: dict(err=True),
- LogLevel.WARNING: dict(err=True),
- LogLevel.SUCCESS: dict(),
- LogLevel.INFO: dict(),
- LogLevel.DEBUG: dict(),
+ LogLevel.ERROR: {"err": True},
+ LogLevel.WARNING: {"err": True},
+ LogLevel.SUCCESS: {},
+ LogLevel.INFO: {},
+ LogLevel.DEBUG: {},
}
@@ -65,7 +65,7 @@ def _isatty(stream: IO) -> bool:
# noinspection PyBroadException
try:
return stream.isatty()
- except Exception:
+ except Exception: # noqa: BLE001
return False
@@ -113,13 +113,13 @@ def emit(self, record):
click.echo(msg, **self.echo_kwargs[level])
else:
click.echo(msg)
- except Exception:
+ except Exception: # noqa: BLE001
self.handleError(record)
class ApeLogger:
_mentioned_verbosity_option = False
- _extra_loggers: dict[str, logging.Logger] = {}
+ _extra_loggers: ClassVar[dict[str, logging.Logger]] = {}
DISABLE_LEVEL: int = 100_000
def __init__(
@@ -166,7 +166,7 @@ def _load_from_sys_argv(self, default: str | int | LogLevel | None = None):
if sys.argv[arg_i] == "-v" or sys.argv[arg_i] == "--verbosity":
try:
level = _get_level(sys.argv[arg_i + 1].upper())
- except Exception:
+ except Exception: # noqa: BLE001, S112
# Let it fail in a better spot, or is not our level.
continue
@@ -346,7 +346,7 @@ def sanitize_url(url: str) -> str:
class _RichConsoleFactory:
- rich_console_map: dict[str, "RichConsole"] = {}
+ rich_console_map: ClassVar[dict[str, "RichConsole"]] = {}
def get_console(self, file: IO[str] | None = None, **kwargs) -> "RichConsole":
# Configure custom file console
diff --git a/src/ape/managers/_contractscache.py b/src/ape/managers/_contractscache.py
index 7c64737a20..5594f607a2 100644
--- a/src/ape/managers/_contractscache.py
+++ b/src/ape/managers/_contractscache.py
@@ -4,7 +4,7 @@
from contextlib import contextmanager
from functools import cached_property
from pathlib import Path
-from typing import TYPE_CHECKING, Generic, TypeVar
+from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar
from ethpm_types import ABI, ContractType
from ethpm_types.contract_type import ABIList
@@ -81,13 +81,12 @@ def get_type(self, key: str, fetch_from_disk: bool = True) -> _BASE_MODEL | None
if model := self.memory.get(key):
return model
- elif fetch_from_disk and self._read_from_disk:
- if data := self.get_data(key):
- # Found on disk.
- model = self._model_type.model_validate(data)
- # Cache locally for next time.
- self.memory[key] = model
- return model
+ elif fetch_from_disk and self._read_from_disk and (data := self.get_data(key)):
+ # Found on disk.
+ model = self._model_type.model_validate(data)
+ # Cache locally for next time.
+ self.memory[key] = model
+ return model
return None
@@ -104,11 +103,11 @@ class ContractCache(BaseManager):
"""
# ecosystem_name -> network_name -> cache_name -> cache
- _caches: dict[str, dict[str, dict[str, ApeDataCache]]] = {}
+ _caches: ClassVar[dict[str, dict[str, dict[str, ApeDataCache]]]] = {}
# chain_id -> address -> custom_err
# Cached to prevent calling `new_class` multiple times with conflicts.
- _custom_error_types: dict[int, dict[AddressType, set[type[CustomError]]]] = {}
+ _custom_error_types: ClassVar[dict[int, dict[AddressType, set[type[CustomError]]]]] = {}
@property
def contract_types(self) -> ApeDataCache[ContractType]:
@@ -256,11 +255,11 @@ def use_temporary_caches(self):
Useful for testing.
"""
caches = self._caches
- self._caches = {}
+ ContractCache._caches = {}
with self.deployments.use_temporary_cache():
yield
- self._caches = caches
+ ContractCache._caches = caches
def _delete_proxy(self, address: AddressType):
if info := self.proxy_infos[address]:
@@ -583,24 +582,30 @@ def get(
# Either no cached entry, or `replace=True` so we ignore the cache.
# Check broader sources, such as an explorer.
if not proxy_info and detect_proxy:
- # Proxy info not provided. Attempt to detect.
- if not (proxy_info := self.proxy_infos[address_key]):
- if proxy_info := self.provider.network.ecosystem.get_proxy_info(address_key):
- self.proxy_infos[address_key] = proxy_info
-
- if proxy_info:
- if proxy_contract_type := self._get_proxy_contract_type(
+ proxy_info = self.proxy_infos[address_key]
+
+ if (
+ not proxy_info
+ and detect_proxy
+ and (detected_proxy_info := self.provider.network.ecosystem.get_proxy_info(address_key))
+ ):
+ proxy_info = detected_proxy_info
+ self.proxy_infos[address_key] = proxy_info
+
+ if proxy_info and (
+ proxy_contract_type := self._get_proxy_contract_type(
address_key,
proxy_info,
fetch_from_explorer=fetch_from_explorer,
default=default,
replace=replace,
- ):
- # `proxy_contract_type` is one of the following:
- # 1. A ContractType with the combined proxy and implementation ABIs
- # 2. Implementation-only ABI ContractType (like forwarder proxies)
- # 3. Proxy only ABI (e.g. unverified implementation ContractType)
- return proxy_contract_type
+ )
+ ):
+ # `proxy_contract_type` is one of the following:
+ # 1. A ContractType with the combined proxy and implementation ABIs
+ # 2. Implementation-only ABI ContractType (like forwarder proxies)
+ # 3. Proxy only ABI (e.g. unverified implementation ContractType)
+ return proxy_contract_type
contract_type = None
# Also gets cached to disk for faster lookup next time.
@@ -675,9 +680,8 @@ def _get_contract_type(
Get the _exact_ ContractType for a given address. For proxy contracts, returns
the proxy ABIs if there are any and not the implementation ABIs.
"""
- if not replace:
- if contract_type := self.contract_types[address]:
- return contract_type
+ if not replace and (contract_type := self.contract_types[address]):
+ return contract_type
if fetch_from_explorer:
return self._get_contract_type_from_explorer(address)
@@ -751,7 +755,7 @@ def instance_at(
prefix = f"Expected type '{ContractType.__name__}' for argument 'contract_type'"
try:
suffix = f"; Given '{type(contract_type).__name__}'."
- except Exception:
+ except Exception: # noqa: BLE001
suffix = "."
raise TypeError(f"{prefix}{suffix}")
@@ -927,7 +931,7 @@ def _get_contract_type_from_explorer(self, address: AddressType) -> ContractType
try:
contract_type = self.provider.network.explorer.get_contract_type(address)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
explorer_name = self.provider.network.explorer.name
if "rate limit" in str(err).lower():
# Don't show any additional error message during rate limit errors,
diff --git a/src/ape/managers/_deploymentscache.py b/src/ape/managers/_deploymentscache.py
index e31ff7a00e..1efcbe0a0a 100644
--- a/src/ape/managers/_deploymentscache.py
+++ b/src/ape/managers/_deploymentscache.py
@@ -1,6 +1,8 @@
from contextlib import contextmanager
from pathlib import Path
+from pydantic import Field
+
from ape.managers.base import BaseManager
from ape.types.address import AddressType
from ape.utils.basemodel import BaseModel, DiskCacheableModel
@@ -35,7 +37,7 @@ def get(self, key: str):
class Deployments(DiskCacheableModel):
"""The deployments structured JSON."""
- ecosystems: dict[str, dict[str, dict[str, list[Deployment]]]] = {}
+ ecosystems: dict[str, dict[str, dict[str, list[Deployment]]]] = Field(default_factory=dict)
class DeploymentDiskCache(BaseManager):
diff --git a/src/ape/managers/accounts.py b/src/ape/managers/accounts.py
index e997d4c425..bbe8b77de6 100644
--- a/src/ape/managers/accounts.py
+++ b/src/ape/managers/accounts.py
@@ -2,7 +2,7 @@
from collections.abc import Generator, Iterator
from contextlib import AbstractContextManager as ContextManager
from functools import cached_property, singledispatchmethod
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, ClassVar
from eth_utils import is_hex
@@ -39,8 +39,11 @@ def _use_sender(
class TestAccountManager(list, ManagerAccessMixin):
__test__ = False
- _impersonated_accounts: dict[AddressType, ImpersonatedAccount] = {}
- _accounts_by_index: dict[int, AccountAPI] = {}
+ _impersonated_accounts: ClassVar[dict[AddressType, ImpersonatedAccount]] = {}
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._accounts_by_index: dict[int, AccountAPI] = {}
@log_instead_of_fail(default="")
def __repr__(self) -> str:
@@ -233,7 +236,7 @@ class AccountManager(BaseManager):
my_accounts = accounts.load("dev")
"""
- _alias_to_account_cache: dict[str, AccountAPI] = {}
+ _alias_to_account_cache: ClassVar[dict[str, AccountAPI]] = {}
@property
def default_sender(self) -> AccountAPI | None:
diff --git a/src/ape/managers/chain.py b/src/ape/managers/chain.py
index 614dacd8f0..e1ec9b3d2d 100644
--- a/src/ape/managers/chain.py
+++ b/src/ape/managers/chain.py
@@ -4,9 +4,10 @@
from datetime import datetime
from functools import cached_property, partial, singledispatchmethod
from statistics import mean, median
-from typing import IO, TYPE_CHECKING, cast
+from typing import IO, TYPE_CHECKING, ClassVar, cast
import pandas as pd
+from pydantic import Field
from rich.box import SIMPLE
from rich.table import Table
@@ -178,7 +179,7 @@ def query(
columns, self.head.__class__
)
extraction = partial(extract_fields, columns=columns)
- data = map(lambda b: extraction(b), blocks)
+ data = (extraction(b) for b in blocks)
return pd.DataFrame(columns=columns, data=data)
def range(
@@ -288,13 +289,11 @@ def poll_blocks(
raise ValueError("'stop' argument must be in the future.")
# Get number of last block with the necessary amount of confirmations.
- block = None
head_minus_confirms = self.height - required_confirmations
if start_block is not None and start_block <= head_minus_confirms:
# Front-load historical blocks.
- for block in self.range(start_block, head_minus_confirms + 1):
- yield block
+ yield from self.range(start_block, head_minus_confirms + 1)
yield from self.provider.poll_blocks(
stop_block=stop_block,
@@ -313,7 +312,7 @@ class AccountHistory(BaseInterfaceModel):
The address to get history for.
"""
- sessional: list[ReceiptAPI] = []
+ sessional: list[ReceiptAPI] = Field(default_factory=list)
"""
The receipts from the current Python session.
"""
@@ -402,7 +401,7 @@ def query(
txns = self.query_manager.query(query, engine_to_use=engine_to_use)
columns = validate_and_expand_columns(columns, ReceiptAPI) # type: ignore
extraction = partial(extract_fields, columns=columns)
- data = map(lambda tx: extraction(tx), txns)
+ data = (extraction(tx) for tx in txns)
return pd.DataFrame(columns=columns, data=data)
def __iter__(self) -> Iterator[ReceiptAPI]: # type: ignore[override]
@@ -505,8 +504,10 @@ class TransactionHistory(BaseManager):
A container mapping Transaction History to the transaction from the active session.
"""
- _account_history_cache: dict[AddressType, AccountHistory] = {}
- _hash_to_receipt_map: dict[str, ReceiptAPI] = {}
+ _account_history_cache: ClassVar[dict[AddressType, AccountHistory]] = {}
+
+ def __init__(self):
+ self._hash_to_receipt_map: dict[str, ReceiptAPI] = {}
@singledispatchmethod
def __getitem__(self, key):
@@ -533,7 +534,7 @@ def __getitem_str(self, account_or_hash: str) -> AccountHistory | ReceiptAPI:
def _get_receipt() -> ReceiptAPI | None:
try:
return self._get_receipt(account_or_hash)
- except Exception:
+ except Exception: # noqa: BLE001
return None
is_account = False
@@ -541,7 +542,7 @@ def _get_receipt() -> ReceiptAPI | None:
# Attempt converting.
try:
account_or_hash = self.conversion_manager.convert(account_or_hash, AddressType)
- except Exception:
+ except Exception: # noqa: BLE001, S110
# Pretend this never happened.
pass
else:
@@ -661,8 +662,8 @@ def show_gas(self, report: "GasReport", file: IO[str] | None = None):
f"{len(gases)}",
f"{min(gases)}",
f"{max(gases)}",
- f"{int(round(mean(gases)))}",
- f"{int(round(median(gases)))}",
+ f"{round(mean(gases))}",
+ f"{round(median(gases))}",
)
if has_at_least_1_row:
@@ -737,12 +738,12 @@ class ChainManager(BaseManager):
from ape import chain
"""
- _snapshots: defaultdict = defaultdict(list) # chain_id -> snapshots
+ _snapshots: ClassVar[defaultdict] = defaultdict(list) # chain_id -> snapshots
_chain_id_cache: ChainIdCache = ChainIdCache()
- _block_container_map: dict[int, BlockContainer] = {}
- _transaction_history_map: dict[int, TransactionHistory] = {}
+ _block_container_map: ClassVar[dict[int, BlockContainer]] = {}
+ _transaction_history_map: ClassVar[dict[int, TransactionHistory]] = {}
_reports: ReportManager = ReportManager()
- _code: dict[str, dict[str, dict[AddressType, "ContractCode"]]] = {}
+ _code: ClassVar[dict[str, dict[str, dict[AddressType, "ContractCode"]]]] = {}
@cached_property
def contracts(self) -> ContractCache:
@@ -960,7 +961,7 @@ def isolate(self):
try:
yield
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass # NOTE: Handle cleanup after any exceptions in yielded context
if snapshot is None:
diff --git a/src/ape/managers/compilers.py b/src/ape/managers/compilers.py
index a7ab147ad4..90c25aad38 100644
--- a/src/ape/managers/compilers.py
+++ b/src/ape/managers/compilers.py
@@ -2,7 +2,7 @@
from collections.abc import Iterable, Iterator, Sequence
from functools import cached_property
from pathlib import Path
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, ClassVar
from eth_pydantic_types import HexBytes
@@ -40,7 +40,7 @@ class CompilerManager(BaseManager, ExtraAttributesMixin):
from ape import compilers # "compilers" is the CompilerManager singleton
"""
- _registered_compilers_cache: dict[Path, dict[str, "CompilerAPI"]] = {}
+ _registered_compilers_cache: ClassVar[dict[Path, dict[str, "CompilerAPI"]]] = {}
@log_instead_of_fail(default="")
def __repr__(self) -> str:
@@ -155,7 +155,7 @@ def compile(
yield contract
- except Exception as err:
+ except Exception as err: # noqa: BLE001
# One of the compilers failed. Show the error but carry on.
logger.log_debug_stack_trace()
errors.append(err)
@@ -314,9 +314,7 @@ def get_custom_error(self, err: ContractLogicError) -> CustomError | None:
:class:`~ape.exceptions.CustomError` | None
"""
message = err.revert_message
- if not message.startswith("0x"):
- return None
- elif not (address := err.address):
+ if not message.startswith("0x") or not (address := err.address):
return None
if provider := self.network_manager.active_provider:
diff --git a/src/ape/managers/config.py b/src/ape/managers/config.py
index a274d73a91..ba3b46bc4c 100644
--- a/src/ape/managers/config.py
+++ b/src/ape/managers/config.py
@@ -151,7 +151,7 @@ def isolate_data_folder(self, keep: Iterable[str] | str | None = None) -> Iterat
dest_path = temp_data_folder / item
try:
shutil.copytree(path_to_keep, dest_path)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.warning(
f"Problem copying '{dest_path.name}' when making isolated project: {err}"
)
diff --git a/src/ape/managers/converters.py b/src/ape/managers/converters.py
index 467d122a27..d4b12c1021 100644
--- a/src/ape/managers/converters.py
+++ b/src/ape/managers/converters.py
@@ -12,6 +12,7 @@
from eth_pydantic_types import Address, HexBytes
from eth_typing.evm import ChecksumAddress
from eth_utils import is_0x_prefixed, is_checksum_address, is_hex, is_hex_address, to_int
+from pydantic import PrivateAttr
from ape.api.address import BaseAddress
from ape.api.convert import ConverterAPI, ConvertibleAPI
@@ -34,11 +35,7 @@ class HexConverter(ConverterAPI):
"""
def is_convertible(self, value: Any) -> bool:
- return (
- (isinstance(value, str) and is_hex(value))
- or isinstance(value, bytes)
- or isinstance(value, int)
- )
+ return (isinstance(value, str) and is_hex(value)) or isinstance(value, (bytes, int))
def convert(self, value: str) -> bytes:
"""
@@ -163,7 +160,7 @@ class IntAddressConverter(ConverterAPI):
A converter that converts an integer address to an :class:`~ape.types.address.AddressType`.
"""
- _cache: dict[int, AddressType | bool] = {}
+ _cache: dict[int, AddressType | bool] = PrivateAttr(default_factory=dict)
def is_convertible(self, value: Any) -> bool:
if not isinstance(value, int):
@@ -196,7 +193,7 @@ def convert(self, value: Any) -> AddressType:
def _convert(self, value: int) -> AddressType | bool:
try:
val = Address.__eth_pydantic_validate__(value)
- except Exception:
+ except Exception: # noqa: BLE001
return False
return AddressType(to_checksum_address(val))
@@ -382,7 +379,7 @@ def _convert_using_converter_apis(self, value: Any, to_type: type) -> Any:
for converter in self._converters[to_type]:
try:
is_convertible = converter.is_convertible(value)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
# If errors while checking if we can convert, log the error
# and assume it's not convertible.
converter_name = converter.__class__.__name__
@@ -398,7 +395,7 @@ def _convert_using_converter_apis(self, value: Any, to_type: type) -> Any:
except Exception as err:
try:
error_value = f" '{value}' (type={type(value)}) "
- except Exception:
+ except Exception: # noqa: BLE001
error_value = " "
message = f"Failed to convert{error_value}"
@@ -527,7 +524,7 @@ def _get_type_name_from_type(var_type: type) -> str:
message = "Unable to deduce type name"
try:
str_value = f"{var_type}"
- except Exception:
+ except Exception: # noqa: BLE001
str_value = ""
if str_value:
diff --git a/src/ape/managers/networks.py b/src/ape/managers/networks.py
index 1906bc0388..1d609a8eef 100644
--- a/src/ape/managers/networks.py
+++ b/src/ape/managers/networks.py
@@ -3,10 +3,10 @@
from collections.abc import Collection, Iterator
from functools import cached_property
from pathlib import Path
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, ClassVar
from evmchains import PUBLIC_CHAIN_META
-from pydantic import ValidationError
+from pydantic import Field, ValidationError
from ape.api.networks import ProviderContextManager
from ape.exceptions import EcosystemNotFoundError, NetworkError, NetworkNotFoundError
@@ -81,7 +81,7 @@ class NodeProcessMap(DiskCacheableModel):
All managed running network subprocesses.
"""
- nodes: dict[int, NodeProcessData] = {}
+ nodes: dict[int, NodeProcessData] = Field(default_factory=dict)
@property
def path(self) -> Path:
@@ -182,7 +182,7 @@ class NetworkManager(BaseManager, ExtraAttributesMixin):
# For adhoc adding custom networks, or incorporating some defined
# in other projects' configs.
- _custom_networks: list[dict] = []
+ _custom_networks: ClassVar[list[dict]] = []
@log_instead_of_fail(default="")
def __repr__(self) -> str:
@@ -316,7 +316,7 @@ def kill_node_process(self, *process_ids: int) -> dict[int, NodeProcessData]:
try:
provider = self.get_running_node(pid)
- except Exception:
+ except Exception: # noqa: BLE001, S110
# Still try to kill the process (below).
pass
else:
@@ -325,7 +325,7 @@ def kill_node_process(self, *process_ids: int) -> dict[int, NodeProcessData]:
try:
os.kill(pid, signal.SIGTERM)
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
else:
pids_killed[pid] = data
@@ -441,12 +441,12 @@ def provider_names(self) -> set[str]:
The set of all provider names in ``ape``.
"""
- return set(
+ return {
provider
for ecosystem in self.ecosystems.values()
for network in ecosystem.networks.values()
for provider in network.providers
- )
+ }
@property
def custom_networks(self) -> list[dict]:
@@ -587,7 +587,7 @@ def create_custom_provider(
name = provider_name
provider_settings: dict = {}
- if connection_str.startswith("https://") or connection_str.startswith("http://"):
+ if connection_str.startswith(("https://", "http://")):
provider_settings["uri"] = connection_str
elif connection_str.endswith(".ipc"):
provider_settings["ipc_path"] = connection_str
@@ -992,7 +992,7 @@ def _invalidate_cache(self):
# NOTE: Called when changing config programmatically.
self.__dict__.pop("_custom_ecosystems", None)
self.__dict__.pop("_custom_networks_from_config", None)
- self._custom_networks = []
+ NetworkManager._custom_networks = []
def _validate_filter(arg: list[str] | str | None, options: set[str]):
@@ -1010,9 +1010,7 @@ def _validate_filter(arg: list[str] | str | None, options: set[str]):
def _is_adhoc_url(value: str) -> bool:
return (
- value.startswith("http://")
- or value.startswith("https://")
- or value.startswith("ws://")
- or value.startswith("wss://")
- or (value.endswith(".ipc") and ":" not in value)
+ value.startswith(("http://", "https://", "ws://", "wss://"))
+ or value.endswith(".ipc")
+ and ":" not in value
)
diff --git a/src/ape/managers/plugins.py b/src/ape/managers/plugins.py
index e9d2ed9aa1..b705d97bc8 100644
--- a/src/ape/managers/plugins.py
+++ b/src/ape/managers/plugins.py
@@ -2,7 +2,7 @@
from functools import cached_property
from importlib import import_module
from itertools import chain
-from typing import Any
+from typing import Any, ClassVar
from ape.exceptions import ApeAttributeError
from ape.logging import logger
@@ -75,7 +75,7 @@ def _get_unimplemented_methods(api) -> Iterable[str]:
class PluginManager:
- _unimplemented_plugins: list[str] = []
+ _unimplemented_plugins: ClassVar[list[str]] = []
def __init__(self) -> None:
self.__registered = False
diff --git a/src/ape/managers/project.py b/src/ape/managers/project.py
index f9bcab489a..e9c0907878 100644
--- a/src/ape/managers/project.py
+++ b/src/ape/managers/project.py
@@ -6,7 +6,7 @@
from functools import cached_property, singledispatchmethod
from pathlib import Path
from re import Pattern
-from typing import Any, cast
+from typing import Any, ClassVar, cast
from eth_typing import HexStr
from eth_utils import to_hex
@@ -14,6 +14,7 @@
from ethpm_types import ContractType, PackageManifest, PackageMeta, Source
from ethpm_types.source import Compiler, ContractSource
from ethpm_types.utils import compute_checksum
+from pydantic import Field
from pydantic_core import Url
from ape.api.projects import ApeProject, DependencyAPI, ProjectAPI
@@ -66,8 +67,6 @@ class SourceManager(BaseManager):
_path_cache: list[Path] | None = None
# perf: calculating paths from source Ids can be expensive.
- _path_to_source_id: dict[Path, str] = {}
-
def __init__(
self,
root_path: Path,
@@ -77,6 +76,7 @@ def __init__(
self.root_path = root_path
self.get_contracts_path = get_contracts_path
self.exclude_globs = exclude_globs or set()
+ self._path_to_source_id: dict[Path, str] = {}
self._sources: dict[str, Source] = {}
self._exclude_cache: dict[str, bool] = {}
@@ -125,7 +125,7 @@ def get(self, source_id: str) -> Source | None:
if path.is_file():
try:
text = path.read_text(encoding="utf8")
- except Exception:
+ except Exception: # noqa: BLE001, S112
continue
else:
@@ -397,11 +397,11 @@ def __iter__(self) -> Iterator[str]:
self._compile_missing_contracts(self.sources.paths)
if contract_types := self.project.manifest.contract_types:
for ct in contract_types.values():
- if not ct.name or not ct.source_id:
- continue
-
- # Ensure was not deleted.
- elif not (self.project.path / ct.source_id).is_file():
+ if (
+ not ct.name
+ or not ct.source_id
+ or not (self.project.path / ct.source_id).is_file()
+ ):
continue
yield ct.name
@@ -561,7 +561,6 @@ def _detect_change(self, path: Path | str) -> bool:
source_id: str
if isinstance(path, Path):
- path = path
source_id = self.sources._get_source_id(path)
else:
source_id = str(path) # str wrap for mypy
@@ -628,7 +627,7 @@ def __getattr__(self, name: str) -> Any:
def __ape_extra_attributes__(self) -> Iterator[ExtraModelAttributes]:
yield ExtraModelAttributes(name="project", attributes=lambda: self.project)
- def __eq__(self, other: Any) -> bool:
+ def __eq__(self, other: object) -> bool:
if not isinstance(other, Dependency):
# We can't handle this type.
# This line causes python to retry from the other end.
@@ -724,11 +723,9 @@ def installed(self) -> bool:
# Fails when version ID errors out (bad config / missing required install etc.)
return False
- if project_path.is_dir():
- if any(x for x in self.project_path.iterdir() if not x.name.startswith(".")):
- return True
-
- return False
+ return project_path.is_dir() and any(
+ x for x in self.project_path.iterdir() if not x.name.startswith(".")
+ )
@property
def compiled(self) -> bool:
@@ -803,7 +800,7 @@ def install(
logger.info(f"Installing {self.clean_package_id} {self.api.version_id}")
try:
self.api.fetch(self.project_path)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
raise ProjectError(f"Fetching failed: {err}")
did_fetch = True
@@ -829,7 +826,7 @@ def install(
manifest = PackageManifest.model_validate_json(
path.read_text(encoding="utf8")
)
- except Exception:
+ except Exception: # noqa: BLE001, S110
# False alarm.
pass
else:
@@ -984,8 +981,7 @@ def _unpack(self, path: Path, tracked: set[str]) -> Iterator["Dependency"]:
# Unpack dependencies of dependencies (if they aren't already).
for dependency in self.project.dependencies.specified:
- for unpacked_dep in dependency._unpack(path, tracked=tracked):
- yield unpacked_dep
+ yield from dependency._unpack(path, tracked=tracked)
def _get_cache_versions_suffix(package_id) -> Path:
@@ -1205,7 +1201,7 @@ class DependencyManager(BaseManager):
"""
# Class-level cache
- _cache: dict[DependencyAPI, Dependency] = {}
+ _cache: ClassVar[dict[DependencyAPI, Dependency]] = {}
def __init__(self, project: "ProjectManager | None" = None):
self.project = project or self.local_project
@@ -1326,7 +1322,7 @@ def get_project_dependencies(
for api in self.config_apis:
try:
api_version_id = api.version_id
- except Exception:
+ except Exception: # noqa: BLE001
api_version_id = None
if (name is not None and api.name != name and api.package_id != name) or (
@@ -1435,11 +1431,10 @@ def get_dependency_api(self, package_id: str, version: str | None = None) -> Dep
:class:`~ape.api.projects.DependencyAPI`
"""
# Check by package ID first.
- if dependency := self._get_dependency_api_by_package_id(package_id, version=version):
- return dependency
-
- elif dependency := self._get_dependency_api_by_package_id(
- package_id, version=version, attr="name"
+ if (dependency := self._get_dependency_api_by_package_id(package_id, version=version)) or (
+ dependency := self._get_dependency_api_by_package_id(
+ package_id, version=version, attr="name"
+ )
):
return dependency
@@ -1471,7 +1466,7 @@ def _get_dependency_api_by_package_id(
# else: prioritize the local dependencies, as that is most-likely what the user wants.
- return sorted(matching, key=lambda d: d.version_id)[-1] if matching else None
+ return max(matching, key=lambda d: d.version_id) if matching else None
def _get(
self,
@@ -1492,11 +1487,10 @@ def _get(
elif dependency.name == name and dependency.version == version:
name_matches.append(dependency)
- if name_matches:
- if len(name_matches) == 1:
- # Return match-by-name after full loop in case was checking by
- # package ID, which is more precise.
- return name_matches[0]
+ if name_matches and len(name_matches) == 1:
+ # Return match-by-name after full loop in case was checking by
+ # package ID, which is more precise.
+ return name_matches[0]
if name_matches:
# If one of the matches is in the `.specified` dependencies, use that one.
@@ -1574,10 +1568,7 @@ def get_versions(self, name: str, allow_install: bool = True) -> Iterator[Depend
# Never yield. Check if using short-name.
for dependency in self.all:
- if dependency.name != name:
- continue
-
- elif dependency.version in versions_yielded:
+ if dependency.name != name or dependency.version in versions_yielded:
continue
yield dependency
@@ -1675,7 +1666,7 @@ def decode_dependency(self, **item: Any) -> DependencyAPI:
name = item.get("name") or f"{item}" # NOTE: Using 'or' for short-circuit eval
raise ProjectError(
f"No installed dependency API that supports '{name}'. "
- f"Keys={', '.join([x for x in item.keys()])}"
+ f"Keys={', '.join([x for x in item])}"
)
def add(self, dependency: dict | DependencyAPI) -> Dependency:
@@ -1730,14 +1721,9 @@ def install(self, **dependency: Any) -> Dependency | list[Dependency]:
return self.install_dependency(dependency, use_cache=use_cache, recurse=recurse)
# Install all project's.
- result: list[Dependency] = []
-
- for dep in self.get_project_dependencies(
- use_cache=use_cache, allow_install=True, recurse=recurse
- ):
- result.append(dep)
-
- return result
+ return list(
+ self.get_project_dependencies(use_cache=use_cache, allow_install=True, recurse=recurse)
+ )
def install_dependency(
self,
@@ -1898,19 +1884,14 @@ def __contains__(self, item):
@property
def name(self) -> str:
- if name := self.config.get("name"):
- return name
- elif name := self.manifest.name:
+ if (name := self.config.get("name")) or (name := self.manifest.name):
return name
return f"unknown-project-{random.randint(100_000, 999_999)}"
@property
def version(self) -> str:
- if version := self._config_override.get("version"):
- return version
-
- elif version := self.manifest.version:
+ if (version := self._config_override.get("version")) or (version := self.manifest.version):
return version
else:
@@ -2114,7 +2095,7 @@ def add_compiler_data(self, compiler_data: Iterable[Compiler]) -> list[Compiler]
# Use Compiler.__hash__ to remove duplicated.
# Also, sort for consistency.
compilers = sorted(
- list({*remaining_existing_compilers, *compiler_data}),
+ {*remaining_existing_compilers, *compiler_data},
key=lambda x: f"{x.name}@{x.version}",
)
self.update_manifest(compilers=compilers)
@@ -2353,7 +2334,7 @@ class MultiProject(ProjectAPI):
containing an ``ape-config.yaml`` file.
"""
- apis: list[ProjectAPI] = []
+ apis: list[ProjectAPI] = Field(default_factory=list)
"""
An ordered list of APIs to use. The last items take precedence as their configs merge.
"""
@@ -2425,7 +2406,7 @@ def __init__(
try:
src_dict = dict(self.sources.items())
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.error(str(err))
else:
if src_dict and not self.manifest.sources:
@@ -2480,9 +2461,10 @@ def __getattr__(self, item: str) -> Any:
# Possibly, the user does not have compiler plugins installed or working.
missing_exts = set()
for path in all_files:
- if ext := get_full_extension(path):
- if ext not in self.compiler_manager.registered_compilers:
- missing_exts.add(ext)
+ if (ext := get_full_extension(path)) and (
+ ext not in self.compiler_manager.registered_compilers
+ ):
+ missing_exts.add(ext)
if missing_exts:
start = "Else, could" if did_append else "Could"
@@ -2527,9 +2509,7 @@ def _deduce_contracts_folder(self) -> Path:
if (self.path / name).is_dir():
return self.path / name
- exts_not_json = {
- k for k in self.compiler_manager.registered_compilers.keys() if k != ".json"
- }
+ exts_not_json = {k for k in self.compiler_manager.registered_compilers if k != ".json"}
if not exts_not_json:
# Not really able to look anywhere else.
return self.path
@@ -2604,9 +2584,7 @@ def _get_ape_project_api(self) -> ApeProject | None:
@property
def name(self) -> str:
- if name := self.config.get("name"):
- return name
- elif name := self.manifest.name:
+ if (name := self.config.get("name")) or (name := self.manifest.name):
return name
return self._base_path.name.replace("_", "-").lower()
@@ -2733,9 +2711,8 @@ def unpack(self, destination: Path, config_override: dict | None = None) -> "Loc
def copytree(src, dst):
try:
shutil.copytree(src, dst, dirs_exist_ok=True)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.error(f"Failed to unpack '{src}' to '{dst}': {err}")
- pass
# Unpack contracts.
if self.contracts_folder.is_dir():
@@ -2785,7 +2762,7 @@ def load_manifest(self) -> PackageManifest:
try:
manifest = _load_manifest(self.manifest_path)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.error(f"__local__.json manifest corrupted! Re-building.\nFull error: {err}.")
self.manifest_path.unlink(missing_ok=True)
manifest = PackageManifest()
@@ -2972,7 +2949,7 @@ def _create_contract_source(self, contract_type: ContractType) -> ContractSource
# Not found in this project's sources.
try:
cwd = Path.cwd()
- except Exception:
+ except Exception: # noqa: BLE001
# Happens when left in a cleaned-up temp path maybe?
cwd = None
diff --git a/src/ape/managers/query.py b/src/ape/managers/query.py
index f25ccbc88e..1992d863a1 100644
--- a/src/ape/managers/query.py
+++ b/src/ape/managers/query.py
@@ -162,7 +162,7 @@ def query(
else:
# Get heuristics from all the query engines to perform this query
- estimates = map(lambda qe: (qe, qe.estimate_query(query)), self.engines.values())
+ estimates = ((qe, qe.estimate_query(query)) for qe in self.engines.values())
# Ignore query engines that can't perform this query
valid_estimates = filter(lambda qe: qe[1] is not None, estimates)
diff --git a/src/ape/plugins/_utils.py b/src/ape/plugins/_utils.py
index 69317b3199..80bea61cef 100644
--- a/src/ape/plugins/_utils.py
+++ b/src/ape/plugins/_utils.py
@@ -10,7 +10,7 @@
import click
from packaging.specifiers import SpecifierSet
from packaging.version import Version
-from pydantic import field_validator, model_validator
+from pydantic import Field, field_validator, model_validator
from ape.exceptions import PluginVersionError
from ape.logging import logger
@@ -658,7 +658,7 @@ class PluginGroup(BaseModel):
"""
plugin_type: PluginType
- plugins: dict[str, PluginMetadata] = {}
+ plugins: dict[str, PluginMetadata] = Field(default_factory=dict)
def __bool__(self) -> bool:
return len(self.plugins) > 0
diff --git a/src/ape/pytest/contextmanagers.py b/src/ape/pytest/contextmanagers.py
index 15bb818ef1..3fa3a77bd1 100644
--- a/src/ape/pytest/contextmanagers.py
+++ b/src/ape/pytest/contextmanagers.py
@@ -51,9 +51,8 @@ def _check_dev_message(self, exception: ContractLogicError):
err_message = "Could not find the source of the revert."
# Attempt to show source traceback so the user can see the real failure.
- if (info := self.revert_info) and (ex := info.value):
- if tb := ex.source_traceback:
- err_message = f"{err_message}\n{tb}"
+ if (info := self.revert_info) and (ex := info.value) and (tb := ex.source_traceback):
+ err_message = f"{err_message}\n{tb}"
raise AssertionError(err_message)
@@ -124,7 +123,7 @@ def _check_custom_error(self, exception: CustomError):
# the expected contract instance (e.g. from the same address).
# If not address is being compared, this check is skipped.
- raise AssertionError(
+ raise AssertionError( # noqa: TRY004
f"Expected error '{expected_error_cls.__name__}' "
f"but was '{type(exception).__name__}'"
)
@@ -155,12 +154,17 @@ def __enter__(self, *args, **kwargs):
self.revert_info = info
return info
- def __exit__(self, exc_type: type, exc_value: Exception, traceback) -> bool:
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc_value: BaseException | None,
+ traceback,
+ ) -> bool:
if exc_type is None:
raise AssertionError("Transaction did not revert.")
if not isinstance(exc_value, ContractLogicError):
- raise AssertionError(
+ raise AssertionError( # noqa: TRY004
f"Transaction did not revert.\n"
f"However, an exception of type {type(exc_value)} occurred: {exc_value}."
) from exc_value
diff --git a/src/ape/pytest/fixtures.py b/src/ape/pytest/fixtures.py
index 6d06a39f8c..590a583246 100644
--- a/src/ape/pytest/fixtures.py
+++ b/src/ape/pytest/fixtures.py
@@ -393,10 +393,7 @@ def is_iterating(self, name: str) -> bool:
True when is a non-function scoped parametrized fixture that hasn't
fully iterated.
"""
- if name not in self.parametrized:
- return False
-
- elif not (info_ls := self.get_info(name)):
+ if name not in self.parametrized or not (info_ls := self.get_info(name)):
return False
for info in info_ls:
@@ -613,7 +610,7 @@ def set_snapshot(self, scope: Scope):
try:
snapshot_id = self.take_snapshot()
- except Exception:
+ except Exception: # noqa: BLE001
self.supported = False
else:
if snapshot_id is not None:
@@ -662,7 +659,7 @@ def restore_snapshot(self, scope: Scope):
)
# To avoid trying again
self.supported = False
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.error(f"Unhandled error with restoring snapshot: {err}")
self.snapshots.clear_snapshot_id(scope)
@@ -672,11 +669,10 @@ def _restore(self, snapshot_id: "SnapshotID"):
class ReceiptCapture(ManagerAccessMixin):
- receipt_map: dict[str, dict[str, "ReceiptAPI"]] = {}
- enter_blocks: list[int] = []
-
def __init__(self, config_wrapper: "ConfigWrapper"):
self.config_wrapper = config_wrapper
+ self.receipt_map: dict[str, dict[str, ReceiptAPI]] = {}
+ self.enter_blocks: list[int] = []
def __enter__(self):
block_number = self._get_block_number()
@@ -701,7 +697,7 @@ def capture_range(self, start_block: int, stop_block: int):
for txn in transactions:
try:
txn_hash = to_hex(txn.txn_hash)
- except Exception:
+ except Exception: # noqa: BLE001, S112
# Might have been from an impersonated account.
# Those txns need to be added separately, same as tracing calls.
# Likely, it was already accounted before this point.
@@ -715,10 +711,7 @@ def capture(self, transaction_hash: str):
except ChainError:
return
- if not receipt:
- return
-
- elif not (contract_address := (receipt.receiver or receipt.contract_address)):
+ if not receipt or not (contract_address := (receipt.receiver or receipt.contract_address)):
return
elif not (contract_type := self.chain_manager.contracts.get(contract_address)):
diff --git a/src/ape/pytest/plugin.py b/src/ape/pytest/plugin.py
index 1df52ab4f7..7767969fd6 100644
--- a/src/ape/pytest/plugin.py
+++ b/src/ape/pytest/plugin.py
@@ -54,20 +54,19 @@ def add_option(*names, **kwargs):
def pytest_configure(config):
# Do not include ape internals in tracebacks unless explicitly asked
- if not config.getoption("--show-internal"):
- if path_str := sys.modules["ape"].__file__:
- base_path = str(Path(path_str).parent)
-
- def is_module(v):
- return getattr(v, "__file__", None) and v.__file__.startswith(base_path)
-
- for module in (v for v in sys.modules.values() if is_module(v)):
- # NOTE: Using try/except w/ type:ignore (over checking for attr)
- # for performance reasons!
- try:
- module.__tracebackhide__ = True # type: ignore[attr-defined]
- except AttributeError:
- pass
+ if not config.getoption("--show-internal") and (path_str := sys.modules["ape"].__file__):
+ base_path = str(Path(path_str).parent)
+
+ def is_module(v):
+ return getattr(v, "__file__", None) and v.__file__.startswith(base_path)
+
+ for module in (v for v in sys.modules.values() if is_module(v)):
+ # NOTE: Using try/except w/ type:ignore (over checking for attr)
+ # for performance reasons!
+ try:
+ module.__tracebackhide__ = True # type: ignore[attr-defined]
+ except AttributeError:
+ pass
if "--help" in config.invocation_params.args:
# perf: Don't bother setting up runner if only showing help.
diff --git a/src/ape/pytest/runners.py b/src/ape/pytest/runners.py
index 7e5f3e0796..f1fe5cc171 100644
--- a/src/ape/pytest/runners.py
+++ b/src/ape/pytest/runners.py
@@ -250,7 +250,7 @@ def pytest_fixture_setup(self, fixturedef, request):
if self._track_fixture_blocks(fixture_name):
try:
block_number = self.chain_manager.blocks.height
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
else:
self.fixture_manager.add_fixture_info(fixture_name, setup_block=block_number)
@@ -274,10 +274,7 @@ def _track_fixture_blocks(self, fixture_name: str) -> bool:
return False
scope = self.fixture_manager.get_fixture_scope(fixture_name)
- if scope in (None, Scope.FUNCTION):
- return False
-
- return True
+ return scope not in (None, Scope.FUNCTION)
@pytest.hookimpl(trylast=True, hookwrapper=True)
def pytest_collection_finish(self, session):
@@ -363,7 +360,7 @@ def pytest_unconfigure(self):
if self._provider_is_connected and self.config_wrapper.disconnect_providers_after:
try:
self._provider_context.disconnect_all()
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.error(f"Failed to disconnect {self}: {err}")
else:
self._provider_is_connected = False
diff --git a/src/ape/types/coverage.py b/src/ape/types/coverage.py
index fafe6d702d..4307cffc1f 100644
--- a/src/ape/types/coverage.py
+++ b/src/ape/types/coverage.py
@@ -1,5 +1,5 @@
import itertools
-from datetime import datetime
+from datetime import datetime, timezone
from html.parser import HTMLParser
from pathlib import Path
from typing import TYPE_CHECKING, Any
@@ -8,7 +8,7 @@
import requests
from ethpm_types.source import SourceLocation
-from pydantic import NonNegativeInt, field_validator
+from pydantic import Field, NonNegativeInt, field_validator
from ape.logging import logger
from ape.utils.basemodel import BaseModel
@@ -169,7 +169,7 @@ class FunctionCoverage(BaseModel):
The unique name of the function.
"""
- statements: list[CoverageStatement] = []
+ statements: list[CoverageStatement] = Field(default_factory=list)
"""
For statement coverage, these are the individual items.
See :class:`~ape.types.coverage.CoverageStatement` for more details.
@@ -277,7 +277,7 @@ class ContractCoverage(BaseModel):
The name of the contract.
"""
- functions: list[FunctionCoverage] = []
+ functions: list[FunctionCoverage] = Field(default_factory=list)
"""
The coverage of each function individually.
"""
@@ -377,7 +377,7 @@ class ContractSourceCoverage(BaseModel):
The ID of the source covered.
"""
- contracts: list[ContractCoverage] = []
+ contracts: list[ContractCoverage] = Field(default_factory=list)
"""
Coverage for each contract in the source file.
"""
@@ -475,7 +475,7 @@ class CoverageProject(BaseModel):
The name of the project being covered.
"""
- sources: list[ContractSourceCoverage] = []
+ sources: list[ContractSourceCoverage] = Field(default_factory=list)
"""
Coverage for each source in the project.
"""
@@ -574,7 +574,7 @@ class CoverageReport(BaseModel):
The timestamp the report was generated, in milliseconds.
"""
- projects: list[CoverageProject] = []
+ projects: list[CoverageProject] = Field(default_factory=list)
"""
Each project with individual coverage tracked.
"""
@@ -769,7 +769,7 @@ def _get_xml(self):
# Is first time coming across this name.
fn_map[function.name] = function
- for fn_name, function in fn_map.items():
+ for function in fn_map.values():
if not function.statements or not any(
s.location for s in function.statements
):
@@ -793,7 +793,7 @@ def _get_xml(self):
lines_to_add[lineno] = statement.hit_count
# NOTE: Line numbers must be sorted in the XML!
- sorted_nos = sorted(list(lines_to_add.keys()))
+ sorted_nos = sorted(lines_to_add.keys())
for no in sorted_nos:
hits = lines_to_add[no]
xline = xml_out.createElement("line")
@@ -861,7 +861,7 @@ def write_html(self, path: Path, verbose: bool = False):
response = requests.get(url)
response.raise_for_status() # Check for any errors during the request
favicon.write_bytes(response.content)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
# Don't let this stop us from generating the report.
logger.debug(f"Failed finding favicon for coverage HTML. {err}")
@@ -908,7 +908,7 @@ def _html_header_sub_element(self, html: Any) -> Any:
h1.text += f" for {self.projects[0].name}"
paragraph = SubElement(div, "p")
- datetime_obj = datetime.fromtimestamp(self.timestamp / 1000)
+ datetime_obj = datetime.fromtimestamp(self.timestamp / 1000, tz=timezone.utc)
datetime_string = datetime_obj.strftime("%Y-%m-%d %H:%M:%S")
paragraph.text = f"Generated by Ape Framework v{ape_version}, {datetime_string}"
diff --git a/src/ape/types/events.py b/src/ape/types/events.py
index be384b481c..052ed22486 100644
--- a/src/ape/types/events.py
+++ b/src/ape/types/events.py
@@ -5,7 +5,7 @@
from eth_pydantic_types import HexBytes, HexStr
from eth_utils import encode_hex, is_hex, keccak, to_hex
from ethpm_types.abi import EventABI
-from pydantic import BaseModel, field_serializer, field_validator, model_validator
+from pydantic import BaseModel, Field, field_serializer, field_validator, model_validator
from web3.types import FilterParams
from ape.exceptions import ContractNotFoundError
@@ -97,11 +97,17 @@ class BaseContractLog(BaseInterfaceModel):
contract_address: AddressType = ZERO_ADDRESS
"""The contract responsible for emitting the log."""
- event_arguments: dict[str, Any] = {}
+ event_arguments: dict[str, Any] = Field(default_factory=dict)
"""The arguments to the event, including both indexed and non-indexed data."""
- def __eq__(self, other: Any) -> bool:
- if self.contract_address != other.contract_address or self.event_name != other.event_name:
+ def __eq__(self, other: object) -> bool:
+ if (
+ not hasattr(other, "contract_address")
+ or not hasattr(other, "event_name")
+ or not hasattr(other, "event_arguments")
+ or self.contract_address != other.contract_address
+ or self.event_name != other.event_name
+ ):
return False
for k, v in self.event_arguments.items():
@@ -272,7 +278,7 @@ def __ape_extra_attributes__(self) -> Iterator[ExtraModelAttributes]:
def __contains__(self, item: str) -> bool:
return item in self.event_arguments
- def __eq__(self, other: Any) -> bool:
+ def __eq__(self, other: object) -> bool:
"""
Check for equality between this instance and another ContractLog instance.
@@ -325,10 +331,11 @@ class MockContractLog(BaseContractLog):
of event arguments between a MockContractLog and a ContractLog instance.
"""
- def __eq__(self, other: Any) -> bool:
+ def __eq__(self, other: object) -> bool:
if (
not hasattr(other, "contract_address")
or not hasattr(other, "event_name")
+ or not hasattr(other, "event_arguments")
or self.contract_address != other.contract_address
or self.event_name != other.event_name
):
diff --git a/src/ape/types/private_mempool.py b/src/ape/types/private_mempool.py
index 00ab9ef1b9..096b432a64 100644
--- a/src/ape/types/private_mempool.py
+++ b/src/ape/types/private_mempool.py
@@ -332,7 +332,7 @@ def decode_logs(self, *events: EventABI):
return ecosystem.decode_logs(list(self.transaction_logs), *events)
@property
- def transaction_logs(self, *events: EventABI) -> Iterator[dict]:
+ def transaction_logs(self) -> Iterator[dict]:
yield from _get_transaction_logs_from_sim_logs(self.logs or [])
diff --git a/src/ape/types/signatures.py b/src/ape/types/signatures.py
index 4f22c2c103..ae6645784f 100644
--- a/src/ape/types/signatures.py
+++ b/src/ape/types/signatures.py
@@ -45,19 +45,19 @@ def _bytes_to_human_str(bytes_value: bytes) -> str | None:
try:
# Try as text
return bytes_value.decode("utf8")
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
try:
# Try as hex
return to_hex(bytes_value)
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
try:
# Try normal str
return str(bytes_value)
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
return None
diff --git a/src/ape/types/trace.py b/src/ape/types/trace.py
index 517f13d4ae..27795e5286 100644
--- a/src/ape/types/trace.py
+++ b/src/ape/types/trace.py
@@ -340,13 +340,13 @@ def revert_type(self) -> str | None:
"""
return self.statements[-1].type if self.statements[-1].type != "source" else None
- def append(self, __object) -> None:
+ def append(self, __object, /) -> None:
"""
Append the given control flow to this one.
"""
self.root.append(__object)
- def extend(self, __iterable) -> None:
+ def extend(self, __iterable, /) -> None:
"""
Append all the control flows from the given traceback to this one.
"""
diff --git a/src/ape/types/units.py b/src/ape/types/units.py
index 18f1531a8c..f814d839c6 100644
--- a/src/ape/types/units.py
+++ b/src/ape/types/units.py
@@ -21,7 +21,7 @@ class CurrencyValueComparable(int):
strings, such as ``"1 ether"``.
"""
- def __eq__(self, other: Any) -> bool:
+ def __eq__(self, other: object) -> bool:
if isinstance(other, int):
return super().__eq__(other)
diff --git a/src/ape/utils/__init__.py b/src/ape/utils/__init__.py
index 6406a8ed07..b11cbc130c 100644
--- a/src/ape/utils/__init__.py
+++ b/src/ape/utils/__init__.py
@@ -186,11 +186,11 @@ def __getattr__(name: str):
"parse_coverage_tables",
"parse_gas_table",
"path_match",
+ "pragma_str_to_specifier_set",
"prettify_dict",
"prettify_function",
"prettify_inputs",
"prettify_list",
- "pragma_str_to_specifier_set",
"raises_not_implemented",
"request_with_retry",
"returns_array",
diff --git a/src/ape/utils/_github.py b/src/ape/utils/_github.py
index e5cd826f6c..6db231b1c0 100644
--- a/src/ape/utils/_github.py
+++ b/src/ape/utils/_github.py
@@ -6,7 +6,7 @@
from collections.abc import Iterator
from io import BytesIO
from pathlib import Path
-from typing import Any
+from typing import Any, ClassVar
from requests import HTTPError, Session
from requests.adapters import HTTPAdapter
@@ -44,7 +44,7 @@ def clone(self, url: str, target_path: Path | None = None, branch: str | None =
# Often times, `v` is required for tags.
try:
self.clone(url, target_path, branch=f"v{branch}")
- except Exception:
+ except Exception: # noqa: BLE001
raise ProjectError(fail_msg)
# Succeeded when prefixing `v`.
@@ -66,7 +66,7 @@ class _GithubClient:
# ApeWorX-specific attributes.
ORGANIZATION_NAME = "ApeWorX"
FRAMEWORK_NAME = "ape"
- _repo_cache: dict[str, dict] = {}
+ _repo_cache: ClassVar[dict[str, dict]] = {}
def __init__(self, session: Session | None = None):
if session:
@@ -75,7 +75,7 @@ def __init__(self, session: Session | None = None):
else:
headers = {"Content-Type": "application/json", "User-Agent": USER_AGENT}
- if auth := os.environ[self.TOKEN_KEY] if self.TOKEN_KEY in os.environ else None:
+ if auth := os.environ.get(self.TOKEN_KEY, None):
headers["Authorization"] = f"token {auth}"
session = Session()
@@ -120,7 +120,7 @@ def get_release(self, org_name: str, repo_name: str, version: str) -> dict:
def _try_get_release(vers):
try:
return self._get_release(org_name, repo_name, vers)
- except Exception:
+ except Exception: # noqa: BLE001
return None
if release := _try_get_release(version):
diff --git a/src/ape/utils/abi.py b/src/ape/utils/abi.py
index 28ed2829e5..50a9d36c10 100644
--- a/src/ape/utils/abi.py
+++ b/src/ape/utils/abi.py
@@ -493,7 +493,7 @@ def decode(self, topics: list[str], data: str | bytes, use_hex_on_fail: bool = F
# Try again with strict=False
try:
data_values = decode(data_abi_types, hex_data, strict=False)
- except Exception:
+ except Exception: # noqa: BLE001
# Even with strict=False, we failed to decode.
# This should be a rare occasion, if it ever happens.
logger.warn_from_exception(err, warning_message)
diff --git a/src/ape/utils/basemodel.py b/src/ape/utils/basemodel.py
index f8d70af199..2cc36d266b 100644
--- a/src/ape/utils/basemodel.py
+++ b/src/ape/utils/basemodel.py
@@ -103,7 +103,7 @@ def __get__(self, *args):
for argument in args:
try:
arg_str = str(argument)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.debug(f"Failed calling __str__. Exception: {err}")
arg_strs.append(">")
continue
@@ -346,7 +346,7 @@ class ExtraModelAttributes(EthpmTypesBaseModel):
def __repr__(self) -> str:
try:
return f""
- except Exception:
+ except Exception: # noqa: BLE001
# Disallow exceptions in __repr__
return ""
@@ -354,7 +354,7 @@ def __contains__(self, name: Any) -> bool:
attrs = self._attrs()
try:
name = str(name)
- except Exception:
+ except Exception: # noqa: BLE001
return False
if name in attrs or hasattr(attrs, name):
@@ -500,7 +500,7 @@ def get_attribute_with_extras(obj: Any, name: str, coerce_attr_error: bool = Tru
if not isinstance(obj, ExtraAttributesMixin):
name = getattr(type(obj), "__name__", "obj")
- raise AttributeError(f"{name} must use the '{ExtraAttributesMixin.__name__}' mixin'")
+ raise AttributeError(f"{name} must use the '{ExtraAttributesMixin.__name__}' mixin'") # noqa: TRY004
try:
res = super(ExtraAttributesMixin, obj).__getattribute__(name)
diff --git a/src/ape/utils/misc.py b/src/ape/utils/misc.py
index ddc7fef716..295fc0dd96 100644
--- a/src/ape/utils/misc.py
+++ b/src/ape/utils/misc.py
@@ -4,7 +4,7 @@
import json
import sys
-if sys.version_info.minor >= 11:
+if sys.version_info >= (3, 11):
# 3.11 or greater
# NOTE: type-ignore is for when running mypy on python versions < 3.11
import tomllib # type: ignore[import-not-found]
@@ -170,7 +170,7 @@ def get_package_version(obj: Any) -> str:
elif not isinstance(obj, str):
try:
str_value = f"{obj}"
- except Exception:
+ except Exception: # noqa: BLE001
str_value = ""
logger.warning(f"Type issue: Unknown if properly handled {str_value}")
@@ -223,9 +223,10 @@ def load_config(path: Path, expand_envars=True, must_exist=False) -> dict:
config = pyproject_toml.get("tool", {}).get("ape", {})
# Utilize [project] for some settings.
- if project_settings := pyproject_toml.get("project"):
- if "name" not in config and "name" in project_settings:
- config["name"] = project_settings["name"]
+ if (project_settings := pyproject_toml.get("project")) and (
+ "name" not in config and "name" in project_settings
+ ):
+ config["name"] = project_settings["name"]
elif path.suffix in (".json",):
config = json.loads(contents)
@@ -256,9 +257,7 @@ def gas_estimation_error_message(tx_error: Exception) -> str:
will likely revert.
"""
txn_error_text = str(tx_error)
- if txn_error_text.endswith("."):
- # Strip period from initial error so it looks better.
- txn_error_text = txn_error_text[:-1]
+ txn_error_text = txn_error_text.removesuffix(".")
return (
f"Gas estimation failed: '{txn_error_text}'. This transaction will likely revert. "
@@ -433,7 +432,7 @@ def is_evm_precompile(address: str) -> bool:
try:
address = address.replace("0x", "")
return 0 < sum(int(x) for x in address) < 10
- except Exception:
+ except Exception: # noqa: BLE001
return False
@@ -456,7 +455,7 @@ def is_zero_hex(address: str) -> bool:
# "0x" counts as zero.
return True
- except Exception:
+ except Exception: # noqa: BLE001
return False
@@ -465,7 +464,7 @@ def _dict_overlay(mapping: dict[str, Any], overlay: dict[str, Any], depth: int =
for key, value in overlay.items():
if isinstance(value, dict):
if key not in mapping:
- mapping[key] = dict()
+ mapping[key] = {}
_dict_overlay(mapping[key], value, depth + 1)
else:
mapping[key] = value
@@ -487,7 +486,7 @@ def wrapped(*args, **kwargs):
else:
return fn(*args, **kwargs)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.error(str(err))
if default:
return default
diff --git a/src/ape/utils/os.py b/src/ape/utils/os.py
index d075680f6c..a8a49064dc 100644
--- a/src/ape/utils/os.py
+++ b/src/ape/utils/os.py
@@ -263,10 +263,7 @@ def path_match(path: str | Path, *exclusions: str) -> bool:
path_path = Path(path)
for excl in exclusions:
- if fnmatch(path_str, excl):
- return True
-
- elif fnmatch(path_path.name, excl):
+ if fnmatch(path_str, excl) or fnmatch(path_path.name, excl):
return True
else:
diff --git a/src/ape/utils/process.py b/src/ape/utils/process.py
index 6b8f133e2b..87ff8d983e 100644
--- a/src/ape/utils/process.py
+++ b/src/ape/utils/process.py
@@ -19,10 +19,11 @@ def __iter__(self):
if isinstance(item, StopIteration) or is_stop_iteration_type:
return
- elif isinstance(item, Exception):
- raise item
-
- elif isinstance(item, type) and issubclass(item, Exception):
+ elif (
+ isinstance(item, Exception)
+ or isinstance(item, type)
+ and issubclass(item, Exception)
+ ):
raise item
yield item
diff --git a/src/ape/utils/trace.py b/src/ape/utils/trace.py
index 6506ff32cd..c51634b860 100644
--- a/src/ape/utils/trace.py
+++ b/src/ape/utils/trace.py
@@ -58,9 +58,12 @@ def prettify_function(
arguments_str = "()"
signature = f"{method}{arguments_str}"
- if not is_create and returndata not in ((), [], None, {}, ""):
- if return_str := _get_outputs_str(returndata, stylize=stylize, depth=depth):
- signature = f"{signature} -> {return_str}"
+ if (
+ not is_create
+ and returndata not in ((), [], None, {}, "")
+ and (return_str := _get_outputs_str(returndata, stylize=stylize, depth=depth))
+ ):
+ signature = f"{signature} -> {return_str}"
if contract:
signature = f"{contract}.{signature}"
@@ -136,7 +139,7 @@ def prettify_list(
# Use multi-line if exceeds threshold OR any of the sub-lists use multi-line
extra_chars_len = (len(sub_lists) - 1) * 2
use_multiline = len(str(sub_lists)) + extra_chars_len > wrap_threshold or any(
- ["\n" in ls for ls in sub_lists]
+ "\n" in ls for ls in sub_lists
)
if not use_multiline:
@@ -147,7 +150,7 @@ def prettify_list(
num_sub_lists = len(sub_lists)
index = 0
spacing = indent * " " * 2
- for formatted_list in sub_lists:
+ for index, formatted_list in enumerate(sub_lists):
if "\n" in formatted_list:
# Multi-line sub list. Append 1 more spacing to each line.
indented_item = f"\n{spacing}".join(formatted_list.splitlines())
@@ -160,7 +163,6 @@ def prettify_list(
value = f"{value},"
value = f"{value}\n"
- index += 1
value = f"{value}]"
return value
@@ -187,11 +189,10 @@ def prettify_dict(
length = sum(len(str(v)) for v in [*dictionary.keys(), *dictionary.values()])
do_wrap = length > wrap_threshold
- index = 0
end_index = len(dictionary) - 1
kv_str = "(\n" if do_wrap else "("
- for key, value in dictionary.items():
+ for index, (key, value) in enumerate(dictionary.items()):
if do_wrap:
kv_str += indent * " "
@@ -206,8 +207,6 @@ def prettify_dict(
if do_wrap:
kv_str += "\n"
- index += 1
-
return f"{kv_str})"
@@ -279,8 +278,8 @@ def parse_gas_table(report: "GasReport") -> list[Table]:
f"{len(gases)}",
f"{min(gases)}",
f"{max(gases)}",
- f"{int(round(mean(gases)))}",
- f"{int(round(median(gases)))}",
+ f"{round(mean(gases))}",
+ f"{round(median(gases))}",
)
if has_at_least_1_row:
@@ -369,9 +368,7 @@ def _parse_verbose_coverage(coverage: "CoverageReport", statement: bool = True)
if any(s.hit_count > 0 for s in fn.statements if s.tag == builtin)
else 1
)
- rows.append(
- tuple((name, name, "1", f"{miss}", "0.0%" if miss else "100.0%"))
- )
+ rows.append((name, name, "1", f"{miss}", "0.0%" if miss else "100.0%"))
else:
row = (
diff --git a/src/ape_accounts/_cli.py b/src/ape_accounts/_cli.py
index 3ca1dbea9e..d8d5831452 100644
--- a/src/ape_accounts/_cli.py
+++ b/src/ape_accounts/_cli.py
@@ -156,7 +156,7 @@ def ask_for_passphrase():
try:
passphrase = ask_for_passphrase()
account = _account_from_mnemonic(alias, passphrase, mnemonic, hd_path=custom_hd_path)
- except Exception as error:
+ except Exception as error: # noqa: BLE001
error_msg = f"{error}".replace(mnemonic, HIDDEN_MESSAGE)
cli_ctx.abort(f"Seed phrase can't be imported: {error_msg}")
@@ -165,7 +165,7 @@ def ask_for_passphrase():
try:
passphrase = ask_for_passphrase()
account = _account_from_key(alias, passphrase, key)
- except Exception as error:
+ except Exception as error: # noqa: BLE001
cli_ctx.abort(f"Key can't be imported: {error}")
if account:
diff --git a/src/ape_accounts/accounts.py b/src/ape_accounts/accounts.py
index 08888ac52d..f12493418f 100644
--- a/src/ape_accounts/accounts.py
+++ b/src/ape_accounts/accounts.py
@@ -4,7 +4,7 @@
from functools import cached_property
from os import environ
from pathlib import Path
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, ClassVar
import click
from eip712.messages import EIP712Message
@@ -46,7 +46,7 @@ def __init__(self):
class AccountContainer(AccountContainerAPI):
- loaded_accounts: dict[str, "KeyfileAccount"] = {}
+ loaded_accounts: ClassVar[dict[str, "KeyfileAccount"]] = {}
@property
def _keyfiles(self) -> Iterator[Path]:
@@ -91,11 +91,11 @@ def sign_authorization(
chain_id = self.chain_manager.chain_id
signed_authorization = EthAccount.sign_authorization(
- dict(
- chainId=chain_id,
- address=to_canonical_address(address),
- nonce=nonce or self.nonce,
- ),
+ {
+ "chainId": chain_id,
+ "address": to_canonical_address(address),
+ "nonce": nonce or self.nonce,
+ },
self.private_key,
)
return MessageSignature(
diff --git a/src/ape_cache/query.py b/src/ape_cache/query.py
index daf2d67b89..6780af9923 100644
--- a/src/ape_cache/query.py
+++ b/src/ape_cache/query.py
@@ -149,7 +149,7 @@ def database_connection(self):
logger.debug(f"Exception when querying:\n{e}")
return None
- except Exception as e:
+ except Exception as e: # noqa: BLE001
logger.warning(f"Unhandled exception when querying:\n{e}")
self.database_bypass = True
return None
@@ -329,8 +329,8 @@ def _perform_block_query(self, query: BlockQuery) -> Iterator[BlockAPI]:
# NOTE: Should be unreachable if estimated correctly
raise QueryEngineError(f"Could not perform query:\n{query}")
- yield from map(
- lambda row: self.provider.network.ecosystem.decode_block(dict(row.items())), result
+ yield from (
+ self.provider.network.ecosystem.decode_block(dict(row.items())) for row in result
)
@perform_query.register
@@ -344,7 +344,7 @@ def _perform_transaction_query(self, query: BlockTransactionQuery) -> Iterator[d
# NOTE: Should be unreachable if estimated correctly
raise QueryEngineError(f"Could not perform query:\n{query}")
- yield from map(lambda row: dict(row.items()), result)
+ yield from (dict(row.items()) for row in result)
@perform_query.register
def _perform_contract_events_query(self, query: ContractEventQuery) -> Iterator[ContractLog]:
@@ -360,7 +360,7 @@ def _perform_contract_events_query(self, query: ContractEventQuery) -> Iterator[
# NOTE: Should be unreachable if estimated correctly
raise QueryEngineError(f"Could not perform query:\n{query}")
- yield from map(lambda row: ContractLog.model_validate(dict(row.items())), result)
+ yield from (ContractLog.model_validate(dict(row.items())) for row in result)
@singledispatchmethod
def _cache_update_clause(self, query: QueryType) -> Insert:
diff --git a/src/ape_compile/_cli.py b/src/ape_compile/_cli.py
index 85fb04419f..32bee1c88a 100644
--- a/src/ape_compile/_cli.py
+++ b/src/ape_compile/_cli.py
@@ -99,7 +99,7 @@ def cli(
c.contract_type.name: c.contract_type
for c in dependency.compile(use_cache=use_cache, allow_install=True).values()
}
- except Exception as err:
+ except Exception as err: # noqa: BLE001
msg = f"Dependency '{dependency.name}' not installed. Reason: {err}"
cli_ctx.logger.error(msg)
errored = True
diff --git a/src/ape_compile/config.py b/src/ape_compile/config.py
index 6f12ca01ce..5ccbdc594b 100644
--- a/src/ape_compile/config.py
+++ b/src/ape_compile/config.py
@@ -1,7 +1,7 @@
import re
from re import Pattern
-from pydantic import field_serializer, field_validator
+from pydantic import Field, field_serializer, field_validator
from pydantic_settings import SettingsConfigDict
from ape.api.config import ConfigEnum, PluginConfig
@@ -27,7 +27,7 @@ class Config(PluginConfig):
Configure general compiler settings.
"""
- exclude: set[str | Pattern] = set()
+ exclude: set[str | Pattern] = Field(default_factory=set)
"""
Source exclusion globs or regex patterns across all file types.
To use regex, start your values with ``r"`` and they'll be turned
@@ -48,7 +48,7 @@ class Config(PluginConfig):
should configure ``include_dependencies`` to be ``True``.
"""
- output_extra: list[OutputExtras] = []
+ output_extra: list[OutputExtras] = Field(default_factory=list)
"""
Extra selections to output. Outputs to ``.build/{key.lower()}``.
"""
diff --git a/src/ape_console/_cli.py b/src/ape_console/_cli.py
index c371d8c333..af4a7fd243 100644
--- a/src/ape_console/_cli.py
+++ b/src/ape_console/_cli.py
@@ -39,7 +39,7 @@ def _code_callback(ctx, param, value) -> list[str]:
@click.command(
cls=ConnectedProviderCommand,
short_help="Load the console",
- context_settings=dict(ignore_unknown_options=True),
+ context_settings={"ignore_unknown_options": True},
)
@ape_cli_context()
@project_option(hidden=True, type=Path) # Hidden as mostly used for test purposes.
diff --git a/src/ape_console/config.py b/src/ape_console/config.py
index 9066867714..1a6b7257cd 100644
--- a/src/ape_console/config.py
+++ b/src/ape_console/config.py
@@ -1,10 +1,11 @@
+from pydantic import Field
from pydantic_settings import SettingsConfigDict
from ape.api.config import PluginConfig
class ConsoleConfig(PluginConfig):
- plugins: list[str] = []
+ plugins: list[str] = Field(default_factory=list)
"""Additional IPython plugins to include in your session."""
model_config = SettingsConfigDict(extra="allow", env_prefix="APE_CONSOLE_")
diff --git a/src/ape_ethereum/__init__.py b/src/ape_ethereum/__init__.py
index 864daf5f71..dc9a93c8e7 100644
--- a/src/ape_ethereum/__init__.py
+++ b/src/ape_ethereum/__init__.py
@@ -107,7 +107,6 @@ def __getattr__(name):
"SetCodeTransaction",
"SharedBlobReceipt",
"SharedBlobTransaction",
- "SharedBlobTransaction",
"StaticFeeTransaction",
"Trace",
"TransactionStatusEnum",
diff --git a/src/ape_ethereum/_converters.py b/src/ape_ethereum/_converters.py
index ed27d95289..53a6db2e5a 100644
--- a/src/ape_ethereum/_converters.py
+++ b/src/ape_ethereum/_converters.py
@@ -26,10 +26,7 @@ class WeiConversions(ConverterAPI):
"""Converts units like `1 ether` to 1e18 wei."""
def is_convertible(self, value: str) -> bool:
- if not isinstance(value, str):
- return False
-
- elif " " not in value or len(value.split(" ")) > 2:
+ if not isinstance(value, str) or " " not in value or len(value.split(" ")) > 2:
return False
val, unit = value.split(" ")
diff --git a/src/ape_ethereum/ecosystem.py b/src/ape_ethereum/ecosystem.py
index 357288689a..324079e54c 100644
--- a/src/ape_ethereum/ecosystem.py
+++ b/src/ape_ethereum/ecosystem.py
@@ -22,7 +22,7 @@
to_hex,
)
from ethpm_types.abi import ABIType, ConstructorABI, EventABI, MethodABI
-from pydantic import Field, computed_field, field_validator, model_validator
+from pydantic import Field, PrivateAttr, computed_field, field_validator, model_validator
from pydantic_settings import SettingsConfigDict
from ape.api.config import PluginConfig
@@ -157,7 +157,7 @@ class NetworkConfig(PluginConfig):
it isn't detected as one.
"""
- request_headers: dict = {}
+ request_headers: dict = Field(default_factory=dict)
"""Optionally config extra request headers whenever using this network."""
model_config = SettingsConfigDict(extra="allow", env_prefix="APE_ETHEREUM_")
@@ -172,10 +172,7 @@ def validate_gas_limit(cls, value):
elif value == "auto":
return AutoGasLimit()
- elif value in ("auto", "max") or isinstance(value, AutoGasLimit):
- return value
-
- elif isinstance(value, int):
+ elif value in ("auto", "max") or isinstance(value, (AutoGasLimit, int)):
return value
elif isinstance(value, str) and value.isnumeric():
@@ -237,11 +234,11 @@ class BaseEthereumConfig(PluginConfig):
NETWORKS: ClassVar[dict[str, tuple[int, int]]] = NETWORKS
default_network: str = LOCAL_NETWORK_NAME
- _forked_configs: dict[str, ForkedNetworkConfig] = {}
- _custom_networks: dict[str, NetworkConfig] = {}
+ _forked_configs: dict[str, ForkedNetworkConfig] = PrivateAttr(default_factory=dict)
+ _custom_networks: dict[str, NetworkConfig] = PrivateAttr(default_factory=dict)
# NOTE: This gets appended to Ape's root User-Agent string.
- request_headers: dict = {}
+ request_headers: dict = Field(default_factory=dict)
model_config = SettingsConfigDict(extra="allow", env_prefix="APE_ETHEREUM_")
@@ -327,9 +324,8 @@ def __contains__(self, key: str) -> bool:
def get(self, key: str, default: Any | None = None) -> Any:
net_key = key.replace("-", "_")
- if net_key.endswith("_fork"):
- if cfg := self._get_forked_config(net_key):
- return cfg
+ if net_key.endswith("_fork") and (cfg := self._get_forked_config(net_key)):
+ return cfg
result: Any
if result := super().get(key, default=default):
@@ -347,15 +343,14 @@ def _get_forked_config(self, name: str) -> ForkedNetworkConfig | None:
return self._forked_configs[live_key]
live_cfg: Any
- if live_cfg := self.get(live_key):
- if isinstance(live_cfg, NetworkConfig):
- fork_cfg = create_local_network_config(
- use_fork=True,
- default_transaction_type=self.DEFAULT_TRANSACTION_TYPE,
- gas_limit=self.DEFAULT_LOCAL_GAS_LIMIT,
- )
- self._forked_configs[live_key] = fork_cfg
- return fork_cfg
+ if (live_cfg := self.get(live_key)) and isinstance(live_cfg, NetworkConfig):
+ fork_cfg = create_local_network_config(
+ use_fork=True,
+ default_transaction_type=self.DEFAULT_TRANSACTION_TYPE,
+ gas_limit=self.DEFAULT_LOCAL_GAS_LIMIT,
+ )
+ self._forked_configs[live_key] = fork_cfg
+ return fork_cfg
return None
@@ -379,7 +374,7 @@ class Block(BlockAPI):
base_fee: HexInt = Field(default=0, alias="baseFeePerGas")
difficulty: HexInt = 0
total_difficulty: HexInt = Field(default=0, alias="totalDifficulty")
- uncles: list[HexBytes] = []
+ uncles: list[HexBytes] = Field(default_factory=list)
# Type re-declares.
hash: HexBytes | None = None
@@ -770,12 +765,9 @@ def decode_calldata(self, abi: ConstructorABI | MethodABI, calldata: bytes) -> d
for v, t in zip(raw_input_values, input_types, strict=True)
]
arguments = {}
- index = 0
- for i, v in zip(abi.inputs, input_values, strict=True):
+ for index, (i, v) in enumerate(zip(abi.inputs, input_values, strict=True)):
name = i.name or f"{index}"
arguments[name] = v
- index += 1
-
return arguments
def decode_returndata(self, abi: MethodABI, raw_data: bytes) -> tuple[Any, ...]:
@@ -821,7 +813,7 @@ def decode_returndata(self, abi: MethodABI, raw_data: bytes) -> tuple[Any, ...]:
else:
try:
return ([o for o in output_values[0]],) # type: ignore[union-attr]
- except Exception:
+ except Exception: # noqa: BLE001
# On-chains transaction data errors.
return (output_values,)
@@ -1057,7 +1049,7 @@ def decode_logs(self, logs: Sequence[dict], *events: EventABI) -> Iterator[Contr
}
def get_abi(_topic: HexStr) -> LogInputABICollection | None:
- return abi_inputs[_topic] if _topic in abi_inputs else None
+ return abi_inputs.get(_topic, None)
for log in logs:
if log.get("anonymous"):
@@ -1191,7 +1183,7 @@ def _enrich_calltree(self, call: dict, **kwargs) -> dict:
call["contract_id"] = address = kwargs["contract_address"] = self.decode_address(
address
)
- except Exception:
+ except Exception: # noqa: BLE001
# Tx was made with a weird address.
call["contract_id"] = address
@@ -1205,7 +1197,7 @@ def _enrich_calltree(self, call: dict, **kwargs) -> dict:
try:
address_int = int(address, 16)
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
else:
# Collapse pre-compile address calls
@@ -1479,7 +1471,7 @@ def _enrich_trace_event(
try:
contract_logs = [log for log in self.decode_logs([log_data], abi)]
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.debug(f"Failed decoding logs from trace data: {err}")
return event
@@ -1508,7 +1500,7 @@ def _get_contract_type_for_enrichment(
if not (contract_type := kwargs.get("contract_type")):
try:
contract_type = self.chain_manager.contracts.get(address)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.debug(f"Error getting contract type during event enrichment: {err}")
return contract_type
@@ -1525,7 +1517,7 @@ def decode_custom_error(
# Use an instance (required for proper error caching).
try:
contract = self.chain_manager.contracts.instance_at(address)
- except Exception:
+ except Exception: # noqa: BLE001
return None
selector = data[:4]
@@ -1559,7 +1551,7 @@ def decode_custom_error(
try:
if not (last_addr := next(trace.get_addresses_used(reverse=True), None)):
return None
- except Exception:
+ except Exception: # noqa: BLE001
# When unable to get trace-frames properly, such as eth-tester.
return None
diff --git a/src/ape_ethereum/provider.py b/src/ape_ethereum/provider.py
index 17d14d3492..f8cfc99979 100644
--- a/src/ape_ethereum/provider.py
+++ b/src/ape_ethereum/provider.py
@@ -17,6 +17,7 @@
from eth_typing import HexStr
from eth_utils import add_0x_prefix, is_hex, to_hex
from evmchains import PUBLIC_CHAIN_META, get_random_rpc
+from pydantic import Field, PrivateAttr
from pydantic.dataclasses import dataclass
from requests import HTTPError
from web3 import HTTPProvider, IPCProvider, Web3
@@ -119,7 +120,7 @@ def _post_send_transaction(tx: TransactionAPI, receipt: ReceiptAPI):
except TransactionNotFound:
# Receipt never published. Likely failed.
pass
- except Exception as err:
+ except Exception as err: # noqa: BLE001
# Avoid letting debug logs causes program crashes.
logger.debug(f"Unable to show debug logs: {err}")
@@ -143,7 +144,7 @@ class Web3Provider(ProviderAPI, ABC):
_supports_debug_trace_call: bool | None = None
- _transaction_trace_cache: dict[str, TransactionTrace] = {}
+ _transaction_trace_cache: dict[str, TransactionTrace] = PrivateAttr(default_factory=dict)
def __new__(cls, *args, **kwargs):
# Post-connection ops
@@ -276,10 +277,13 @@ def _get_connected_rpc(self, validator: Callable[[str], bool]) -> str | None:
like `ape-node`, configure your URI and that will
be returned here instead.
"""
- if web3 := self._web3:
- if endpoint_uri := getattr(web3.provider, "endpoint_uri", None):
- if isinstance(endpoint_uri, str) and validator(endpoint_uri):
- return endpoint_uri
+ if (
+ (web3 := self._web3)
+ and (endpoint_uri := getattr(web3.provider, "endpoint_uri", None))
+ and isinstance(endpoint_uri, str)
+ and validator(endpoint_uri)
+ ):
+ return endpoint_uri
return None
@@ -337,17 +341,13 @@ def network_choice(self) -> str:
@property
def http_uri(self) -> str | None:
- if rpc := self._connected_http_uri:
+ if (rpc := self._connected_http_uri) or (rpc := self._configured_http_uri):
return rpc
- elif rpc := self._configured_http_uri:
+ elif (rpc := self._configured_uri) and _is_http_url(rpc):
+ # "uri" found in config/settings and is WS.
return rpc
- elif rpc := self._configured_uri:
- if _is_http_url(rpc):
- # "uri" found in config/settings and is WS.
- return rpc
-
return self._default_http_uri
@property
@@ -385,10 +385,9 @@ def ws_uri(self) -> str | None:
# "ws_uri" found in config/settings
return rpc
- elif rpc := self._configured_uri:
- if _is_ws_url(rpc):
- # "uri" found in config/settings and is WS.
- return rpc
+ elif (rpc := self._configured_uri) and _is_ws_url(rpc):
+ # "uri" found in config/settings and is WS.
+ return rpc
return None
@@ -398,10 +397,9 @@ def ipc_path(self) -> Path | None:
# "ipc_path" found in config/settings
return Path(rpc)
- elif rpc := self._configured_uri:
- if _is_ipc_path(rpc):
- # "uri" found in config/settings and is IPC.
- return Path(rpc)
+ elif (rpc := self._configured_uri) and _is_ipc_path(rpc):
+ # "uri" found in config/settings and is IPC.
+ return Path(rpc)
return None
@@ -422,7 +420,7 @@ def rpc_available(rpc_uri: str) -> bool:
try:
return Web3(HTTPProvider(rpc_uri)).is_connected()
- except Exception:
+ except Exception: # noqa: BLE001
return False
retries = 10
@@ -451,7 +449,7 @@ def client_version(self) -> str:
def base_fee(self) -> int:
try:
fee_history = self._get_fee_history()
- except Exception as exc:
+ except Exception as exc: # noqa: BLE001
logger.debug(
f"Failed using `eth_feeHistory` for network '{self.network_choice}'. Error: {exc}"
)
@@ -512,7 +510,7 @@ def supports_tracing(self) -> bool:
except NotImplementedError:
return False
- except Exception:
+ except Exception: # noqa: BLE001
# We know tracing works because we didn't get a NotImplementedError.
return True
@@ -729,19 +727,23 @@ def send_call(
if track_gas and self._test_runner is not None and txn.receiver:
self._test_runner.gas_tracker.append_gas(trace, txn.receiver)
- if track_coverage and self._test_runner is not None and txn.receiver:
- if contract_type := self.chain_manager.contracts.get(txn.receiver):
- if contract_src := self.local_project._create_contract_source(contract_type):
- method_id = HexBytes(txn.data)
- selector = (
- contract_type.methods[method_id].selector
- if method_id in contract_type.methods
- else None
- )
- source_traceback = SourceTraceback.create(contract_src, trace, method_id)
- self._test_runner.coverage_tracker.cover(
- source_traceback, function=selector, contract=contract_type.name
- )
+ if (
+ track_coverage
+ and self._test_runner is not None
+ and txn.receiver
+ and (contract_type := self.chain_manager.contracts.get(txn.receiver))
+ and (contract_src := self.local_project._create_contract_source(contract_type))
+ ):
+ method_id = HexBytes(txn.data)
+ selector = (
+ contract_type.methods[method_id].selector
+ if method_id in contract_type.methods
+ else None
+ )
+ source_traceback = SourceTraceback.create(contract_src, trace, method_id)
+ self._test_runner.coverage_tracker.cover(
+ source_traceback, function=selector, contract=contract_type.name
+ )
if show_gas:
trace.show_gas_report()
@@ -769,15 +771,14 @@ def _eth_call(
contract_address = arguments[0].get("to")
_lazy_call_trace = _LazyCallTrace(arguments)
- if not skip_trace:
- if address := contract_address:
- try:
- contract_type = self.chain_manager.contracts.get(address)
- except RecursionError:
- # Occurs when already in the middle of fetching this contract.
- pass
- else:
- _lazy_call_trace.contract_type = contract_type
+ if not skip_trace and (address := contract_address):
+ try:
+ contract_type = self.chain_manager.contracts.get(address)
+ except RecursionError:
+ # Occurs when already in the middle of fetching this contract.
+ pass
+ else:
+ _lazy_call_trace.contract_type = contract_type
vm_err = self.get_virtual_machine_error(
err,
@@ -1051,7 +1052,7 @@ def assert_chain_activity():
if adjusted_head.number is None or adjusted_head.hash is None:
raise ProviderError("Adjusted head block has no number or hash.")
- except Exception:
+ except Exception: # noqa: BLE001
# TODO: I did encounter this sometimes in a re-org, needs better handling
# and maybe bubbling up the block number/hash exceptions above.
assert_chain_activity()
@@ -1114,9 +1115,8 @@ def poll_logs(
if required_confirmations is None:
required_confirmations = self.network.required_confirmations
- if stop_block is not None:
- if stop_block <= (self._get_latest_block().number or 0):
- raise ValueError("'stop' argument must be in the future.")
+ if stop_block is not None and stop_block <= (self._get_latest_block().number or 0):
+ raise ValueError("'stop' argument must be in the future.")
for block in self.poll_blocks(stop_block, required_confirmations, new_block_timeout):
if block.number is None:
@@ -1193,12 +1193,15 @@ def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI:
# else: Assume user specified the correct amount or txn will fail and waste gas
- if txn_type is TransactionType.ACCESS_LIST and isinstance(txn, AccessListTransaction):
- if not txn.access_list:
- try:
- txn.access_list = self.create_access_list(txn)
- except APINotImplementedError:
- pass
+ if (
+ txn_type is TransactionType.ACCESS_LIST
+ and isinstance(txn, AccessListTransaction)
+ and not txn.access_list
+ ):
+ try:
+ txn.access_list = self.create_access_list(txn)
+ except APINotImplementedError:
+ pass
gas_limit = self.network.gas_limit if txn.gas_limit is None else txn.gas_limit
if gas_limit in (None, "auto") or isinstance(gas_limit, AutoGasLimit):
@@ -1467,7 +1470,7 @@ def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMa
# NOTE: For some reason, it comes back with single quotes though.
try:
err_data = json.loads(str(err_data or "").replace("'", '"'))
- except Exception:
+ except Exception: # noqa: BLE001
return VirtualMachineError(base_err=exception, **kwargs)
if not (err_msg := err_data.get("message")):
@@ -1524,9 +1527,6 @@ def _handle_execution_reverted(
if trace is not None:
if callable(trace):
trace = params["trace"] = trace()
- else:
- trace = trace
-
if trace is not None and (revert_message := trace.revert_message):
message = revert_message
no_reason = False
@@ -1580,7 +1580,9 @@ class EthereumNodeProvider(Web3Provider, ABC):
name: str = "node"
# NOTE: Appends user-agent to base User-Agent string.
- request_header: dict = {"User-Agent": f"EthereumNodeProvider/web3.py/{web3_version}"}
+ request_header: dict = Field(
+ default_factory=lambda: {"User-Agent": f"EthereumNodeProvider/web3.py/{web3_version}"}
+ )
@property
def connection_str(self) -> str:
@@ -1626,7 +1628,7 @@ def has_poa_history(self) -> bool:
findings = True
break
- except Exception:
+ except Exception: # noqa: BLE001, S112
# Some chains are "light" and we may not be able to detect
# if it needs PoA middleware.
continue
@@ -1817,11 +1819,11 @@ def _is_uri(val: str) -> bool:
def _is_http_url(val: str) -> bool:
- return val.startswith("https://") or val.startswith("http://")
+ return val.startswith(("https://", "http://"))
def _is_ws_url(val: str) -> bool:
- return val.startswith("wss://") or val.startswith("ws://")
+ return val.startswith(("wss://", "ws://"))
def _is_ipc_path(val: str | Path) -> bool:
@@ -1847,8 +1849,7 @@ def source_traceback(self) -> SourceTraceback | None:
return None
method_id = self._arguments[0].get("data", "")[:10] or None
- if ct and method_id:
- if contract_src := self.local_project._create_contract_source(ct):
- return SourceTraceback.create(contract_src, self.trace, method_id)
+ if ct and method_id and (contract_src := self.local_project._create_contract_source(ct)):
+ return SourceTraceback.create(contract_src, self.trace, method_id)
return None
diff --git a/src/ape_ethereum/trace.py b/src/ape_ethereum/trace.py
index 1d1badb9a9..e99be62793 100644
--- a/src/ape_ethereum/trace.py
+++ b/src/ape_ethereum/trace.py
@@ -21,7 +21,7 @@
)
from evm_trace.gas import merge_reports
from hexbytes import HexBytes
-from pydantic import field_validator
+from pydantic import Field, PrivateAttr, field_validator
from rich.tree import Tree
from ape.api.trace import TraceAPI
@@ -104,7 +104,7 @@ class Trace(TraceAPI):
def __repr__(self) -> str:
try:
return f"{self}"
- except Exception as err:
+ except Exception as err: # noqa: BLE001
# Don't let __repr__ fail.
logger.debug(f"Problem transaction trace: {err}")
return ""
@@ -160,16 +160,15 @@ def enriched_calltree(self) -> dict:
self._enriched_calltree = {}
# Add top-level data if missing.
- if not self._enriched_calltree.get("gas_cost"):
- # Happens on calltrees built from structLogs.
- if gas_used := self.transaction.get("gas_used"):
- if "data" in self.transaction:
- # Subtract base gas costs.
- # (21_000 + 4 gas per 0-byte and 16 gas per non-zero byte).
- data_gas = sum(
- [4 if x == 0 else 16 for x in HexBytes(self.transaction["data"])]
- )
- self._enriched_calltree["gas_cost"] = gas_used - 21_000 - data_gas
+ if (
+ not self._enriched_calltree.get("gas_cost")
+ and (gas_used := self.transaction.get("gas_used"))
+ and "data" in self.transaction
+ ):
+ # Subtract base gas costs.
+ # (21_000 + 4 gas per 0-byte and 16 gas per non-zero byte).
+ data_gas = sum([4 if x == 0 else 16 for x in HexBytes(self.transaction["data"])])
+ self._enriched_calltree["gas_cost"] = gas_used - 21_000 - data_gas
return self._enriched_calltree
@@ -186,7 +185,7 @@ def root_contract_type(self) -> "ContractType | None":
if address := self.transaction.get("to"):
try:
return self.chain_manager.contracts.get(address)
- except Exception:
+ except Exception: # noqa: BLE001
return None
return None
@@ -197,7 +196,7 @@ def root_method_abi(self) -> "MethodABI | None":
if ct := self.root_contract_type:
try:
return ct.methods[method_id]
- except Exception:
+ except Exception: # noqa: BLE001
return None
return None
@@ -252,19 +251,20 @@ def _get_return_value_from_calltree(
self, calltree: dict | CallTreeNode
) -> tuple[Any | None, ...]:
num_outputs = 1
- if raw_return_data := (
- calltree.get("returndata") if isinstance(calltree, dict) else calltree.returndata
- ):
- if abi := self._get_abi(calltree):
- # Ensure we return a tuple with the correct length, even if fails.
- num_outputs = len(abi.outputs)
- try:
- return self._ecosystem.decode_returndata(abi, HexBytes(raw_return_data))
- except Exception as err:
- logger.debug(
- f"Failed decoding raw returndata: {to_hex(raw_return_data)}. Error: {err}"
- )
- return tuple([None for _ in range(num_outputs)])
+ if (
+ raw_return_data := (
+ calltree.get("returndata") if isinstance(calltree, dict) else calltree.returndata
+ )
+ ) and (abi := self._get_abi(calltree)):
+ # Ensure we return a tuple with the correct length, even if fails.
+ num_outputs = len(abi.outputs)
+ try:
+ return self._ecosystem.decode_returndata(abi, HexBytes(raw_return_data))
+ except Exception as err: # noqa: BLE001
+ logger.debug(
+ f"Failed decoding raw returndata: {to_hex(raw_return_data)}. Error: {err}"
+ )
+ return tuple([None for _ in range(num_outputs)])
return tuple([None for _ in range(num_outputs)])
@@ -297,7 +297,7 @@ def try_get_revert_msg(c) -> str | None:
def _last_frame(self) -> dict | None:
try:
frame = deque(self.raw_trace_frames, maxlen=1)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.error(f"Failed getting traceback: {err}")
return None
@@ -349,19 +349,20 @@ def show(self, verbose: bool = False, file: IO[str] = sys.stdout):
if sender := self.transaction.get("from"):
console.print(f"tx.origin=[{TraceStyles.CONTRACTS}]{sender}[/]")
- if self.call_trace_approach not in approaches_handling_events and hasattr(
- self._ecosystem, "_enrich_trace_events"
+ if (
+ self.call_trace_approach not in approaches_handling_events
+ and hasattr(self._ecosystem, "_enrich_trace_events")
+ and (logs := self.transaction.get("logs", []))
):
# We must manually attach the contract logs.
# NOTE: With these approaches, we don't know where they appear
# in the call-tree so we have to put them at the top.
- if logs := self.transaction.get("logs", []):
- enriched_events = self._ecosystem._enrich_trace_events(logs)
- event_trees = _events_to_trees(enriched_events)
- if event_trees:
- console.print()
- self.chain_manager._reports.show_events(event_trees, console=console)
- console.print()
+ enriched_events = self._ecosystem._enrich_trace_events(logs)
+ event_trees = _events_to_trees(enriched_events)
+ if event_trees:
+ console.print()
+ self.chain_manager._reports.show_events(event_trees, console=console)
+ console.print()
# else: the events are already included in the right spots in the call tree.
@@ -461,8 +462,8 @@ def _get_abi(self, call: dict | CallTreeNode) -> "MethodABI | None":
class TransactionTrace(Trace):
transaction_hash: HexStr
- debug_trace_transaction_parameters: dict = {"enableMemory": True}
- _frames: list[dict] = []
+ debug_trace_transaction_parameters: dict = Field(default_factory=lambda: {"enableMemory": True})
+ _frames: list[dict] = PrivateAttr(default_factory=list)
@property
def raw_trace_frames(self) -> Iterator[dict]:
@@ -531,7 +532,7 @@ def _discover_calltrace_approach(self) -> CallTreeNode:
for approach, fn in approaches.items():
try:
call = fn()
- except Exception as err:
+ except Exception as err: # noqa: BLE001
reason_map[approach.name] = f"{err}"
continue
@@ -595,7 +596,7 @@ class CallTrace(Trace):
be created near sending the request.
"""
- arguments: list[Any] = []
+ arguments: list[Any] = Field(default_factory=list)
"""
Remaining eth-call arguments, minus the transaction.
"""
@@ -631,7 +632,7 @@ def _traced_call(self) -> dict:
try:
result = self._debug_trace_call()
- except Exception:
+ except Exception: # noqa: BLE001
self._set_supports_trace_call(False)
return {}
@@ -699,9 +700,9 @@ def _events_to_trees(events: list[dict]) -> list[Tree]:
event_counter[tuple_key].append(evt)
result = []
- for evt_tup, events in event_counter.items():
- count = len(events)
- evt = events[0]
+ for grouped_events in event_counter.values():
+ count = len(grouped_events)
+ evt = grouped_events[0]
if "name" not in evt and "calldata" not in evt:
# Not sure; or not worth showing.
logger.debug(f"Unknown event data: '{evt}'.")
diff --git a/src/ape_ethereum/transactions.py b/src/ape_ethereum/transactions.py
index 49df031f6f..41f3b3cb76 100644
--- a/src/ape_ethereum/transactions.py
+++ b/src/ape_ethereum/transactions.py
@@ -320,14 +320,15 @@ def method_called(self) -> MethodABI | None:
@cached_property
def source_traceback(self) -> SourceTraceback:
- if contract_type := self.contract_type:
- if contract_src := self.local_project._create_contract_source(contract_type):
- try:
- return SourceTraceback.create(contract_src, self.trace, HexBytes(self.data))
- except Exception as err:
- # Failing to get a traceback should not halt an Ape application.
- # Sometimes, a node crashes and we are left with nothing.
- logger.error(f"Problem retrieving traceback: {err}")
+ if (contract_type := self.contract_type) and (
+ contract_src := self.local_project._create_contract_source(contract_type)
+ ):
+ try:
+ return SourceTraceback.create(contract_src, self.trace, HexBytes(self.data))
+ except Exception as err: # noqa: BLE001
+ # Failing to get a traceback should not halt an Ape application.
+ # Sometimes, a node crashes and we are left with nothing.
+ logger.error(f"Problem retrieving traceback: {err}")
return SourceTraceback.model_validate([])
diff --git a/src/ape_networks/_cli.py b/src/ape_networks/_cli.py
index 05125f2a3e..ebf8b66cb4 100644
--- a/src/ape_networks/_cli.py
+++ b/src/ape_networks/_cli.py
@@ -89,7 +89,7 @@ def make_sub_tree(data: dict, create_tree: Callable) -> Tree:
ecosystem_tree = make_sub_tree(ecosystem, Tree)
_networks = {n["name"]: n for n in ecosystem["networks"]}
_networks = {n: _networks[n] for n in sorted(_networks)}
- for network_name, network in _networks.items():
+ for network in _networks.values():
providers = network["providers"]
if providers:
network_tree = make_sub_tree(network, ecosystem_tree.add)
@@ -112,7 +112,7 @@ def make_sub_tree(data: dict, create_tree: Callable) -> Tree:
except ValueError as err:
try:
data_str = json.dumps(network_data)
- except Exception:
+ except Exception: # noqa: BLE001
data_str = str(network_data)
raise NetworkError(
@@ -208,7 +208,7 @@ def _run(
finally:
try:
provider.disconnect()
- except Exception:
+ except Exception: # noqa: BLE001
# Prevent not being able to CTRL-C.
cli_ctx.abort("Terminated")
@@ -246,7 +246,7 @@ def kill(cli_ctx, process_ids, kill_all):
click.echo("Stopped the following node(s):")
pids_stopped = set()
for pid, data in processes_killed.items():
- echo_rich_text(f"\t{repr(data)}")
+ echo_rich_text(f"\t{data!r}")
pids_stopped.add(pid)
if rest := [pid for pid in process_ids if pid not in pids_stopped]:
diff --git a/src/ape_networks/config.py b/src/ape_networks/config.py
index b1ae538d13..ef89ad6987 100644
--- a/src/ape_networks/config.py
+++ b/src/ape_networks/config.py
@@ -1,3 +1,4 @@
+from pydantic import Field
from pydantic_settings import SettingsConfigDict
from ape.api.config import PluginConfig
@@ -23,7 +24,7 @@ class CustomNetwork(PluginConfig):
default_provider: str = "node"
"""The default provider plugin to use. Default is the default node provider."""
- request_header: dict = {}
+ request_header: dict = Field(default_factory=dict)
"""The HTTP request header."""
model_config = SettingsConfigDict(extra="allow", env_prefix="APE_NETWORKS_")
@@ -37,5 +38,5 @@ def is_fork(self) -> bool:
class NetworksConfig(PluginConfig):
- custom: list[CustomNetwork] = []
+ custom: list[CustomNetwork] = Field(default_factory=list)
model_config = SettingsConfigDict(extra="allow", env_prefix="APE_NETWORKS_")
diff --git a/src/ape_node/provider.py b/src/ape_node/provider.py
index 893d32ce32..bc8f74afca 100644
--- a/src/ape_node/provider.py
+++ b/src/ape_node/provider.py
@@ -13,7 +13,7 @@
from geth.chain import initialize_chain as initialize_gethdev_chain
from geth.process import BaseGethProcess
from geth.wrapper import ALL_APIS, construct_test_chain_kwargs
-from pydantic import field_validator
+from pydantic import Field, field_validator
from pydantic_settings import SettingsConfigDict
from requests.exceptions import ConnectionError
from web3.middleware import ExtraDataToPOAMiddleware
@@ -207,11 +207,8 @@ def __init__(
geth_kwargs.pop("password", None)
# Ensure IPC path has correct name.
- if ipc_path_kwarg := geth_kwargs.get("ipc_path"):
- if ipc_path_kwarg.endswith("geth.ipc"):
- geth_kwargs["ipc_path"] = ipc_path_kwarg.replace(
- "geth.ipc", f"{self.process_name}.ipc"
- )
+ if (ipc_path_kwarg := geth_kwargs.get("ipc_path")) and ipc_path_kwarg.endswith("geth.ipc"):
+ geth_kwargs["ipc_path"] = ipc_path_kwarg.replace("geth.ipc", f"{self.process_name}.ipc")
# Ensure a clean data-dir.
self._clean()
@@ -422,11 +419,17 @@ def wait(self, *args, **kwargs):
class EthereumNetworkConfig(PluginConfig):
# Make sure you are running the right networks when you try for these
- mainnet: dict = {}
- holesky: dict = {}
- sepolia: dict = {}
+ mainnet: dict = Field(default_factory=dict)
+ holesky: dict = Field(default_factory=dict)
+ sepolia: dict = Field(default_factory=dict)
# Make sure to run via `geth --dev` (or similar)
- local: dict = {**DEFAULT_SETTINGS.copy(), "chain_id": DEFAULT_TEST_CHAIN_ID, "block_time": 0}
+ local: dict = Field(
+ default_factory=lambda: {
+ **DEFAULT_SETTINGS,
+ "chain_id": DEFAULT_TEST_CHAIN_ID,
+ "block_time": 0,
+ }
+ )
model_config = SettingsConfigDict(extra="allow", env_prefix="APE_NODE_")
@@ -483,7 +486,7 @@ class EthereumNodeConfig(PluginConfig):
based on your node's client-version and available RPCs.
"""
- request_headers: dict = {}
+ request_headers: dict = Field(default_factory=dict)
"""
Optionally specify request headers to use whenever using this provider.
"""
diff --git a/src/ape_pm/_cli.py b/src/ape_pm/_cli.py
index 7b261fb569..5d8ebb6a04 100644
--- a/src/ape_pm/_cli.py
+++ b/src/ape_pm/_cli.py
@@ -147,7 +147,7 @@ def _package_callback(ctx, param, value):
# Check if is a local package.
try:
path = Path(value).absolute()
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
else:
if path.exists():
@@ -230,7 +230,7 @@ def install(cli_ctx, package, name, version, ref, force, config_override, no_rec
try:
dependency = pm.dependencies.install(**package, use_cache=not force, recurse=not no_recurse)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
cli_ctx.logger.log_error(err)
sys.exit(1)
@@ -320,7 +320,7 @@ def _uninstall(dependency: "Dependency", yes: bool = False) -> bool:
try:
dependency.uninstall()
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.error(f"Failed uninstalling '{key}': {err}")
return False
@@ -376,7 +376,7 @@ def compile(cli_ctx, name, version, force, config_override):
def _compile_dependency(cli_ctx, dependency: "Dependency", force: bool):
try:
result = dependency.compile(use_cache=not force)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
cli_ctx.logger.error(str(err))
else:
if result:
diff --git a/src/ape_pm/compiler.py b/src/ape_pm/compiler.py
index 0e0a9ebeb8..45d2a73e05 100644
--- a/src/ape_pm/compiler.py
+++ b/src/ape_pm/compiler.py
@@ -125,7 +125,7 @@ def enrich_error(self, err: ContractLogicError) -> ContractLogicError:
try:
contract = self.chain_manager.contracts.instance_at(address)
- except Exception:
+ except Exception: # noqa: BLE001
return err
if (
diff --git a/src/ape_pm/dependency.py b/src/ape_pm/dependency.py
index bdb7eac57e..ac602e3e0b 100644
--- a/src/ape_pm/dependency.py
+++ b/src/ape_pm/dependency.py
@@ -219,7 +219,7 @@ def fetch(self, destination: Path):
version = self.version or "latest"
try:
self._fetch_version(version, destination)
- except Exception as err_from_version_approach:
+ except Exception as err_from_version_approach: # noqa: BLE001
logger.warning(
f"No official release found for version '{version}'. "
"Use `ref:` instead of `version:` for release tags. "
@@ -233,7 +233,7 @@ def fetch(self, destination: Path):
shutil.rmtree(destination, onerror=_remove_readonly)
try:
self._fetch_ref(version, destination)
- except Exception:
+ except Exception: # noqa: BLE001
# NOTE: Ignore this error, it was merely a last attempt.
raise err_from_version_approach
@@ -241,14 +241,14 @@ def _fetch_ref(self, ref: str, destination: Path):
options = _version_to_options(ref)
attempt = 0
num_attempts = len(options)
- for ref in options:
+ for ref_option in options:
attempt += 1
try:
self._github_client.clone_repo(
self.org_name,
self.repo_name,
destination,
- branch=ref,
+ branch=ref_option,
scheme=self.scheme,
)
except Exception:
@@ -398,7 +398,7 @@ def _get_version_from_package_json(
try:
data = json.loads(package_json.read_text())
- except Exception as err:
+ except Exception as err: # noqa: BLE001
logger.warning(f"Failed to parse package.json: {err}")
return None
@@ -555,10 +555,8 @@ def version_from_package_data(self) -> str | None:
@cached_property
def download_archive_url(self) -> str:
- if not (version := self.version):
- if not (version := self.version_from_package_data):
- # Not sure if this is possible, but just in case API data changes or something.
- raise ProjectError(f"Unable to find version for package '{self.package_id}'.")
+ if not (version := self.version) and not (version := self.version_from_package_data):
+ raise ProjectError(f"Unable to find version for package '{self.package_id}'.")
releases = self.package_data.get("releases", {})
if version not in releases:
@@ -596,8 +594,7 @@ def _fetch_archive_file(self, destination) -> Path:
with requests.get(download_url, stream=True) as response:
response.raise_for_status()
with open(archive_destination, "wb") as file:
- for chunk in response.iter_content(chunk_size=8192): # 8 KB
- file.write(chunk)
+ file.writelines(response.iter_content(chunk_size=8192))
return archive_destination
diff --git a/src/ape_pm/project.py b/src/ape_pm/project.py
index 89930b8b1d..7d1540acb7 100644
--- a/src/ape_pm/project.py
+++ b/src/ape_pm/project.py
@@ -5,7 +5,7 @@
from ape.utils._github import _GithubClient, github_client
-if sys.version_info.minor >= 11:
+if sys.version_info >= (3, 11):
# 3.11 or greater
# NOTE: type-ignore is for when running mypy on python versions < 3.11
import tomllib # type: ignore[import-not-found]
@@ -42,7 +42,7 @@ def extract_config(self, **overrides) -> ApeConfig:
try:
brownie_config_data = safe_load(text) or {}
- except Exception:
+ except Exception: # noqa: BLE001
brownie_config_data = {}
contracts_folder = brownie_config_data.get("contracts_folder", "contracts")
@@ -204,12 +204,9 @@ def _parse_gitmodules(self) -> list[dict[str, str]]:
for line in content.splitlines():
line = line.strip()
- if line.startswith("[submodule"):
- # Add the submodule we have been building to the list
- # if it exists. This happens on submodule after the first one.
- if submodule:
- submodules.append(submodule)
- submodule = {}
+ if line.startswith("[submodule") and submodule:
+ submodules.append(submodule)
+ submodule = {}
for key in ("path", "url", "release", "branch"):
if not line.startswith(f"{key} ="):
@@ -358,4 +355,4 @@ def _parse_remappings(
# Item seems like a dependency but not found in `dependencies`.
ape_sol_remappings.add(f_remap)
- return sorted(list(ape_sol_remappings))
+ return sorted(ape_sol_remappings)
diff --git a/src/ape_run/_cli.py b/src/ape_run/_cli.py
index 1602811418..e3877f975c 100644
--- a/src/ape_run/_cli.py
+++ b/src/ape_run/_cli.py
@@ -116,7 +116,7 @@ def _get_command(self, filepath: Path) -> click.Command | click.Group | None:
with use_scripts_sys_path(filepath.parent.parent):
try:
cli_ns = run_script_module(filepath)
- except Exception as e:
+ except Exception as e: # noqa: BLE001
logger.error_from_exception(
e, f"Exception while parsing script: {relative_filepath}"
)
diff --git a/src/ape_test/_cli.py b/src/ape_test/_cli.py
index 7b83c3dd37..b7b6c08512 100644
--- a/src/ape_test/_cli.py
+++ b/src/ape_test/_cli.py
@@ -72,7 +72,7 @@ def parse_args(self, ctx, args: list[str]) -> list[str]:
@click.command(
add_help_option=False, # NOTE: This allows pass-through to pytest's help
short_help="Launches pytest and runs the tests for a project",
- context_settings=dict(ignore_unknown_options=True),
+ context_settings={"ignore_unknown_options": True},
cls=ApeTestCommand,
)
# NOTE: Using '.value' because more performant.
diff --git a/src/ape_test/_watch.py b/src/ape_test/_watch.py
index 95627cf4f4..682cef98d0 100644
--- a/src/ape_test/_watch.py
+++ b/src/ape_test/_watch.py
@@ -1,7 +1,7 @@
import threading
import time
from collections.abc import Iterable
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
from functools import cached_property
from pathlib import Path
from subprocess import run as run_subprocess
@@ -46,7 +46,7 @@ def emit_trigger():
global trigger
with trigger_lock:
- trigger = datetime.now()
+ trigger = datetime.now(timezone.utc)
class EventHandler(events.FileSystemEventHandler):
@@ -79,13 +79,13 @@ def process_event(self, event: events.FileSystemEvent) -> None:
def _run_ape_test(*pytest_args):
- return run_subprocess(["ape", "test", *[f"{a}" for a in pytest_args]])
+ return run_subprocess(["ape", "test", *[f"{a}" for a in pytest_args]], check=False)
def _run_main_loop(delay: float, *pytest_args: str) -> None:
global trigger
- now = datetime.now()
+ now = datetime.now(timezone.utc)
if trigger and now - trigger > timedelta(seconds=delay):
_run_ape_test(*pytest_args)
diff --git a/src/ape_test/accounts.py b/src/ape_test/accounts.py
index 1abc228ed2..43b89721fe 100644
--- a/src/ape_test/accounts.py
+++ b/src/ape_test/accounts.py
@@ -2,6 +2,8 @@
from functools import cached_property
from typing import cast
+from pydantic import Field
+
from ape.api.accounts import TestAccountAPI, TestAccountContainerAPI
from ape.exceptions import ProviderNotConnectedError
from ape.types import AddressType
@@ -11,7 +13,7 @@
class TestAccountContainer(TestAccountContainerAPI):
- generated_accounts: list["TestAccount"] = []
+ generated_accounts: list["TestAccount"] = Field(default_factory=list)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
diff --git a/src/ape_test/config.py b/src/ape_test/config.py
index f7a030f5e1..45e0e6d057 100644
--- a/src/ape_test/config.py
+++ b/src/ape_test/config.py
@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING, NewType
-from pydantic import NonNegativeInt, field_validator
+from pydantic import Field, NonNegativeInt, field_validator
from pydantic_settings import SettingsConfigDict
from ape.api.config import PluginConfig
@@ -37,7 +37,7 @@ class GasConfig(PluginConfig):
Configuration related to test gas reports.
"""
- exclude: list[GasExclusion] = []
+ exclude: list[GasExclusion] = Field(default_factory=list)
"""
Contract methods patterns to skip. Specify ``contract_name:`` and not
``method_name:`` to skip all methods in the contract. Only specify
@@ -46,7 +46,7 @@ class GasConfig(PluginConfig):
use ``prefix_*`` to skip all items with a certain prefix.
"""
- reports: list[str] = []
+ reports: list[str] = Field(default_factory=list)
"""
Report-types to use. Currently, only supports `terminal`.
"""
@@ -117,7 +117,7 @@ class CoverageConfig(PluginConfig):
Enable reports.
"""
- exclude: list[CoverageExclusion] = []
+ exclude: list[CoverageExclusion] = Field(default_factory=list)
"""
Contract methods patterns to skip. Specify ``contract_name:`` and not
``method_name:`` to skip all methods in the contract. Only specify
diff --git a/src/ape_test/provider.py b/src/ape_test/provider.py
index 9e71017348..390c1832c1 100644
--- a/src/ape_test/provider.py
+++ b/src/ape_test/provider.py
@@ -460,12 +460,14 @@ def set_timestamp(self, new_timestamp: int):
r"timestamp must be strictly later than parent, "
r"but is 0 seconds before\.\n- child\s{2}: (\d*)\n- parent : (\d*)\.\s*"
)
- if match := re.match(pattern, str(err)):
- if groups := match.groups():
- if groups[0].strip() == groups[1].strip():
- # Handle race condition when block headers are the same.
- # Treat as noop, same as pre-check.
- return
+ if (
+ (match := re.match(pattern, str(err)))
+ and (groups := match.groups())
+ and groups[0].strip() == groups[1].strip()
+ ):
+ # Handle race condition when block headers are the same.
+ # Treat as noop, same as pre-check.
+ return
raise ProviderError(f"Failed to time travel: {err}") from err
diff --git a/tests/conftest.py b/tests/conftest.py
index d675f3b20f..8dd90bd54f 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -84,7 +84,7 @@ def validate_cwd(start_dir):
# TODO: Find root cause and fix there.
try:
os.getcwd()
- except Exception:
+ except Exception: # noqa: BLE001
# Change back to project root, hopefully.
os.chdir(start_dir)
@@ -523,7 +523,7 @@ def invoke(self, *subcommand: str, input=None, timeout: int = 40, env: dict | No
if self.project:
try:
here = os.getcwd()
- except Exception:
+ except Exception: # noqa: BLE001
here = None
os.chdir(f"{self.project.path}")
diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py
index da6eb1844e..f5e5bbca03 100644
--- a/tests/functional/conftest.py
+++ b/tests/functional/conftest.py
@@ -323,7 +323,7 @@ def __exit__(self, exc_type, exc_value, exc_tb):
def run(self):
try:
self._run_until_stop(timeout_iterations=self._max_iterations)
- except Exception as err:
+ except Exception as err: # noqa: BLE001
self._exception = err
def stop(self):
diff --git a/tests/functional/geth/test_chain.py b/tests/functional/geth/test_chain.py
index 0dcdac5e3b..0397c8ca0e 100644
--- a/tests/functional/geth/test_chain.py
+++ b/tests/functional/geth/test_chain.py
@@ -1,4 +1,5 @@
import pytest
+
from tests.conftest import geth_process_test
diff --git a/tests/functional/geth/test_network_manager.py b/tests/functional/geth/test_network_manager.py
index d576dc4d94..2ce9d94cc3 100644
--- a/tests/functional/geth/test_network_manager.py
+++ b/tests/functional/geth/test_network_manager.py
@@ -60,9 +60,8 @@ def test_fork_upstream_provider_request_headers(
},
"node": {"request_headers": {"X-Provider": "node"}},
}
- with project.temp_config(**config):
- with networks.fork():
- call = mock_fork_provider.partial_call
+ with project.temp_config(**config), networks.fork():
+ call = mock_fork_provider.partial_call
settings = call[1]["provider_settings"]["fork"]["ethereum"]["sepolia"]
headers = settings["upstream_provider_request_headers"]
diff --git a/tests/functional/geth/test_provider.py b/tests/functional/geth/test_provider.py
index df0e5a98b3..32c0ac0131 100644
--- a/tests/functional/geth/test_provider.py
+++ b/tests/functional/geth/test_provider.py
@@ -308,17 +308,21 @@ def test_connect_using_only_ipc_for_uri_already_connected(project, networks, get
Shows we can remote-connect to a node that is already running when it exposes its IPC path.
"""
ipc_path = geth_provider.ipc_path
- with project.temp_config(node={"ethereum": {"local": {"uri": f"{ipc_path}"}}}):
- with networks.ethereum.local.use_provider("node") as node:
- assert node.uri == f"{ipc_path}"
+ with (
+ project.temp_config(node={"ethereum": {"local": {"uri": f"{ipc_path}"}}}),
+ networks.ethereum.local.use_provider("node") as node,
+ ):
+ assert node.uri == f"{ipc_path}"
@geth_process_test
def test_connect_using_ipc(process_factory_patch, project, networks, geth_provider):
ipc_path = geth_provider.ipc_path
- with project.temp_config(node={"ethereum": {"local": {"uri": f"{ipc_path}"}}}):
- with networks.ethereum.local.use_provider("node") as node:
- assert node.uri == f"{ipc_path}"
+ with (
+ project.temp_config(node={"ethereum": {"local": {"uri": f"{ipc_path}"}}}),
+ networks.ethereum.local.use_provider("node") as node,
+ ):
+ assert node.uri == f"{ipc_path}"
@geth_process_test
@@ -334,46 +338,45 @@ def test_connect_request_headers(project, geth_provider, networks):
},
"node": {"request_headers": {"h3": 3, "USER-AGENT": "custom-geth-client/v100"}},
}
- with project.temp_config(**config):
- with networks.ethereum.local.use_provider("node") as geth:
- w3_provider = geth.web3.provider
- if isinstance(w3_provider, AutoProvider):
- for pot_provider_fn in w3_provider._potential_providers:
- pot_provider = pot_provider_fn()
- if not isinstance(pot_provider, HTTPProvider):
- continue
- else:
- http_provider = pot_provider
-
- elif isinstance(w3_provider, HTTPProvider):
- http_provider = w3_provider
-
- else:
- pytest.fail("Not using HTTP. Please adjust test.")
-
- assert http_provider is not None, "Setup failed - HTTP Provider still None."
-
- assert isinstance(http_provider._request_kwargs, dict)
- actual = http_provider._request_kwargs["headers"]
- assert actual["h0"] == 0 # top-level
- assert actual["h1"] == 1 # ecosystem
- assert actual["h2"] == 2 # network
- assert actual["h3"] == 3 # provider
-
- # Also, assert Ape's default user-agent strings.
- assert actual["User-Agent"].startswith("Ape/")
- assert "Python" in actual["User-Agent"]
- assert "ape-ethereum" in actual["User-Agent"]
- assert "web3.py/" in actual["User-Agent"]
-
- # Show other default headers.
- assert actual["Content-Type"] == "application/json"
-
- # Show appended user-agents strings.
- assert "myapp/2.0" in actual["User-Agent"]
- assert "ETH/1.0" in actual["User-Agent"]
- assert "MyPrivateNetwork/0.0.1" in actual["User-Agent"]
- assert "custom-geth-client/v100" in actual["User-Agent"]
+ with project.temp_config(**config), networks.ethereum.local.use_provider("node") as geth:
+ w3_provider = geth.web3.provider
+ if isinstance(w3_provider, AutoProvider):
+ for pot_provider_fn in w3_provider._potential_providers:
+ pot_provider = pot_provider_fn()
+ if not isinstance(pot_provider, HTTPProvider):
+ continue
+ else:
+ http_provider = pot_provider
+
+ elif isinstance(w3_provider, HTTPProvider):
+ http_provider = w3_provider
+
+ else:
+ pytest.fail("Not using HTTP. Please adjust test.")
+
+ assert http_provider is not None, "Setup failed - HTTP Provider still None."
+
+ assert isinstance(http_provider._request_kwargs, dict)
+ actual = http_provider._request_kwargs["headers"]
+ assert actual["h0"] == 0 # top-level
+ assert actual["h1"] == 1 # ecosystem
+ assert actual["h2"] == 2 # network
+ assert actual["h3"] == 3 # provider
+
+ # Also, assert Ape's default user-agent strings.
+ assert actual["User-Agent"].startswith("Ape/")
+ assert "Python" in actual["User-Agent"]
+ assert "ape-ethereum" in actual["User-Agent"]
+ assert "web3.py/" in actual["User-Agent"]
+
+ # Show other default headers.
+ assert actual["Content-Type"] == "application/json"
+
+ # Show appended user-agents strings.
+ assert "myapp/2.0" in actual["User-Agent"]
+ assert "ETH/1.0" in actual["User-Agent"]
+ assert "MyPrivateNetwork/0.0.1" in actual["User-Agent"]
+ assert "custom-geth-client/v100" in actual["User-Agent"]
@geth_process_test
@@ -883,7 +886,7 @@ def test_start(process_factory_patch, convert, project, geth_provider):
with project.temp_config(test={"balance": amount}):
try:
geth_provider.start()
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass # Exceptions are fine here.
actual = process_factory_patch.call_args[1]["balance"]
@@ -900,7 +903,7 @@ def test_start_from_ws_uri(process_factory_patch, project, geth_provider, key):
geth_provider.provider_settings = {}
try:
geth_provider.start()
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass # Exceptions are fine here.
geth_provider.provider_settings = settings
diff --git a/tests/functional/geth/test_trace.py b/tests/functional/geth/test_trace.py
index e93b552376..416582a9c6 100644
--- a/tests/functional/geth/test_trace.py
+++ b/tests/functional/geth/test_trace.py
@@ -119,9 +119,9 @@ def assert_rich_output(rich_capture: list[str], expected: str):
assert actual_lines, "No output."
output = "\n".join(actual_lines)
- for actual, expected in zip(actual_lines, expected_lines, strict=True):
+ for actual, expected_line in zip(actual_lines, expected_lines, strict=True):
fail_message = f"""\n
- \tPattern: {expected}\n
+ \tPattern: {expected_line}\n
\tLine : {actual}\n
\n
Complete output:
@@ -129,10 +129,10 @@ def assert_rich_output(rich_capture: list[str], expected: str):
"""
try:
- assert re.match(expected, actual), fail_message
+ assert re.match(expected_line, actual), fail_message
except AssertionError:
raise # Let assertion errors raise as normal.
- except Exception as err:
+ except Exception as err: # noqa: BLE001
pytest.fail(f"{fail_message}\n{err}")
actual_len = len(actual_lines)
diff --git a/tests/functional/test_accounts.py b/tests/functional/test_accounts.py
index ef931fe30d..98d6b74f25 100644
--- a/tests/functional/test_accounts.py
+++ b/tests/functional/test_accounts.py
@@ -148,7 +148,7 @@ def test_sign_message_with_prompts(runner, keyfile_account, message):
def test_sign_eip712_message_shows_custom_types(signer):
baz = Baz(addr=signer.address)
foo = Foo(bar=signer.address, baz=baz)
- display_msg, msg = _get_signing_message_with_display(foo)
+ display_msg, _msg = _get_signing_message_with_display(foo)
expected = """
Signing EIP712 Message
Domain
@@ -261,9 +261,8 @@ def test_transfer_with_prompts(runner, receiver, keyfile_account):
assert receipt.receiver == receiver
# "n": don't sign
- with runner.isolation(input="n\n"):
- with pytest.raises(SignatureError):
- keyfile_account.transfer(receiver, "1 gwei")
+ with runner.isolation(input="n\n"), pytest.raises(SignatureError):
+ keyfile_account.transfer(receiver, "1 gwei")
def test_transfer_using_type_0(sender, receiver, convert):
@@ -632,11 +631,13 @@ def test_unlock_from_prompt_retries_and_succeeds(runner, keyfile_account):
def test_unlock_from_prompt_fails_after_three_attempts(runner, keyfile_account):
- with runner.isolation(
- input=f"{INVALID_PASSPHRASE}\n{INVALID_PASSPHRASE}\n{INVALID_PASSPHRASE}\n"
+ with (
+ runner.isolation(
+ input=f"{INVALID_PASSPHRASE}\n{INVALID_PASSPHRASE}\n{INVALID_PASSPHRASE}\n"
+ ),
+ pytest.raises(AccountsError, match="Failed to unlock after 3 attempts."),
):
- with pytest.raises(AccountsError, match="Failed to unlock after 3 attempts."):
- keyfile_account.unlock()
+ keyfile_account.unlock()
assert keyfile_account.locked
@@ -949,7 +950,7 @@ def test_load_public_key_from_keyfile(runner, keyfile_account):
assert (
to_hex(keyfile_account.public_key)
- == "0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5" # noqa: E501
+ == "0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5"
)
# no need for password when loading from the keyfile
assert keyfile_account.public_key
@@ -994,14 +995,12 @@ def test_generate_account_invalid_passphrase():
def test_generate_account_insecure_passphrase(delete_account_after):
short_alias = "shortaccount"
- with delete_account_after(short_alias):
- with pytest.warns(UserWarning, match="short"):
- generate_account(short_alias, "short")
+ with delete_account_after(short_alias), pytest.warns(UserWarning, match="short"):
+ generate_account(short_alias, "short")
simple_alias = "simpleaccount"
- with delete_account_after(simple_alias):
- with pytest.warns(UserWarning, match="simple"):
- generate_account(simple_alias, "simple")
+ with delete_account_after(simple_alias), pytest.warns(UserWarning, match="simple"):
+ generate_account(simple_alias, "simple")
def test_import_account_from_mnemonic(delete_account_after):
@@ -1043,14 +1042,12 @@ def test_import_account_from_mnemonic_invalid_passphrase():
def test_import_account_from_mnemonic_insecure_passphrase(delete_account_after):
short_alias = "iafmshortaccount"
- with delete_account_after(short_alias):
- with pytest.warns(UserWarning, match="short"):
- import_account_from_mnemonic(short_alias, "short", MNEMONIC)
+ with delete_account_after(short_alias), pytest.warns(UserWarning, match="short"):
+ import_account_from_mnemonic(short_alias, "short", MNEMONIC)
simple_alias = "iafmsimpleaccount"
- with delete_account_after(simple_alias):
- with pytest.warns(UserWarning, match="simple"):
- import_account_from_mnemonic(simple_alias, "simple", MNEMONIC)
+ with delete_account_after(simple_alias), pytest.warns(UserWarning, match="simple"):
+ import_account_from_mnemonic(simple_alias, "simple", MNEMONIC)
def test_import_account_from_private_key(delete_account_after):
@@ -1098,14 +1095,12 @@ def test_import_account_from_private_key_invalid_passphrase():
def test_import_account_from_private_key_insecure_passphrase(delete_account_after):
short_alias = "iafpkshortaccount"
- with delete_account_after(short_alias):
- with pytest.warns(UserWarning, match="short"):
- import_account_from_private_key(short_alias, "short", PRIVATE_KEY)
+ with delete_account_after(short_alias), pytest.warns(UserWarning, match="short"):
+ import_account_from_private_key(short_alias, "short", PRIVATE_KEY)
simple_alias = "iafpksimpleaccount"
- with delete_account_after(simple_alias):
- with pytest.warns(UserWarning, match="simple"):
- import_account_from_private_key(simple_alias, "simple", PRIVATE_KEY)
+ with delete_account_after(simple_alias), pytest.warns(UserWarning, match="simple"):
+ import_account_from_private_key(simple_alias, "simple", PRIVATE_KEY)
def test_load(account_manager, keyfile_account):
diff --git a/tests/functional/test_block_container.py b/tests/functional/test_block_container.py
index b7ae5cca1b..5829c7a300 100644
--- a/tests/functional/test_block_container.py
+++ b/tests/functional/test_block_container.py
@@ -143,9 +143,11 @@ def test_poll_blocks_timeout(
):
poller = chain_that_mined_5.blocks.poll_blocks(new_block_timeout=1)
- with pytest.raises(ProviderError, match=r"Timed out waiting for next block."):
- with PollDaemon("blocks", poller, lambda x: None, lambda: False):
- time.sleep(1.5)
+ with (
+ pytest.raises(ProviderError, match=r"Timed out waiting for next block."),
+ PollDaemon("blocks", poller, lambda x: None, lambda: False),
+ ):
+ time.sleep(1.5)
def test_poll_blocks_reorg_with_timeout(
@@ -154,26 +156,28 @@ def test_poll_blocks_reorg_with_timeout(
blocks: Queue = Queue(maxsize=6)
poller = chain_that_mined_5.blocks.poll_blocks(new_block_timeout=1)
- with pytest.raises(ProviderError, match=r"Timed out waiting for next block."):
- with PollDaemon("blocks", poller, blocks.put, blocks.full):
- # Sleep first to ensure listening before mining.
- time.sleep(1)
+ with (
+ pytest.raises(ProviderError, match=r"Timed out waiting for next block."),
+ PollDaemon("blocks", poller, blocks.put, blocks.full),
+ ):
+ # Sleep first to ensure listening before mining.
+ time.sleep(1)
- snapshot = chain_that_mined_5.snapshot()
- chain_that_mined_5.mine(2)
+ snapshot = chain_that_mined_5.snapshot()
+ chain_that_mined_5.mine(2)
- # Wait to allow blocks before re-org to get yielded
- time.sleep(5)
+ # Wait to allow blocks before re-org to get yielded
+ time.sleep(5)
- # Simulate re-org by reverting to the snapshot
- chain_that_mined_5.restore(snapshot)
+ # Simulate re-org by reverting to the snapshot
+ chain_that_mined_5.restore(snapshot)
- # Allow it time to trigger realizing there was a re-org
- time.sleep(1)
- chain_that_mined_5.mine(2)
- time.sleep(1)
+ # Allow it time to trigger realizing there was a re-org
+ time.sleep(1)
+ chain_that_mined_5.mine(2)
+ time.sleep(1)
- chain_that_mined_5.mine(3)
+ chain_that_mined_5.mine(3)
def test_poll_blocks_future(chain_that_mined_5, eth_tester_provider, owner, PollDaemon):
diff --git a/tests/functional/test_chain.py b/tests/functional/test_chain.py
index debf20af8c..5be3918657 100644
--- a/tests/functional/test_chain.py
+++ b/tests/functional/test_chain.py
@@ -1,5 +1,5 @@
from collections import defaultdict
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
import pytest
@@ -151,11 +151,13 @@ def test_snapshot_and_restore_switched_chains(networks, chain):
"""
snapshot = chain.snapshot()
# Switch chains.
- with networks.ethereum.local.use_provider(
- "test", provider_settings={"chain_id": 11191919191991918223773}
+ with (
+ networks.ethereum.local.use_provider(
+ "test", provider_settings={"chain_id": 11191919191991918223773}
+ ),
+ pytest.raises(UnknownSnapshotError),
):
- with pytest.raises(UnknownSnapshotError):
- chain.restore(snapshot)
+ chain.restore(snapshot)
def test_isolate(chain, vyper_contract_instance, owner):
@@ -238,7 +240,7 @@ def test_set_pending_timestamp_failure(chain):
ValueError, match="Cannot give both `timestamp` and `deltatime` arguments together."
):
chain.mine(
- timestamp=int(datetime.now().timestamp() + timedelta(seconds=10).seconds),
+ timestamp=int(datetime.now(timezone.utc).timestamp() + timedelta(seconds=10).seconds),
deltatime=10,
)
diff --git a/tests/functional/test_compilers.py b/tests/functional/test_compilers.py
index 42a4e2dbe3..8408ef24f0 100644
--- a/tests/functional/test_compilers.py
+++ b/tests/functional/test_compilers.py
@@ -231,11 +231,11 @@ def test_compile_in_project_where_source_id_matches_local_project(project, compi
argument = Path(contract.source_id)
# Compile the file with the same name but different content.
- result = [
+ result = next(
x
for x in compilers.compile([argument], project=temp_project)
if x.name == contract.name
- ][0]
+ )
# It should reflect the new content and not the one with the same
# source ID from the local project.
diff --git a/tests/functional/test_config.py b/tests/functional/test_config.py
index 5535b9f6c6..fa78957bd0 100644
--- a/tests/functional/test_config.py
+++ b/tests/functional/test_config.py
@@ -6,7 +6,7 @@
from typing import TYPE_CHECKING, Any
import pytest
-from pydantic import ValidationError
+from pydantic import Field, ValidationError
from pydantic_settings import SettingsConfigDict
from ape.api.config import ApeConfig, ConfigEnum, PluginConfig
@@ -318,20 +318,22 @@ def test_deployments_integer_type_addresses(networks, project):
def test_deployments_bad_ecosystem(project):
deployments = _create_deployments(ecosystem_name="madeup")
- with project.temp_config(deployments=deployments):
- with pytest.raises(
- ConfigError, match=r"Invalid ecosystem 'madeup' in deployments config\."
- ):
- _ = project.config.deployments
+ with (
+ project.temp_config(deployments=deployments),
+ pytest.raises(ConfigError, match=r"Invalid ecosystem 'madeup' in deployments config\."),
+ ):
+ _ = project.config.deployments
def test_deployments_bad_network(project):
deployments = _create_deployments(network_name="madeup")
- with project.temp_config(deployments=deployments):
- with pytest.raises(
+ with (
+ project.temp_config(deployments=deployments),
+ pytest.raises(
ConfigError, match=r"Invalid network 'ethereum:madeup' in deployments config\."
- ):
- _ = project.config.deployments
+ ),
+ ):
+ _ = project.config.deployments
def _create_deployments(
@@ -405,7 +407,7 @@ def test_network_gas_limit_string_config(gas_limit, project):
assert actual.local.gas_limit == "max"
-@pytest.mark.parametrize("gas_limit", (1234, "1234", 0x4D2, "0x4D2"))
+@pytest.mark.parametrize("gas_limit", (1234, "1234", "0x4D2"))
def test_network_gas_limit_numeric_config(gas_limit, project):
eth_config = _sepolia_with_gas_limit(gas_limit)
with project.temp_config(**eth_config):
@@ -459,7 +461,7 @@ class SubConfig(PluginConfig):
bar: int = 1
class MyConfig(PluginConfig):
- sub: dict[str, dict[str, SubConfig]] = {}
+ sub: dict[str, dict[str, SubConfig]] = Field(default_factory=dict)
overrides = {"sub": {"baz": {"test": {"foo": 5}}}}
actual = MyConfig.from_overrides(overrides)
@@ -484,7 +486,7 @@ class MyConfig(PluginConfig):
def test_plugin_config_with_union_dicts(override_0, override_1):
class SubConfig(PluginConfig):
bool_or_dict: bool | dict = True
- dict_or_bool: dict | bool = {}
+ dict_or_bool: dict | bool = Field(default_factory=dict)
config = SubConfig.from_overrides({"bool_or_dict": override_0, "dict_or_bool": override_1})
assert config.bool_or_dict == override_0
@@ -588,7 +590,7 @@ class MyConfig(PluginConfig):
def test_contracts_folder(project):
- with project.temp_config(**{"contracts_folder": "src"}):
+ with project.temp_config(contracts_folder="src"):
assert project.contracts_folder.name == "src"
@@ -601,9 +603,8 @@ def test_contracts_folder_invalid(project):
"""
Show that we can still attempt to use `.get_config()` when config is invalid.
"""
- with pytest.raises(ConfigError):
- with project.temp_config(**{"contracts_folder": {}}):
- _ = project.contracts_folder
+ with pytest.raises(ConfigError), project.temp_config(contracts_folder={}):
+ _ = project.contracts_folder
# Ensure config is fine _after_ something invalid.
assert project.contracts_folder
diff --git a/tests/functional/test_contract_event.py b/tests/functional/test_contract_event.py
index 802c36012c..c7c1857b8f 100644
--- a/tests/functional/test_contract_event.py
+++ b/tests/functional/test_contract_event.py
@@ -321,9 +321,11 @@ def test_poll_logs_timeout(vyper_contract_instance, eth_tester_provider, owner,
new_block_timeout = 1
poller = vyper_contract_instance.NumberChange.poll_logs(new_block_timeout=new_block_timeout)
- with pytest.raises(ProviderError) as err:
- with PollDaemon("logs-timeout", poller, lambda x: None, lambda: False):
- time.sleep(1.5)
+ with (
+ pytest.raises(ProviderError) as err,
+ PollDaemon("logs-timeout", poller, lambda x: None, lambda: False),
+ ):
+ time.sleep(1.5)
assert "Timed out waiting for next block" in str(err.value)
diff --git a/tests/functional/test_contract_method_handler.py b/tests/functional/test_contract_method_handler.py
index e56cf1c4bd..56a4f107e8 100644
--- a/tests/functional/test_contract_method_handler.py
+++ b/tests/functional/test_contract_method_handler.py
@@ -32,9 +32,9 @@ def test_encode_input_list_for_struct(chain, mocker, owner, method_abi_with_stru
def test_info(contract_instance):
# The `setNumber()` ABI defined a NatSpec.
- abi = [x for x in contract_instance.contract_type.abi if getattr(x, "name", "") == "setNumber"][
- 0
- ]
+ abi = next(
+ x for x in contract_instance.contract_type.abi if getattr(x, "name", "") == "setNumber"
+ )
handler = ContractMethodHandler(contract=contract_instance, abis=[abi])
actual = handler.info
expected_doc = (
@@ -58,9 +58,9 @@ def test_info(contract_instance):
def test_info_no_natspec(project, vyper_contract_instance):
# The `myNumber()` ABI does not have a natspec.
- abi = [
+ abi = next(
x for x in project.VyperContract.contract_type.abi if getattr(x, "name", "") == "myNumber"
- ][0]
+ )
handler = ContractMethodHandler(contract=vyper_contract_instance, abis=[abi])
actual = handler.info
assert actual == ""
diff --git a/tests/functional/test_contracts_cache.py b/tests/functional/test_contracts_cache.py
index a962c4123c..11bd3eaa18 100644
--- a/tests/functional/test_contracts_cache.py
+++ b/tests/functional/test_contracts_cache.py
@@ -542,6 +542,37 @@ def test_get_proxy(chain, owner, minimal_proxy_container, vyper_contract_instanc
assert actual == minimal_proxy.contract_type
+def test_get_non_proxy_does_not_cache_negative_result(chain, mocker):
+ address = "0xBEbeBeBEbeBebeBeBEBEbebEBeBeBebeBeBebebe"
+ del chain.contracts.proxy_infos[address]
+ get_proxy_info = mocker.patch(
+ "ape_ethereum.ecosystem.Ethereum.get_proxy_info", return_value=None
+ )
+
+ chain.contracts.get(address, fetch_from_explorer=False)
+ chain.contracts.get(address, fetch_from_explorer=False)
+
+ assert get_proxy_info.call_count == 2
+ assert address not in chain.contracts.proxy_infos.memory
+
+
+def test_get_proxy_caches_positive_result(chain, mocker):
+ address = "0xBEbeBeBEbeBebeBeBEBEbebEBeBeBebeBeBebebe"
+ target = "0x1234567890123456789012345678901234567890"
+ proxy_info = ProxyInfo(type=ProxyType.Minimal, target=target)
+ del chain.contracts.proxy_infos[address]
+ get_proxy_info = mocker.patch(
+ "ape_ethereum.ecosystem.Ethereum.get_proxy_info", return_value=proxy_info
+ )
+ mocker.patch.object(chain.contracts, "_get_proxy_contract_type", return_value=None)
+
+ chain.contracts.get(address, fetch_from_explorer=False)
+ chain.contracts.get(address, fetch_from_explorer=False)
+
+ get_proxy_info.assert_called_once_with(address)
+ assert chain.contracts.proxy_infos[address] == proxy_info
+
+
def test_get_proxy_implementation_missing(chain, owner, project):
"""
Proxy is cached but implementation is missing.
diff --git a/tests/functional/test_default_sender_context_manager.py b/tests/functional/test_default_sender_context_manager.py
index 68b590ee0f..3d48b7fd57 100644
--- a/tests/functional/test_default_sender_context_manager.py
+++ b/tests/functional/test_default_sender_context_manager.py
@@ -60,9 +60,8 @@ def test_nested_default_sender(solidity_contract_instance, owner, accounts, not_
with accounts.use_sender(owner):
tx = solidity_contract_instance.setNumber(1)
assert tx.transaction.sender == owner.address
- with accounts.use_sender(not_owner):
- with reverts():
- solidity_contract_instance.setNumber(2)
+ with accounts.use_sender(not_owner), reverts():
+ solidity_contract_instance.setNumber(2)
solidity_contract_instance.setNumber(3)
assert tx.transaction.sender == owner.address
@@ -71,8 +70,7 @@ def test_nested_default_sender(solidity_contract_instance, owner, accounts, not_
def test_with_error(solidity_contract_instance, account_manager, not_owner):
# safe to use reverts with use_sender and when outside of the use_sender
# there is no remaining default_user set
- with reverts("!authorized"):
- with account_manager.test_accounts.use_sender(not_owner):
- solidity_contract_instance.setNumber(2)
+ with reverts("!authorized"), account_manager.test_accounts.use_sender(not_owner):
+ solidity_contract_instance.setNumber(2)
assert account_manager.default_sender is None
diff --git a/tests/functional/test_dependencies.py b/tests/functional/test_dependencies.py
index 32c611bce6..fcf66107ec 100644
--- a/tests/functional/test_dependencies.py
+++ b/tests/functional/test_dependencies.py
@@ -298,7 +298,7 @@ def test_unpack(smaller_project):
with create_tempdir() as tempdir:
created = list(dep.unpack(tempdir))
assert len(created) > 0
- assert any([x.name == "default" for x in created])
+ assert any(x.name == "default" for x in created)
assert created[0].package_id.endswith("default")
files = [x.name for x in (tempdir / "default" / "local" / "contracts").iterdir()]
diff --git a/tests/functional/test_ecosystem.py b/tests/functional/test_ecosystem.py
index 27567367b5..a847453501 100644
--- a/tests/functional/test_ecosystem.py
+++ b/tests/functional/test_ecosystem.py
@@ -1021,9 +1021,8 @@ def test_networks_multiple_networks_with_same_name(custom_networks_config_dict,
data["networks"]["custom"][0]["name"] = "foonet"
data["networks"]["custom"][1]["name"] = "foonet"
expected = ".*More than one network named 'foonet' in ecosystem 'ethereum'.*"
- with project.temp_config(**data):
- with pytest.raises(NetworkError, match=expected):
- _ = ethereum.networks
+ with project.temp_config(**data), pytest.raises(NetworkError, match=expected):
+ _ = ethereum.networks
def test_getattr(ethereum):
@@ -1101,7 +1100,7 @@ def test_default_network_name_when_not_set_and_no_local_uses_only(
if actual == LOCAL_NETWORK_NAME:
# For some reason, this test is flake-y. Offer more info
# to try and debug when this happens (intermittent CI failure).
- all_nets = ", ".join([x for x in ecosystem.networks.keys()])
+ all_nets = ", ".join([x for x in ecosystem.networks])
pytest.fail(
f"assert '{LOCAL_NETWORK_NAME}' == '{only_network}'. More info below:\n"
f"ecosystem_name={ecosystem.name}\n"
diff --git a/tests/functional/test_exceptions.py b/tests/functional/test_exceptions.py
index 928bbb17e3..d5b7495a8f 100644
--- a/tests/functional/test_exceptions.py
+++ b/tests/functional/test_exceptions.py
@@ -118,7 +118,7 @@ def test_call_with_source_tb_and_not_txn(self, mocker, project_with_contract):
# Have to raise for sys.exc_info() to be available.
try:
raise err
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
def assert_ape_traceback(err_arg):
@@ -137,7 +137,7 @@ def assert_ape_traceback(err_arg):
)
try:
raise err2
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
# No Ape frames are here.
@@ -147,7 +147,7 @@ def assert_ape_traceback(err_arg):
err3 = ContractLogicError(source_traceback=mock_tb, project=project_with_contract)
try:
raise err3
- except Exception:
+ except Exception: # noqa: BLE001, S110
pass
assert_ape_traceback(err3)
diff --git a/tests/functional/test_fixtures.py b/tests/functional/test_fixtures.py
index 3f96ea1bb5..73da471304 100644
--- a/tests/functional/test_fixtures.py
+++ b/tests/functional/test_fixtures.py
@@ -1,4 +1,4 @@
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, ClassVar
import pytest
@@ -88,7 +88,7 @@ def test_isolation_snapshot_id_types(snapshot_id, fixtures):
class IsolationManagerWithCustomSnapshot(IsolationManager):
take_call_count = 0
restore_call_count = 0
- restore_called_with = []
+ restore_called_with: ClassVar[list] = []
def take_snapshot(self) -> "SnapshotID | None":
self.take_call_count += 1
diff --git a/tests/functional/test_network_manager.py b/tests/functional/test_network_manager.py
index 9c2b177083..8968230aeb 100644
--- a/tests/functional/test_network_manager.py
+++ b/tests/functional/test_network_manager.py
@@ -338,9 +338,8 @@ def test_ecosystems_when_custom_has_bad_base_ecosystem(
rf"base-ecosystem class '{eco_plugin_name}'\. "
rf"Are you missing plugin 'ape-{eco_plugin_name}'\?"
)
- with project.temp_config(**data):
- with pytest.raises(NetworkError, match=expected):
- _ = networks.ecosystems
+ with project.temp_config(**data), pytest.raises(NetworkError, match=expected):
+ _ = networks.ecosystems
def test_fork_network_not_forkable(networks, eth_tester_provider):
@@ -348,9 +347,8 @@ def test_fork_network_not_forkable(networks, eth_tester_provider):
Show correct failure when trying to fork the local network.
"""
expected = "Unable to fork network 'local'."
- with pytest.raises(NetworkError, match=expected):
- with networks.fork():
- pass
+ with pytest.raises(NetworkError, match=expected), networks.fork():
+ pass
def test_fork_no_providers(networks, mock_sepolia, disable_fork_providers):
@@ -359,9 +357,8 @@ def test_fork_no_providers(networks, mock_sepolia, disable_fork_providers):
ape-hardhat or ape-foundry installed.
"""
expected = "No providers for network 'sepolia-fork'."
- with pytest.raises(NetworkError, match=expected):
- with networks.fork():
- pass
+ with pytest.raises(NetworkError, match=expected), networks.fork():
+ pass
def test_fork_use_non_existing_provider(networks, mock_sepolia):
@@ -369,9 +366,11 @@ def test_fork_use_non_existing_provider(networks, mock_sepolia):
Show correct failure when specifying a non-existing provider.
"""
expected = "No provider named 'NOT_EXISTS' in network 'sepolia-fork' in ecosystem 'ethereum'.*"
- with pytest.raises(ProviderNotFoundError, match=expected):
- with networks.fork(provider_name="NOT_EXISTS"):
- pass
+ with (
+ pytest.raises(ProviderNotFoundError, match=expected),
+ networks.fork(provider_name="NOT_EXISTS"),
+ ):
+ pass
def test_fork(networks, mock_sepolia, mock_fork_provider):
@@ -440,9 +439,11 @@ def test_fork_with_negative_block_number(
def test_fork_past_genesis(networks, mock_sepolia, mock_fork_provider, eth_tester_provider):
block_id = -10_000_000_000
- with pytest.raises(NetworkError, match="Unable to fork past genesis block."):
- with networks.fork(block_number=block_id):
- pass
+ with (
+ pytest.raises(NetworkError, match="Unable to fork past genesis block."),
+ networks.fork(block_number=block_id),
+ ):
+ pass
def test_getitem(networks):
diff --git a/tests/functional/test_plugins.py b/tests/functional/test_plugins.py
index a0a6f265db..395597855a 100644
--- a/tests/functional/test_plugins.py
+++ b/tests/functional/test_plugins.py
@@ -5,7 +5,7 @@
from ape.api import TransactionAPI
from ape.exceptions import PluginVersionError
from ape.logging import LogLevel
-from ape.managers.plugins import _get_unimplemented_methods_warning
+from ape.managers.plugins import PluginManager, _get_unimplemented_methods_warning
from ape.plugins._utils import CORE_PLUGINS as CORE_PLUGINS_LIST
from ape.plugins._utils import (
ApePluginsRepr,
@@ -119,9 +119,7 @@ def test_all_plugins(self, plugin_metadata, package_names):
class TestPluginMetadata:
- @pytest.mark.parametrize(
- "name", ("ape-foo-bar", "ape-foo-bar", "ape_foo_bar", "foo-bar", "foo_bar")
- )
+ @pytest.mark.parametrize("name", ("ape-foo-bar", "ape_foo_bar", "foo-bar", "foo_bar"))
def test_names(self, name):
metadata = PluginMetadata(name=name)
assert metadata.name == "foo-bar"
@@ -187,14 +185,14 @@ def test_install_str_only_remote(self):
assert metadata.name == "foo"
def test_is_available(self):
- metadata = PluginMetadata(name=list(AVAILABLE_PLUGINS)[0])
+ metadata = PluginMetadata(name=next(iter(AVAILABLE_PLUGINS)))
assert metadata.is_available
metadata = PluginMetadata(name="foobar")
assert not metadata.is_available
@parametrize_pip_cmd
def test_prepare_install(self, pip_command):
- metadata = PluginMetadata(name=list(AVAILABLE_PLUGINS)[0], pip_command=pip_command)
+ metadata = PluginMetadata(name=next(iter(AVAILABLE_PLUGINS)), pip_command=pip_command)
actual = metadata._prepare_install(skip_confirmation=True)
assert actual is not None
arguments = actual.get("args", [])
@@ -214,7 +212,7 @@ def test_prepare_install(self, pip_command):
@parametrize_pip_cmd
def test_prepare_install_upgrade(self, pip_command):
- metadata = PluginMetadata(name=list(AVAILABLE_PLUGINS)[0], pip_command=pip_command)
+ metadata = PluginMetadata(name=next(iter(AVAILABLE_PLUGINS)), pip_command=pip_command)
actual = metadata._prepare_install(upgrade=True, skip_confirmation=True)
assert actual is not None
arguments = actual.get("args", [])
@@ -237,7 +235,7 @@ def test_prepare_install_upgrade(self, pip_command):
@mark_specifiers_less_than_ape
def test_prepare_install_version_smaller_than_ape(self, specifier, ape_caplog):
- metadata = PluginMetadata(name=list(AVAILABLE_PLUGINS)[0], version=specifier)
+ metadata = PluginMetadata(name=next(iter(AVAILABLE_PLUGINS)), version=specifier)
expected = (
r"Unable to install plugin\.\n"
r"Reason: Doing so will downgrade Ape's version\.\n"
@@ -259,7 +257,8 @@ def test_check_installed_python_39(self, mocker, get_dists_patch):
assert metadata.check_installed()
@pytest.mark.parametrize(
- "plugin,expected", [(list(INSTALLED_PLUGINS)[0], True), (list(AVAILABLE_PLUGINS)[0], False)]
+ "plugin,expected",
+ [(next(iter(INSTALLED_PLUGINS)), True), (next(iter(AVAILABLE_PLUGINS)), False)],
)
def test_check_installed_python_310_or_greater(self, plugin, expected):
installed = PluginMetadata(name=plugin)
@@ -416,6 +415,21 @@ def test_get_unimplemented_methods_warning_list_containing_plugin(abstract_metho
assert actual == expected
+def test_warn_not_fully_implemented_error_only_warns_once(mocker, monkeypatch):
+ monkeypatch.setattr(PluginManager, "_unimplemented_plugins", [])
+ warning = mocker.patch("ape.managers.plugins.logger.warning")
+ manager = PluginManager()
+
+ manager._warn_not_fully_implemented_error(TransactionAPI, "incomplete")
+ manager._warn_not_fully_implemented_error(TransactionAPI, "incomplete")
+
+ warning.assert_called_once_with(
+ "'TransactionAPI' from 'incomplete' is not fully implemented. "
+ "Remaining abstract methods: 'serialize_transaction, txn_hash'."
+ )
+ assert manager._unimplemented_plugins == ["incomplete"]
+
+
def test_core_plugins():
# In case any of these happen to be installed, and this feature
# is broken, it will fail. If none are installed, the test will always pass.
diff --git a/tests/functional/test_project.py b/tests/functional/test_project.py
index 6d91acfc45..340b60afb1 100644
--- a/tests/functional/test_project.py
+++ b/tests/functional/test_project.py
@@ -135,7 +135,7 @@ def test_repr():
assert actual == expected
-@pytest.mark.parametrize("name", ("contracts", "contracts"))
+@pytest.mark.parametrize("name", ("contracts"))
def test_contracts_folder_from_config(smaller_project, name):
with smaller_project.temp_config(contracts_folder=name):
assert smaller_project.contracts_folder == smaller_project.path / name
@@ -399,7 +399,7 @@ def test_load_contracts(small_temp_project):
try:
contract_to_rm = small_temp_project.contracts["Other"]
except KeyError:
- existing_contracts = ",".join([k for k in small_temp_project.contracts.keys()])
+ existing_contracts = ",".join([k for k in small_temp_project.contracts])
pytest.fail(f"Contract named 'Other' not found. Existing contracts: {existing_contracts}")
return
@@ -647,12 +647,12 @@ def test_add_compiler_data(project_with_dependency_config):
settings={"outputSelection": {"path/to/Bar.vy": "*"}},
)
project.add_compiler_data([compiler_3])
- comp = [c for c in project.manifest.compilers if c.name == "test" and c.version == "2.0.0"][0]
+ comp = next(c for c in project.manifest.compilers if c.name == "test" and c.version == "2.0.0")
assert "bar" not in comp.contractTypes
assert "path/to/Bar.vy" not in comp.settings["outputSelection"]
- new_comp = [c for c in project.manifest.compilers if c.name == "test" and c.version == "3.0.0"][
- 0
- ]
+ new_comp = next(
+ c for c in project.manifest.compilers if c.name == "test" and c.version == "3.0.0"
+ )
assert "bar" in new_comp.contractTypes
assert "path/to/Bar.vy" in new_comp.settings["outputSelection"]
@@ -1146,7 +1146,7 @@ def test_instance_map(self, project, vyper_contract_instance):
bip122_chain_id = to_hex(project.provider.get_block(0).hash)
expected_uri = f"blockchain://{bip122_chain_id[2:]}/block/"
- for key in project.deployments.instance_map.keys():
+ for key in project.deployments.instance_map:
if key.startswith(expected_uri):
return
diff --git a/tests/functional/test_reverts_context_manager.py b/tests/functional/test_reverts_context_manager.py
index abdfdfa8e7..b4bd819f2d 100644
--- a/tests/functional/test_reverts_context_manager.py
+++ b/tests/functional/test_reverts_context_manager.py
@@ -84,9 +84,8 @@ def test_revert_unexpected_error(error_contract, not_owner):
Test when given a different error type than what was raised.
"""
expected = "Expected error 'OtherError' but was 'Unauthorized'"
- with pytest.raises(AssertionError, match=expected):
- with reverts(error_contract.OtherError):
- error_contract.withdraw(sender=not_owner)
+ with pytest.raises(AssertionError, match=expected), reverts(error_contract.OtherError):
+ error_contract.withdraw(sender=not_owner)
def test_revert_error_inputs(error_contract, not_owner):
@@ -105,9 +104,11 @@ def test_revert_error_unexpected_inputs(error_contract, owner, not_owner):
rf"Expected input 'addr' to be '{owner.address}' but was '{not_owner.address}'\."
r"\nExpected input 'counter' to be '321' but was '123'\."
)
- with pytest.raises(AssertionError, match=expected):
- with reverts(error_contract.Unauthorized, addr=owner.address, counter=321):
- error_contract.withdraw(sender=not_owner)
+ with (
+ pytest.raises(AssertionError, match=expected),
+ reverts(error_contract.Unauthorized, addr=owner.address, counter=321),
+ ):
+ error_contract.withdraw(sender=not_owner)
def test_revert_fails(owner, reverts_contract_instance):
@@ -115,9 +116,8 @@ def test_revert_fails(owner, reverts_contract_instance):
Test that ``AssertionError`` is raised if the supplied revert reason does not match the actual
revert reason.
"""
- with pytest.raises(AssertionError):
- with reverts("one"):
- reverts_contract_instance.revertStrings(0, sender=owner)
+ with pytest.raises(AssertionError), reverts("one"):
+ reverts_contract_instance.revertStrings(0, sender=owner)
def test_revert_pattern_fails(owner, reverts_contract_instance):
@@ -125,9 +125,8 @@ def test_revert_pattern_fails(owner, reverts_contract_instance):
Test that ``AssertionError`` is raised if the actual revert reason does not match the supplied
revert pattern.
"""
- with pytest.raises(AssertionError):
- with reverts(re.compile(r"[^zero]+")):
- reverts_contract_instance.revertStrings(0, sender=owner)
+ with pytest.raises(AssertionError), reverts(re.compile(r"[^zero]+")):
+ reverts_contract_instance.revertStrings(0, sender=owner)
def test_revert_partial_fails(owner, reverts_contract_instance):
@@ -135,6 +134,5 @@ def test_revert_partial_fails(owner, reverts_contract_instance):
Test that ``AssertionError`` is raised if the supplied revert reason does not match the actual
revert reason exactly.
"""
- with pytest.raises(AssertionError):
- with reverts("ze"):
- reverts_contract_instance.revertStrings(0, sender=owner)
+ with pytest.raises(AssertionError), reverts("ze"):
+ reverts_contract_instance.revertStrings(0, sender=owner)
diff --git a/tests/functional/test_test.py b/tests/functional/test_test.py
index 80e9b208ef..fb81cd0ce2 100644
--- a/tests/functional/test_test.py
+++ b/tests/functional/test_test.py
@@ -525,4 +525,4 @@ def test_watch(mocker):
# NOTE: We had a bug once where the args it received were not strings.
# (wasn't deconstructing), so this check is important.
- run_subprocess_patch.assert_called_once_with(["ape", "test", "-s"])
+ run_subprocess_patch.assert_called_once_with(["ape", "test", "-s"], check=False)
diff --git a/tests/functional/utils/test_abi.py b/tests/functional/utils/test_abi.py
index f9e3bc30d0..1f76a91c02 100644
--- a/tests/functional/utils/test_abi.py
+++ b/tests/functional/utils/test_abi.py
@@ -128,7 +128,7 @@ def test_get_and_set_item(self, struct):
def test_is_equal(self, struct):
# Show struct equality works when props are the same.
- assert struct == struct # even self
+ assert struct == struct # even self # noqa: PLR0124
new_struct = deepcopy(struct)
assert struct == new_struct
# Show changing a property makes them unequal.
diff --git a/tests/integration/cli/conftest.py b/tests/integration/cli/conftest.py
index b86c658511..4d198433fb 100644
--- a/tests/integration/cli/conftest.py
+++ b/tests/integration/cli/conftest.py
@@ -1,6 +1,7 @@
from importlib import import_module
from pathlib import Path
from shutil import copytree
+from typing import ClassVar
import pytest
@@ -91,7 +92,7 @@ def project_dir_map():
"""
class ProjectDirCache:
- project_map: dict[str, Path] = {}
+ project_map: ClassVar[dict[str, Path]] = {}
def load(self, name: str) -> Path:
base_path = Path(__file__).parent / "projects"
diff --git a/tests/integration/cli/projects/only-dependencies/__init__.py b/tests/integration/cli/projects/only-dependencies/__init__.py
index e69de29bb2..b7b77a8a5a 100644
--- a/tests/integration/cli/projects/only-dependencies/__init__.py
+++ b/tests/integration/cli/projects/only-dependencies/__init__.py
@@ -0,0 +1 @@
+# noqa: N999
diff --git a/tests/integration/cli/projects/only-dependencies/dependency_in_project_only/__init__.py b/tests/integration/cli/projects/only-dependencies/dependency_in_project_only/__init__.py
index e69de29bb2..b7b77a8a5a 100644
--- a/tests/integration/cli/projects/only-dependencies/dependency_in_project_only/__init__.py
+++ b/tests/integration/cli/projects/only-dependencies/dependency_in_project_only/__init__.py
@@ -0,0 +1 @@
+# noqa: N999
diff --git a/tests/integration/cli/projects/script/scripts/error_cli.py b/tests/integration/cli/projects/script/scripts/error_cli.py
index 95dee78277..e38543e0f7 100644
--- a/tests/integration/cli/projects/script/scripts/error_cli.py
+++ b/tests/integration/cli/projects/script/scripts/error_cli.py
@@ -4,4 +4,4 @@
@click.command(short_help="Use a subcommand")
def cli():
local_variable = "test foo bar" # noqa: F841
- raise Exception("Expected exception")
+ raise Exception("Expected exception") # noqa: TRY002
diff --git a/tests/integration/cli/projects/script/scripts/error_main.py b/tests/integration/cli/projects/script/scripts/error_main.py
index 2681ad6fa3..293f208ded 100644
--- a/tests/integration/cli/projects/script/scripts/error_main.py
+++ b/tests/integration/cli/projects/script/scripts/error_main.py
@@ -5,4 +5,4 @@ def main():
local_variable = "test foo bar" # noqa: F841
provider = ape.chain.provider
provider.set_timestamp(123123123123123123)
- raise Exception("Expected exception")
+ raise Exception("Expected exception") # noqa: TRY002
diff --git a/tests/integration/cli/projects/script/scripts/error_no_def.py b/tests/integration/cli/projects/script/scripts/error_no_def.py
index 77e9d3c04a..cecb6a975a 100644
--- a/tests/integration/cli/projects/script/scripts/error_no_def.py
+++ b/tests/integration/cli/projects/script/scripts/error_no_def.py
@@ -3,4 +3,4 @@
local_variable = "test foo bar"
provider = ape.chain.provider
provider.set_timestamp(123123123123123123)
-raise Exception("Expected exception")
+raise Exception("Expected exception") # noqa: TRY002
diff --git a/tests/integration/cli/projects/test/tests/test_networks.py b/tests/integration/cli/projects/test/tests/test_networks.py
index e83a719b4e..e2dca256ab 100644
--- a/tests/integration/cli/projects/test/tests/test_networks.py
+++ b/tests/integration/cli/projects/test/tests/test_networks.py
@@ -11,7 +11,7 @@ def test_networks_context_does_not_disconnect(networks):
context = networks.ethereum.local.use_provider("test")
error_output = f"""
ape: {shutil.which("ape")},
- connected_providers: {", ".join([pid for pid in context.connected_providers.keys()])},
+ connected_providers: {", ".join([pid for pid in context.connected_providers])},
"""
assert False, f"Provider lost connection!\n{error_output}"
diff --git a/tests/integration/cli/test_accounts.py b/tests/integration/cli/test_accounts.py
index 7c3d05fe3e..0339b336b6 100644
--- a/tests/integration/cli/test_accounts.py
+++ b/tests/integration/cli/test_accounts.py
@@ -116,7 +116,7 @@ def test_import_invalid_private_key(ape_cli, runner):
result = runner.invoke(
ape_cli,
("accounts", "import", ALIAS),
- input="\n".join(["0xhello", PASSWORD, PASSWORD]),
+ input=f"0xhello\n{PASSWORD}\n{PASSWORD}",
)
assert result.exit_code == 1, result.output
assert_failure(result, "Key can't be imported: Non-hexadecimal digit found")
@@ -158,7 +158,7 @@ def test_import_mnemonic_default_hdpath(
result = runner.invoke(
ape_cli,
("accounts", "import", "--use-mnemonic", ALIAS, "-v", "INFO"),
- input="\n".join([MNEMONIC, PASSWORD, PASSWORD]),
+ input=f"{MNEMONIC}\n{PASSWORD}\n{PASSWORD}",
)
assert result.exit_code == 0, result.output
assert temp_account_mnemonic_default_hdpath.address in result.output
@@ -175,7 +175,7 @@ def test_import_mnemonic_custom_hdpath(
result = runner.invoke(
ape_cli,
("accounts", "import", ALIAS, "--use-mnemonic", "--hd-path", CUSTOM_HDPATH, "-v", "INFO"),
- input="\n".join([MNEMONIC, PASSWORD, PASSWORD]),
+ input=f"{MNEMONIC}\n{PASSWORD}\n{PASSWORD}",
)
assert result.exit_code == 0, result.output
assert temp_account_mnemonic_custom_hdpath.address in result.output
@@ -189,7 +189,7 @@ def test_export(ape_cli, runner, temp_keyfile, keyfile_account, accounts):
result = runner.invoke(
ape_cli,
("accounts", "export", ALIAS, "-v", "INFO"),
- input="\n".join([PASSWORD, PASSWORD]),
+ input=f"{PASSWORD}\n{PASSWORD}",
)
assert result.exit_code == 0, result.output
# NOTE: temp_keyfile uses the same address as the keyfile account.
@@ -205,7 +205,7 @@ def test_import_invalid_mnemonic(ape_cli, runner):
result = runner.invoke(
ape_cli,
("accounts", "import", "--use-mnemonic", ALIAS),
- input="\n".join([INVALID_MNEMONIC, PASSWORD, PASSWORD]),
+ input=f"{INVALID_MNEMONIC}\n{PASSWORD}\n{PASSWORD}",
)
assert result.exit_code == 1, result.output
assert_failure(result, "Seed phrase can't be imported")
@@ -221,7 +221,7 @@ def test_generate_default(ape_cli, runner, temp_keyfile_path):
result = runner.invoke(
ape_cli,
("accounts", "generate", ALIAS, "-v", "INFO"),
- input="\n".join(["random entropy", show_mnemonic, PASSWORD, PASSWORD]),
+ input=f"random entropy\n{show_mnemonic}\n{PASSWORD}\n{PASSWORD}",
)
assert result.exit_code == 0, result.output
mnemonic = extract_mnemonic(result.output)
@@ -241,7 +241,7 @@ def test_generate_hide_mnemonic_prompt(ape_cli, runner, temp_keyfile_path):
result = runner.invoke(
ape_cli,
("accounts", "generate", ALIAS, "-v", "INFO"),
- input="\n".join(["random entropy", show_mnemonic, PASSWORD, PASSWORD]),
+ input=f"random entropy\n{show_mnemonic}\n{PASSWORD}\n{PASSWORD}",
)
assert result.exit_code == 0, result.output
assert "Newly generated mnemonic is" not in result.output
@@ -257,7 +257,7 @@ def test_generate_hide_mnemonic_option(ape_cli, runner, temp_keyfile_path):
result = runner.invoke(
ape_cli,
("accounts", "generate", ALIAS, "--hide-mnemonic", "-v", "INFO"),
- input="\n".join(["random entropy", PASSWORD, PASSWORD]),
+ input=f"random entropy\n{PASSWORD}\n{PASSWORD}",
)
assert result.exit_code == 0, result.output
assert "Newly generated mnemonic is" not in result.output
@@ -275,7 +275,7 @@ def test_generate_24_words(ape_cli, runner, temp_keyfile_path):
result = runner.invoke(
ape_cli,
("accounts", "generate", ALIAS, "--word-count", word_count, "-v", "INFO"),
- input="\n".join(["random entropy", show_mnemonic, PASSWORD, PASSWORD]),
+ input=f"random entropy\n{show_mnemonic}\n{PASSWORD}\n{PASSWORD}",
)
assert result.exit_code == 0, result.output
mnemonic = extract_mnemonic(result.output)
@@ -295,7 +295,7 @@ def test_generate_custom_hdpath(ape_cli, runner, temp_keyfile_path):
result = runner.invoke(
ape_cli,
("accounts", "generate", ALIAS, "--hd-path", CUSTOM_HDPATH, "-v", "INFO"),
- input="\n".join(["random entropy", show_mnemonic, PASSWORD, PASSWORD]),
+ input=f"random entropy\n{show_mnemonic}\n{PASSWORD}\n{PASSWORD}",
)
assert result.exit_code == 0, result.output
mnemonic = extract_mnemonic(result.output)
@@ -326,7 +326,7 @@ def test_generate_24_words_and_custom_hdpath(ape_cli, runner, temp_keyfile_path)
"-v",
"INFO",
),
- input="\n".join(["random entropy", show_mnemonic, PASSWORD, PASSWORD]),
+ input=f"random entropy\n{show_mnemonic}\n{PASSWORD}\n{PASSWORD}",
)
assert result.exit_code == 0, result.output
mnemonic = extract_mnemonic(result.output)
@@ -345,7 +345,7 @@ def invoke_generate():
return runner.invoke(
ape_cli,
("accounts", "generate", ALIAS),
- input="\n".join(["random entropy", show_mnemonic, PASSWORD, PASSWORD]),
+ input=f"random entropy\n{show_mnemonic}\n{PASSWORD}\n{PASSWORD}",
)
result = invoke_generate()
@@ -426,7 +426,7 @@ def test_authorizations_cli(ape_cli, runner, keyfile_account, geth_contract):
GETH_URI,
),
catch_exceptions=False,
- input="\n".join(["y", PASSWORD, "y", "y"]),
+ input=f"y\n{PASSWORD}\ny\ny",
)
assert result.exit_code == 0, result.output
@@ -457,7 +457,7 @@ def test_authorizations_cli(ape_cli, runner, keyfile_account, geth_contract):
GETH_URI,
),
catch_exceptions=False,
- input="\n".join(["y", PASSWORD, "y", "y"]),
+ input=f"y\n{PASSWORD}\ny\ny",
)
assert result.exit_code == 0, result.output
diff --git a/tests/integration/cli/test_init.py b/tests/integration/cli/test_init.py
index e2f3f9ffea..85873ebbfe 100644
--- a/tests/integration/cli/test_init.py
+++ b/tests/integration/cli/test_init.py
@@ -26,7 +26,7 @@ def test_init_success(ape_cli, runner, integ_project):
os.chdir(str(project_folder_path))
try:
- result = runner.invoke(ape_cli, ("init",), input="\n".join(["init_success"]))
+ result = runner.invoke(ape_cli, ("init",), input="init_success")
assert result.exit_code == 0, result.output
# checks if the directory exist
@@ -65,7 +65,7 @@ def test_fail_all_files_and_folders_exist(ape_cli, runner, integ_project):
if not folder.exists():
folder.mkdir(exist_ok=False)
- result = runner.invoke(ape_cli, ("init", "-v", "info"), input="\n".join(["init_fail"]))
+ result = runner.invoke(ape_cli, ("init", "-v", "info"), input="init_fail")
# checks if the directory existence
assert result.exit_code == 0, result.output
assert "contracts' exists" in result.output
diff --git a/tests/integration/cli/test_pm.py b/tests/integration/cli/test_pm.py
index 4a308606f7..0c7f2817b6 100644
--- a/tests/integration/cli/test_pm.py
+++ b/tests/integration/cli/test_pm.py
@@ -33,7 +33,7 @@ def test_install_path_to_local_package(pm_runner, integ_project):
name = path.stem
result = pm_runner.invoke("install", path.as_posix(), "--name", project_name)
assert result.exit_code == 0, result.output
- assert f"Package '{path.as_posix()}' installed."
+ assert f"Package '{project_name}@local' installed." in result.output
# Ensure was installed correctly.
assert integ_project.dependencies[name]["local"]
@@ -77,7 +77,7 @@ def test_install_github_dependency_with_version(pm_runner):
timeout=300,
)
assert result.exit_code == 0, result.output
- assert "Package 'openzeppelin@4.6.0' installed."
+ assert "Package 'openzeppelin@4.6.0' installed." in result.output
@run_once
@@ -93,7 +93,7 @@ def test_install_github_dependency_with_ref(pm_runner):
timeout=300,
)
assert result.exit_code == 0, result.output
- assert "Package 'OpenZeppelin@master' installed."
+ assert "Package 'OpenZeppelin@master' installed." in result.output
@skip_projects_except("with-contracts")
@@ -119,7 +119,7 @@ def test_install_config_override(pm_runner, integ_project):
@skip_projects_except("with-contracts")
def test_install_package_id(pm_runner, integ_project):
pm_runner.project = integ_project
- package_id = [x for x in integ_project.dependencies][0].package_id
+ package_id = next(iter(integ_project.dependencies)).package_id
result = pm_runner.invoke("install", package_id)
assert result.exit_code == 0, result.output
@@ -127,7 +127,7 @@ def test_install_package_id(pm_runner, integ_project):
@skip_projects_except("with-contracts")
def test_install_name(pm_runner, integ_project):
pm_runner.project = integ_project
- name = [x for x in integ_project.dependencies][0].name
+ name = next(iter(integ_project.dependencies)).name
result = pm_runner.invoke("install", name)
assert result.exit_code == 0, result.output
@@ -135,7 +135,7 @@ def test_install_name(pm_runner, integ_project):
@skip_projects_except("with-contracts")
def test_install_name_with_version_in_id(pm_runner, integ_project):
pm_runner.project = integ_project
- dependency = [x for x in integ_project.dependencies][0]
+ dependency = next(iter(integ_project.dependencies))
result = pm_runner.invoke("install", f"{dependency.name}@{dependency.version}")
assert result.exit_code == 0, result.output
assert f"Package '{dependency.name}@{dependency.version}' installed." in result.output
@@ -144,7 +144,7 @@ def test_install_name_with_version_in_id(pm_runner, integ_project):
@skip_projects_except("with-contracts")
def test_install_name_with_version_flag(pm_runner, integ_project):
pm_runner.project = integ_project
- dependency = [x for x in integ_project.dependencies][0]
+ dependency = next(iter(integ_project.dependencies))
result = pm_runner.invoke("install", f"{dependency.name}", "--version", dependency.version)
assert result.exit_code == 0, result.output
assert f"Package '{dependency.name}@{dependency.version}' installed." in result.output
diff --git a/tests/integration/cli/test_test.py b/tests/integration/cli/test_test.py
index b907338daa..4782b29721 100644
--- a/tests/integration/cli/test_test.py
+++ b/tests/integration/cli/test_test.py
@@ -63,7 +63,7 @@ def path_check():
# 'Something something' temp path.
try:
os.getcwd()
- except Exception:
+ except Exception: # noqa: BLE001
os.chdir(f"{Path(__file__).parent}")
@@ -379,7 +379,7 @@ def test_gas_flag_exclusions_set_in_config(
for _ in range(3):
try:
geth_account.transfer(geth_account, "1 wei") # Force a clean block.
- except Exception as err:
+ except Exception as err: # noqa: BLE001
# Sometimes tests are weird, since running async.
# So give this a chance a few times before bailing out...
logger.error(f"Failure bumping block: {err}")
diff --git a/tests/unit/api/test_api_init.py b/tests/unit/api/test_api_init.py
index ee194232b2..88f8990360 100644
--- a/tests/unit/api/test_api_init.py
+++ b/tests/unit/api/test_api_init.py
@@ -1,7 +1,7 @@
import pytest
-import ape.api as api
-import ape.api.providers as providers
+from ape import api
+from ape.api import providers
@pytest.mark.parametrize(
From 7cc18bc7d3f27af2b9b77d3645e8ae3310a612c3 Mon Sep 17 00:00:00 2001
From: beardthelion <56458543+beardthelion@users.noreply.github.com>
Date: Fri, 21 Aug 2026 15:29:19 -0500
Subject: [PATCH 2/2] fix: resolve overloaded methods by argument type, not
just count (#2800)
Co-authored-by: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com>
---
src/ape/contracts/base.py | 71 ++++++++---
src/ape_ethereum/multicall/handlers.py | 4 +-
tests/functional/test_contract_instance.py | 136 +++++++++++++++++++++
tests/functional/test_multicall.py | 37 ++++++
4 files changed, 233 insertions(+), 15 deletions(-)
diff --git a/src/ape/contracts/base.py b/src/ape/contracts/base.py
index 5a7be464f1..553c6420cd 100644
--- a/src/ape/contracts/base.py
+++ b/src/ape/contracts/base.py
@@ -7,6 +7,7 @@
from typing import TYPE_CHECKING, Any
import click
+from eth_abi.exceptions import EncodingError
from eth_pydantic_types import HexBytes
from eth_utils import to_hex
from ethpm_types.abi import EventABI
@@ -26,9 +27,11 @@
ContractDataError,
ContractLogicError,
ContractNotFoundError,
+ ConversionError,
CustomError,
MethodNonPayableError,
MissingDeploymentBytecodeError,
+ MissingStructFieldError,
)
from ape.logging import get_rich_console, logger
from ape.types.events import ContractLog, LogFilter, MockContractLog
@@ -230,8 +233,25 @@ def _get_info(self, enrich: bool = False) -> str:
return "\n\n".join(numeric_infos)
+ def _can_encode(self, abi: "MethodABI", args: tuple | list) -> bool:
+ # Probe whether args encode against this overload's input types. Used only to pick
+ # among same-arity overloads; encoding is pure (no network). Only a conversion or
+ # encoding failure means the args do not fit this overload. Anything else is a real
+ # error and propagates, so a bug in conversion is not silently read as a no-match.
+ try:
+ arguments = self.conversion_manager.convert_method_args(abi, args)
+ self.provider.network.ecosystem.encode_calldata(abi, *arguments)
+ except (ConversionError, MissingStructFieldError, EncodingError) as err:
+ logger.debug(
+ f"Overload '{abi.selector}' does not accept the given arguments "
+ f"({type(err).__name__}: {err})."
+ )
+ return False
+
+ return True
+
def encode_input(self, *args) -> HexBytes:
- selected_abi = _select_method_abi(self.abis, args)
+ selected_abi = _select_method_abi(self.abis, args, encode_check=self._can_encode)
arguments = self.conversion_manager.convert_method_args(selected_abi, args)
ecosystem = self.provider.network.ecosystem
encoded_calldata = ecosystem.encode_calldata(selected_abi, *arguments)
@@ -293,7 +313,7 @@ def _validate_is_contract(self):
class ContractCallHandler(ContractMethodHandler):
def __call__(self, *args, **kwargs) -> Any:
self._validate_is_contract()
- selected_abi = _select_method_abi(self.abis, args)
+ selected_abi = _select_method_abi(self.abis, args, encode_check=self._can_encode)
arguments = self.conversion_manager.convert_method_args(selected_abi, args)
return ContractCall(
@@ -347,23 +367,46 @@ def estimate_gas_cost(self, *args, **kwargs) -> int:
reported in the fee-currency's smallest unit, e.g. Wei.
"""
- selected_abi = _select_method_abi(self.abis, args)
+ selected_abi = _select_method_abi(self.abis, args, encode_check=self._can_encode)
arguments = self.conversion_manager.convert_method_args(selected_abi, args)
return self.transact.estimate_gas_cost(*arguments, **kwargs)
-def _select_method_abi(abis: list["MethodABI"], args: tuple | list) -> "MethodABI":
+def _select_method_abi(
+ abis: list["MethodABI"],
+ args: tuple | list,
+ encode_check: "Callable[[MethodABI, tuple | list], bool] | None" = None,
+) -> "MethodABI":
+ """
+ Pick the overload to call for ``args``.
+
+ Selection rules, in order:
+
+ 1. Only overloads whose input count matches ``len(args)`` are candidates. No candidate
+ raises :class:`~ape.exceptions.ArgumentsLengthError`.
+ 2. With more than one candidate and an ``encode_check``, the first candidate (in ABI
+ declaration order) whose input types the args encode to wins. This is a behavior
+ change: selection used to be by count alone, so an ambiguous overload set resolved
+ to the last declared one regardless of the argument types (#2670). Encoding is pure,
+ so the probe costs no network calls.
+ 3. Otherwise (a single candidate, no ``encode_check``, or no candidate the args fit) the
+ last matching overload wins, which is the prior behavior.
+
+ When several overloads all accept the same args, e.g. ``1`` against both ``uint256``
+ and ``int256``, rule 2 falls back on ABI declaration order. That is deterministic but
+ arbitrary, since the ABI itself does not say which one the caller meant.
+ """
args = args or []
- selected_abi = None
- for abi in abis:
- inputs = abi.inputs or []
- if len(args) == len(inputs):
- selected_abi = abi
-
- if not selected_abi:
+ matching_abis = [abi for abi in abis if len(abi.inputs or []) == len(args)]
+ if not matching_abis:
raise ArgumentsLengthError(len(args), inputs=abis)
- return selected_abi
+ if len(matching_abis) > 1 and encode_check is not None:
+ for abi in matching_abis:
+ if encode_check(abi, args):
+ return abi
+
+ return matching_abis[-1]
class ContractTransaction(ManagerAccessMixin):
@@ -454,7 +497,7 @@ def estimate_gas_cost(self, *args, **kwargs) -> int:
int: The estimated cost of gas to execute the transaction
reported in the fee-currency's smallest unit, e.g. Wei.
"""
- selected_abi = _select_method_abi(self.abis, args)
+ selected_abi = _select_method_abi(self.abis, args, encode_check=self._can_encode)
arguments = self.conversion_manager.convert_method_args(selected_abi, args)
txn = self.as_transaction(*arguments, **kwargs)
return self.provider.estimate_gas_cost(txn)
@@ -479,7 +522,7 @@ def __call__(self, *args, **kwargs) -> "ReceiptAPI":
def _as_transaction(self, *args) -> ContractTransaction:
self._validate_is_contract()
- selected_abi = _select_method_abi(self.abis, args)
+ selected_abi = _select_method_abi(self.abis, args, encode_check=self._can_encode)
return ContractTransaction(
abi=selected_abi,
address=self.contract.address,
diff --git a/src/ape_ethereum/multicall/handlers.py b/src/ape_ethereum/multicall/handlers.py
index 5406adcee3..504927bd70 100644
--- a/src/ape_ethereum/multicall/handlers.py
+++ b/src/ape_ethereum/multicall/handlers.py
@@ -183,7 +183,9 @@ def add(self, call: ContractMethodHandler, *args, **kwargs):
raise InvalidOption("value")
super().add(call, *args, **kwargs)
- self.abis.append(_select_method_abi(call.abis, args))
+ # Must match the overload `super().add` encoded the calldata with, or an overloaded
+ # call is decoded against the wrong return types.
+ self.abis.append(_select_method_abi(call.abis, args, encode_check=call._can_encode))
return self
@property
diff --git a/tests/functional/test_contract_instance.py b/tests/functional/test_contract_instance.py
index 0d3f8564c1..2f4e72bbf8 100644
--- a/tests/functional/test_contract_instance.py
+++ b/tests/functional/test_contract_instance.py
@@ -20,6 +20,8 @@
ProviderNotConnectedError,
SignatureError,
)
+from ape.logging import LogLevel
+from ape.managers.converters import ConversionManager
from ape.types.address import AddressType
from ape_ethereum.transactions import TransactionStatusEnum, TransactionType
@@ -874,6 +876,140 @@ def test_encode_input_transaction(contract_instance, calldata):
assert actual == expected
+BYTES_ARRAY_OVERLOAD = [{"name": "updateData", "type": "bytes[]"}]
+UINT_OVERLOAD = [{"name": "updateDataSize", "type": "uint256"}]
+INT_OVERLOAD = [{"name": "updateDataSize", "type": "int256"}]
+
+
+@pytest.fixture
+def overloaded_contract(chain):
+ # Overload selection happens at encode time, which is pure, so a hand-crafted ABI at an
+ # arbitrary address exercises it without deploying anything. Each call needs its own
+ # address because instance_at caches by address.
+ def make(overloads, address):
+ abi = [
+ {
+ "type": "function",
+ "name": "getUpdateFee",
+ "stateMutability": "view",
+ "inputs": inputs,
+ "outputs": [{"name": "", "type": "uint256"}],
+ }
+ for inputs in overloads
+ ]
+ return chain.contracts.instance_at(
+ address,
+ abi=abi,
+ detect_proxy=False,
+ fetch_from_explorer=False,
+ fetch_from_disk=False,
+ )
+
+ return make
+
+
+def selector_for(ecosystem, handler, canonical_type):
+ abi = next(a for a in handler.abis if a.inputs[0].canonical_type == canonical_type)
+ return ecosystem.get_method_selector(abi)
+
+
+def test_encode_input_overloaded_selects_convertible_overload(
+ eth_tester_provider, overloaded_contract
+):
+ # Regression for #2670: when overloads share the same argument count, the arg count
+ # cannot tell them apart. An empty list is convertible to bytes[] but not uint256, so
+ # the bytes[] overload must be selected rather than the last-declared one.
+ ecosystem = eth_tester_provider.network.ecosystem
+
+ # Both declaration orders must resolve to the bytes[] overload for a list argument.
+ for overloads, address in (
+ ([BYTES_ARRAY_OVERLOAD, UINT_OVERLOAD], "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"),
+ ([UINT_OVERLOAD, BYTES_ARRAY_OVERLOAD], "0x1111111111111111111111111111111111111111"),
+ ):
+ handler = overloaded_contract(overloads, address).getUpdateFee
+ calldata = handler.encode_input([])
+ assert calldata[:4] == selector_for(ecosystem, handler, "bytes[]")
+
+
+def test_encode_input_overloaded_ambiguous_picks_first_declared(
+ eth_tester_provider, overloaded_contract
+):
+ # 1 encodes against both uint256 and int256, so the probe cannot separate them and
+ # selection falls back on declaration order. That choice is arbitrary, but it has to
+ # stay stable, so pin it.
+ ecosystem = eth_tester_provider.network.ecosystem
+
+ for overloads, address in (
+ ([UINT_OVERLOAD, INT_OVERLOAD], "0x2222222222222222222222222222222222222222"),
+ ([INT_OVERLOAD, UINT_OVERLOAD], "0x3333333333333333333333333333333333333333"),
+ ):
+ handler = overloaded_contract(overloads, address).getUpdateFee
+ first_declared = overloads[0][0]["type"]
+ calldata = handler.encode_input(1)
+ assert calldata[:4] == selector_for(ecosystem, handler, first_declared)
+
+
+def test_encode_input_overloaded_out_of_bounds_value(eth_tester_provider, overloaded_contract):
+ # A value that converts fine but is too wide for an overload's type fails at encode time
+ # rather than conversion time, and that is still a no-match, not an error.
+ ecosystem = eth_tester_provider.network.ecosystem
+ small_overload = [{"name": "updateDataSize", "type": "uint8"}]
+ handler = overloaded_contract(
+ [small_overload, UINT_OVERLOAD], "0x7777777777777777777777777777777777777777"
+ ).getUpdateFee
+
+ calldata = handler.encode_input(999999)
+
+ assert calldata[:4] == selector_for(ecosystem, handler, "uint256")
+
+
+def test_encode_input_overloaded_struct_field_mismatch(eth_tester_provider, overloaded_contract):
+ # A dict that is missing an overload's struct fields means the args do not fit that
+ # overload, same as a conversion failure, so the probe must treat it as a no-match
+ # rather than letting the error escape.
+ ecosystem = eth_tester_provider.network.ecosystem
+ ab_struct = [
+ {
+ "name": "data",
+ "type": "tuple",
+ "components": [{"name": "a", "type": "uint256"}, {"name": "b", "type": "uint256"}],
+ }
+ ]
+ x_struct = [{"name": "data", "type": "tuple", "components": [{"name": "x", "type": "uint256"}]}]
+ handler = overloaded_contract(
+ [ab_struct, x_struct], "0x4444444444444444444444444444444444444444"
+ ).getUpdateFee
+
+ calldata = handler.encode_input({"x": 1})
+
+ assert calldata[:4] == selector_for(ecosystem, handler, "(uint256)")
+
+
+def test_can_encode_propagates_unexpected_errors(mocker, eth_tester_provider, overloaded_contract):
+ # The probe only means "these args do not fit this overload". A bug inside conversion is
+ # not that, so it must surface instead of being read as a failed match.
+ handler = overloaded_contract(
+ [BYTES_ARRAY_OVERLOAD, UINT_OVERLOAD], "0x5555555555555555555555555555555555555555"
+ ).getUpdateFee
+ mocker.patch.object(ConversionManager, "convert_method_args", side_effect=RuntimeError("boom"))
+
+ with pytest.raises(RuntimeError, match="boom"):
+ handler._can_encode(handler.abis[0], ([],))
+
+
+def test_can_encode_logs_probe_failure(eth_tester_provider, overloaded_contract, ape_caplog):
+ handler = overloaded_contract(
+ [BYTES_ARRAY_OVERLOAD, UINT_OVERLOAD], "0x6666666666666666666666666666666666666666"
+ ).getUpdateFee
+ uint_abi = next(a for a in handler.abis if a.inputs[0].canonical_type == "uint256")
+
+ with ape_caplog.at_level(LogLevel.DEBUG):
+ assert handler._can_encode(uint_abi, ([],)) is False
+
+ message = next(m for m in ape_caplog.messages if "getUpdateFee(uint256" in m)
+ assert "ConversionError" in message
+
+
def test_decode_input_call(contract_instance, calldata):
method = contract_instance.setNumber.call
actual = method.decode_input(calldata)
diff --git a/tests/functional/test_multicall.py b/tests/functional/test_multicall.py
index a3a80415f5..009ac85eaf 100644
--- a/tests/functional/test_multicall.py
+++ b/tests/functional/test_multicall.py
@@ -74,3 +74,40 @@ def test_returndata(returndata_key):
call = Call()
call._result = [result] # type: ignore
assert call.returnData[0] == output
+
+
+def test_add_overloaded_records_the_encoded_overload(chain, eth_tester_provider):
+ # The recorded ABI decodes the return data, so it has to be the same overload that
+ # encoded the calldata. Both go through type-aware selection or neither does.
+ # bytes[] first so that count-only selection would land on the uint256 overload and
+ # disagree with the calldata.
+ abi = [
+ {
+ "type": "function",
+ "name": "getUpdateFee",
+ "stateMutability": "view",
+ "inputs": [{"name": "updateData", "type": "bytes[]"}],
+ "outputs": [{"name": "", "type": "uint256"}],
+ },
+ {
+ "type": "function",
+ "name": "getUpdateFee",
+ "stateMutability": "view",
+ "inputs": [{"name": "updateDataSize", "type": "uint256"}],
+ "outputs": [{"name": "", "type": "uint256"}],
+ },
+ ]
+ contract = chain.contracts.instance_at(
+ "0x8888888888888888888888888888888888888888",
+ abi=abi,
+ detect_proxy=False,
+ fetch_from_explorer=False,
+ fetch_from_disk=False,
+ )
+ call = Call()
+
+ call.add(contract.getUpdateFee, [])
+
+ ecosystem = eth_tester_provider.network.ecosystem
+ assert call.abis[0].inputs[0].canonical_type == "bytes[]"
+ assert call.calls[0]["callData"][:4] == ecosystem.get_method_selector(call.abis[0])