Brownie to Ape Migration
Brownie is no longer actively maintained. The Brownie README directs Python Ethereum developers to Ape Framework. This guide documents a practical migration path for Brownie projects moving to Ape and references ApeWorX/ape issue #640, which originally tracked Brownie project migration support.
Automated Migration with ApeShift
Run ApeShift to apply deterministic Brownie-to-Ape rewrites, validation, reports, and manual-review TODOs.
npx apeshift migrate .
ApeShift is published in the Codemod registry: https://app.codemod.com/registry/apeshift
Note on tooling: ApeShift is distributed through the Codemod registry, which uses npm packaging for codemod distribution. The migration itself operates on Python files using ast-grep rules that parse and transform Python AST directly. No JavaScript runtime is involved in the user’s Python project code. A Python-native wrapper may be added in the future.
Imports
Brownie scripts often import accounts, contracts, config, and networks from brownie. Migrated Ape scripts import public Ape APIs and access contracts through project.
# Before
from brownie import accounts, config, SimpleStorage, network
# After
from ape import accounts, config, networks, project
Official docs: Contracts
Accounts
Use accounts.test_accounts for local generated accounts and accounts.load() for named account aliases. The alias must be imported by the user before live-network use.
# Before
if network.show_active() == "development":
return accounts[0]
return accounts.add(config["wallets"]["from_key"])
# After
if networks.provider.network.name == "development":
return accounts.test_accounts[0]
return accounts.add(config["wallets"]["from_key"]) # TODO(apeshift): accounts.add(key) not valid in Ape; use accounts.load("account-name") after: ape accounts import <name>
Official docs: Accounts
Contract Deployment and Transactions
Brownie transaction dictionaries become explicit Ape keyword arguments such as sender= and value=.
# Before
simple_storage = SimpleStorage.deploy({"from": account})
transaction = simple_storage.store(15, {"from": account})
# After
simple_storage = project.SimpleStorage.deploy(sender=account)
transaction = simple_storage.store(15, sender=account)
For payable calls:
# Before
tx = fund_me.fund({"from": account, "value": entrance_fee})
# After
tx = fund_me.fund(sender=account, value=entrance_fee)
Official docs: Contracts
Networks
Brownie’s active-network helper maps to Ape’s active provider network metadata.
# Before
print(f"The active network is {network.show_active()}")
# After
print(f"The active network is {networks.provider.network.name}")
Official docs: Networks
Testing and Reverts
Brownie revert helpers and exceptions map to Ape testing helpers and exceptions.
# Before
with brownie.reverts("Ownable: caller is not the owner"):
fund_me.withdraw({"from": bad_actor})
# After
with ape.reverts("Ownable: caller is not the owner"):
fund_me.withdraw(sender=bad_actor)
# Before
with pytest.raises(exceptions.VirtualMachineError):
fund_me.withdraw({"from": bad_actor})
# After
from ape.exceptions import ContractLogicError
with pytest.raises(ContractLogicError):
fund_me.withdraw(sender=bad_actor)
Official docs: Testing
Testing: pytest Fixtures
Ape tests use pytest fixtures from the ape-test plugin.
In Ape, contract types are not injected as pytest fixtures.
Use the project fixture and access contracts as project.ContractName.
Remove Brownie’s fn_isolation fixture when migrating tests:
# Before
@pytest.fixture(autouse=True)
def isolate(fn_isolation):
pass
# After
# Remove entirely — Ape handles test isolation through its pytest plugin.
Use Ape’s accounts fixture with project for deployments:
# Before
def test_deploy(Token, accounts):
account = accounts[0]
token = Token.deploy({"from": account})
# After
def test_deploy(project, accounts):
account = accounts[0]
token = project.Token.deploy(sender=account)
Update contract fixture patterns the same way:
# Before
@pytest.fixture
def token(Token, accounts):
return Token.deploy({"from": accounts[0]})
# After
@pytest.fixture
def token(project, accounts):
return project.Token.deploy(sender=accounts[0])
If tests manually use chain snapshots, chain.snapshot() remains similar, but Brownie’s chain.revert() should become Ape’s chain.restore().
# Before
chain.snapshot()
chain.revert()
# After
chain.snapshot()
chain.restore()
Brownie provides a web3 pytest fixture for direct Web3.py access in tests.
Ape does not provide a web3 fixture.
If direct Web3 access is needed, use the active provider:
# Before
def test_block_number(web3):
assert web3.eth.block_number >= 0
# After
from ape import networks
def test_block_number():
assert networks.provider.web3.eth.block_number >= 0
Official docs: Testing
Config Files
Brownie configuration values should move into Ape’s ape-config.yaml structure. Wallet private keys are not copied into config; import an Ape account alias instead.
# Before: brownie-config.yaml
dependencies:
- smartcontractkit/[email protected]
compiler:
solc:
remappings:
- "@chainlink=smartcontractkit/[email protected]"
wallets:
from_key: ${PRIVATE_KEY}
networks:
development:
verify: false
# After: ape-config.yaml
name: migrated-ape-project
plugins:
- name: solidity
solidity:
version: 0.8.20
import_remapping:
- "@chainlink=smartcontractkit/[email protected]"
ethereum:
default_network: local
# Import keys with `ape accounts import <alias>` and use `accounts.load("<alias>")`.
networks:
development:
verify: false
Official docs: Config
Real-World Results
Repo |
Files |
Patterns Before |
Patterns After |
Auto% |
FP |
FN |
Syntax OK |
Runtime Safe |
Classification |
|---|---|---|---|---|---|---|---|---|---|
brownie_simple_storage |
4 |
12 |
1 |
92% |
0 |
0 |
✅ |
✅ |
PASS |
brownie_fund_me |
7 |
23 |
1 |
96% |
0 |
1 |
✅ |
✅ |
DEPENDENCY_SOURCE_LAYOUT_BLOCKED |
chainlink-mix |
21 |
104 |
4 |
96% |
0 |
7 |
✅ |
✅ |
DEPENDENCY_SOURCE_LAYOUT_BLOCKED |
brownie-nft-course |
18 |
76 |
11 |
86% |
0 |
3 |
✅ |
✅ |
DEPENDENCY_SOURCE_LAYOUT_BLOCKED |
token-mix |
6 |
64 |
2 |
97% |
0 |
0 |
✅ |
✅ |
PROJECT_TEST_SETUP_REVIEW |
Combined |
56 |
279 |
19 |
93% |
0 |
11 |
✅ |
✅ |
Real Ape runtime validation was also run locally with Ape 0.8.48:
Repo |
Ape Compile |
Ape Test |
Notes |
|---|---|---|---|
brownie_simple_storage |
✅ PASS |
✅ 2 passed |
Fully validated |
brownie_fund_me |
❌ FAIL |
❌ 2 failed |
Chainlink dependency source layout unresolved |
chainlink-mix |
❌ FAIL |
❌ collection error |
Chainlink dependency source layout and import-time provider access unresolved |
brownie-nft-course |
❌ FAIL |
❌ collection error |
Chainlink/OpenZeppelin dependency source layout unresolved |
token-mix |
✅ PASS |
❌ collection error |
Brownie |
What Remains Manual
web3.eth.contract(...)is deterministically rewritten toContract(address, abi=...)with a TODO if the ABI source is unclear.accounts.load()aliases require a human to choose and import the account name.Complex event filters receive TODO comments with exact guidance.
from brownie.network import priority_feereceives a TODO because Ape has no safe deterministic equivalent.