From 1960bda50f3f70b80280b79c4dac93e7facddad0 Mon Sep 17 00:00:00 2001 From: antazoey Date: Mon, 18 Dec 2023 15:30:47 -0600 Subject: [PATCH] docs: fix a myriad of docs issues for 0.7 (#1784) --- docs/conf.py | 8 ++++ docs/methoddocs/contracts.md | 8 ---- docs/methoddocs/types.md | 13 ++++-- docs/userguides/accounts.md | 59 +++++++------------------- docs/userguides/clis.md | 2 +- docs/userguides/networks.md | 4 +- docs/userguides/testing.md | 2 + setup.py | 2 +- src/ape/api/accounts.py | 52 +++++++++++++++-------- src/ape/api/address.py | 8 ++-- src/ape/api/compiler.py | 10 +++-- src/ape/api/config.py | 12 ++++++ src/ape/api/convert.py | 2 +- src/ape/api/explorers.py | 12 +++--- src/ape/api/networks.py | 66 +++++++++++++++++------------ src/ape/api/providers.py | 14 +++--- src/ape/cli/options.py | 25 ++++++----- src/ape/contracts/base.py | 8 ++-- src/ape/managers/accounts.py | 2 +- src/ape/managers/chain.py | 2 +- src/ape/managers/compilers.py | 13 +++--- src/ape/managers/converters.py | 20 +++++---- src/ape/managers/project/manager.py | 10 +++-- src/ape/managers/project/types.py | 2 +- src/ape/types/address.py | 4 +- src/ape/types/signatures.py | 2 +- src/ape_ethereum/ecosystem.py | 6 +-- src/ape_geth/provider.py | 8 ++-- src/ape_networks/_cli.py | 8 +++- tests/functional/test_config.py | 14 +++++- 30 files changed, 225 insertions(+), 173 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 50da54b097..a78597b980 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -109,6 +109,14 @@ def get_versions() -> List[str]: return [f"v{x}" for x in sorted_version_objs] +def setup(app): + from ape import project + + # Disable accessing contracts via __getattr__ + # For ease of sphinx. + project._getattr_contracts = False + + html_context = { "fixpath": fixpath, "get_versions": get_versions, diff --git a/docs/methoddocs/contracts.md b/docs/methoddocs/contracts.md index 9a14d62b28..a5f1804c5f 100644 --- a/docs/methoddocs/contracts.md +++ b/docs/methoddocs/contracts.md @@ -31,11 +31,3 @@ :special-members: :exclude-members: __repr__, __weakref__, __metaclass__, __call__, __init__ ``` - -```{eval-rst} -.. autoclass:: ape.contracts.base.ContractError - :members: - :show-inheritance: - :special-members: - :exclude-members: __repr__, __weakref__, __metaclass__, __call__, __init__ -``` diff --git a/docs/methoddocs/types.md b/docs/methoddocs/types.md index a1499a0ed3..24a8e27b5c 100644 --- a/docs/methoddocs/types.md +++ b/docs/methoddocs/types.md @@ -1,10 +1,10 @@ # ape.types -## Miscellaneous +## Address ```{eval-rst} -.. automodule:: ape.types - :members: BlockID, AddressType, BaseContractLog, ContractLog, MockContractLog +.. automodule:: ape.types.address + :members: AddressType, RawAddress ``` ## Signatures @@ -14,3 +14,10 @@ :members: :show-inheritance: ``` + +## Miscellaneous + +```{eval-rst} +.. automodule:: ape.types + :members: BlockID, BaseContractLog, ContractLog, MockContractLog +``` diff --git a/docs/userguides/accounts.md b/docs/userguides/accounts.md index b2f3cecd3e..64ac1b7ced 100644 --- a/docs/userguides/accounts.md +++ b/docs/userguides/accounts.md @@ -14,6 +14,9 @@ To learn more about Ethereum accounts, see [the Ethereum documentation](https:// ## Test Accounts Ape ships with pytest fixtures to assist in writing your tests. + +### Use test accounts in tests + Pre-funded test accounts are accessible via the [accounts fixture](./testing.html#accounts-fixture). ```python @@ -22,6 +25,8 @@ def test_my_contract_method(accounts): ... ``` +### Use test accounts outside of tests + To access the same prefunded accounts in your scripts or console, use the root `accounts` object and the [test_accounts](../methoddocs/managers.html#ape.managers.accounts.AccountManager.test_accounts) property: ```python @@ -41,9 +46,13 @@ test: **WARN**: NEVER put a seed phrase with real funds here. The accounts generated from this seed are solely for testing and debugging purposes. +### Creating new test accounts + You can create a new test account by doing the following: ```python +from ape import accounts + account = accounts.test_accounts.generate_test_account() ``` @@ -76,7 +85,7 @@ with accounts.use_sender(a): # a is a `TestAccountAPI` object When using live networks, you need to get your accounts into Ape. Ape ships with a keyfile accounts plugin to assist with this. -All the available CLI commands for this accounts plugin can be found [here](../commands/accounts.html). +All the available CLI commands for this account's plugin can be found [here](../commands/accounts.html). For example, you can [generate](../commands/accounts.html#accounts-generate) an account: @@ -85,27 +94,19 @@ ape accounts generate ``` Ape will prompt you for entropy which is used to increase randomness when creating your account. - Ape will then prompt you whether you want to show your mnemonic. - If you do not want to see your mnemonic you can select `n`. - -Alternatively you can use the `--hide-mnemonic` option to skip the prompt. +Alternatively, you can use the `--hide-mnemonic` option to skip the prompt. ```bash ape accounts generate --hide-mnemonic ``` If you elected to show your mnemonic Ape will then show you your newly generated mnemonic. - Ape will then prompt you for a passphrase which you will need to enter twice to confirm. - This passphrase is used to encrypt your account on disk, for extra security. - You will be prompted for it each time you load your account, so make sure to remember it. - After entering the passphrase Ape will then show you your new account address, HDPath, and account alias. - If you want to use a custom HDPath, use the `--hd-path` option: ```bash @@ -113,7 +114,6 @@ ape accounts generate --hd-path ``` If you do not use the `--hd-path` option, Ape will use the default HDPath of (Ethereum network, first account). - If you want to use a custom mnemonic phrase word length, use the `--word-count` option: ```bash @@ -121,19 +121,15 @@ ape accounts generate --word-count ``` If you do not use the `--word-count` option, Ape will use the default word count of 12. - You can use all of these together or separately to control the way Ape creates and displays your account information. - -If you already have an account and you wish to import it into Ape (say, from Metamask), you can use the [import command](../commands/accounts.html#accounts-import): +If you already have an account and wish to import it into Ape (say, from Metamask), you can use the [import command](../commands/accounts.html#accounts-import): ```bash ape accounts import ``` It will prompt you for the private key. - If you need help exporting your private key from Metamask, see [this guide](https://metamask.zendesk.com/hc/en-us/articles/360015289632-How-to-export-an-account-s-private-key). - You can also import accounts from mnemonic seed by using the `--use-mnemonic` flag: ```bash @@ -141,9 +137,7 @@ ape accounts import --use-mnemonic ``` It will then prompt you for the [mnemonic seed](https://en.bitcoin.it/wiki/Seed_phrase). - If you need help finding your mnemonic seed (Secret Recovery Phrase) in Metamask, see [this guide](https://metamask.zendesk.com/hc/en-us/articles/360015290032-How-to-reveal-your-Secret-Recovery-Phrase). - In addition, you can also use a custom HDPath by using the `--hd-path` option: ```bash @@ -151,9 +145,7 @@ ape accounts import --use-mnemonic --hd-path ``` If you use the `--hd-path` option, you will need to pass the [HDPath](https://help.myetherwallet.com/en/articles/5867305-hd-wallets-and-derivation-paths) you'd like to use as an argument in the command. - If you do not use the `--hd-path` option, Ape will use the default HDPath of (Ethereum network, first account). - You can also [export](../commands/accounts.html#accounts-export) the private key of an account: ```bash @@ -161,11 +153,8 @@ ape accounts export ``` Ape will ask you for the password to the account and then give you the private key of that account. - You can then use that private key with [import](../commands/accounts.html#accounts-import). - You can alternatively load the private key into [Metamask wallet](https://metamask.zendesk.com/hc/en-us/articles/360015489331-How-to-import-an-account#h_01G01W07NV7Q94M7P1EBD5BYM4). - Then, in your scripts, you can [load](../methoddocs/managers.html#ape.managers.accounts.AccountManager.load) an account: ```python @@ -194,26 +183,8 @@ with accounts.use_sender(a): # a is a `AccountAPI` object ## Automation -If you use your keyfile accounts in automation, such as CI/CD, you may need to programmatically unlock them and enable autosign. -**WARNING**: We don't recommend using this approach but it is possible due to sometimes being needed. -Ensure you are using a secure environment and are aware of what you are doing. - -```python -from ape import accounts -from eth_account.messages import encode_defunct - -account = accounts.load("") -account.set_autosign(True, passphrase="") - -# Now, you will not be prompted to sign messages or transactions -message = encode_defunct(text="Hello Apes!") -signature = account.sign_message(message) -``` - -## Keyfile Passphrase Environment Variable (more secure) - -Another, more secure approach is to use an environment variable. -Set your passphrase in an environment variable by following this template: +If you use your keyfile accounts in automation, such as CI/CD, you may need to programmatically unlock them and enable auto-sign. +To do this, use a special environment variable for the account's passphrase: ```bash export APE_ACCOUNTS__PASSPHRASE="a" @@ -234,6 +205,8 @@ message = encode_defunct(text="Hello Apes!") signature = account.sign_message(message) ``` +**NOTE**: Alternatively, you may use the `passphrase=` kwarg on methods `account.set_autosign()` and `account.unlock()`, but we highly recommend using the environment variable approach to avoid accidentally leaking your passphrase. + ## Hardware Wallets Because of the plugin system in Ape, we are able to support other types of accounts including hardware wallet accounts. diff --git a/docs/userguides/clis.md b/docs/userguides/clis.md index 395a9f6fe6..803078d659 100644 --- a/docs/userguides/clis.md +++ b/docs/userguides/clis.md @@ -116,7 +116,7 @@ def cmd(provider): @click.command(cls=ConnectedProviderCommand) def cmd(): - click.echo("Using params is from ConnectedProviderCommand is optional") + click.echo("Using params from ConnectedProviderCommand is optional") ``` ## Account Tools diff --git a/docs/userguides/networks.md b/docs/userguides/networks.md index 4a554cd5b0..57ed6ee975 100644 --- a/docs/userguides/networks.md +++ b/docs/userguides/networks.md @@ -27,7 +27,7 @@ as a short-cut for `ethereum:local:foundry`. ## Configuring Networks Change network defaults using your project's `ape-config.yaml` file. -The following configuration changes the default ecosystem, network, and provider such that if you omitted the `--network` option on network-bound commands, it would use the value `::`. +The following configuration changes the default ecosystem, network, and provider such that if you omitted the `--network` option on connected-provider commands, it would use the value `::`. ```yaml default_ecosystem: @@ -53,7 +53,7 @@ You may use one of: - `"max"` - the maximum block gas limit is used - A number or numeric string, base 10 or 16 (e.g. `1234`, `"1234"`, `0x1234`, `"0x1234"`) -For the local network configuration, the default is `"max"`. Otherwise it is `"auto"`. +For the local network configuration, the default is `"max"`. Otherwise, it is `"auto"`. ## Local Network diff --git a/docs/userguides/testing.md b/docs/userguides/testing.md index b20f0ce79b..d05f7816c3 100644 --- a/docs/userguides/testing.md +++ b/docs/userguides/testing.md @@ -32,6 +32,8 @@ If the sender of the transaction is not the owner, the transaction will fail to This is an example of how that test may look: ```python +import ape + def test_authorization(my_contract, owner, not_owner): my_contract.set_owner(sender=owner) assert owner == my_contract.owner() diff --git a/setup.py b/setup.py index 7b46bf07f2..cb76336b2a 100644 --- a/setup.py +++ b/setup.py @@ -123,7 +123,7 @@ "web3[tester]>=6.12.0,<7", # ** Dependencies maintained by ApeWorX ** "eip712>=0.2.3,<0.4", - "ethpm-types>=0.6.4,<0.7", + "ethpm-types>=0.6.5,<0.7", "eth_pydantic_types>=0.1.0a5,<0.2", "evm-trace>=0.1.2", ], diff --git a/src/ape/api/accounts.py b/src/ape/api/accounts.py index cba98d5260..75a1c991a5 100644 --- a/src/ape/api/accounts.py +++ b/src/ape/api/accounts.py @@ -63,7 +63,7 @@ def sign_message(self, msg: Any, **signer_options) -> Optional[MessageSignature] Args: msg (Any): The message to sign. Account plugins can handle various types of messages. - For example, :class:`~ape_accounts.accouns.KeyfileAccount` can handle + For example, :class:`~ape_accounts.accounts.KeyfileAccount` can handle :class:`~ape.types.signatures.SignableMessage`, str, int, and bytes. See these `docs `__ # noqa: E501 @@ -85,9 +85,9 @@ def sign_transaction(self, txn: TransactionAPI, **signer_options) -> Optional[Tr Returns: :class:`~ape.api.transactions.TransactionAPI` (optional): A signed transaction. - The `TransactionAPI` returned by this method may not correspond to `txn` given as input, - however returning a properly-formatted transaction here is meant to be executed. - Returns `None` if the account does not have a transaction it wishes to execute. + The ``TransactionAPI`` returned by this method may not correspond to ``txn`` given as + input, however returning a properly-formatted transaction here is meant to be executed. + Returns ``None`` if the account does not have a transaction it wishes to execute. """ @@ -113,7 +113,7 @@ def call( txn (:class:`~ape.api.transactions.TransactionAPI`): An invoke-transaction. send_everything (bool): ``True`` will send the difference from balance and fee. Defaults to ``False``. - private (bool): ``True`` with use the + private (bool): ``True`` will use the :meth:`~ape.api.providers.ProviderAPI.send_private_transaction` method. **signer_options: Additional kwargs given to the signer to modify the signing operation. @@ -165,8 +165,8 @@ def call( def transfer( self, account: Union[str, AddressType, BaseAddress], - value: Union[str, int, None] = None, - data: Union[bytes, str, None] = None, + value: Optional[Union[str, int]] = None, + data: Optional[Union[bytes, str]] = None, private: bool = False, **kwargs, ) -> ReceiptAPI: @@ -178,12 +178,13 @@ def transfer( and using a provider that does not support private transactions. Args: - account (str): The account to send funds to. - value (str): The amount to send. - data (str): Extra data to include in the transaction. + account (Union[str, AddressType, BaseAddress]): The receiver of the funds. + value (Optional[Union[str, int]]): The amount to send. + data (Optional[Union[bytes, str]]): Extra data to include in the transaction. private (bool): ``True`` asks the provider to make the transaction - private. For example, EVM providers uses the RPC ``eth_sendPrivateTransaction`` - to achieve this. Local providers may ignore this value. + private. For example, EVM providers typically use the RPC + ``eth_sendPrivateTransaction`` to achieve this. Local providers may ignore + this value. Returns: :class:`~ape.api.transactions.ReceiptAPI` @@ -218,8 +219,8 @@ def deploy( deploying and a provider must be active. Args: - contract (:class:`~ape.contracts.ContractContainer`): - The type of contract to deploy. + contract (:class:`~ape.contracts.base.ContractContainer`): The type of contract to + deploy. publish (bool): Set to ``True`` to attempt explorer contract verification. Defaults to ``False``. @@ -249,6 +250,19 @@ def deploy( return instance def declare(self, contract: "ContractContainer", *args, **kwargs) -> ReceiptAPI: + """ + Deploy the "blueprint" of a contract type. For EVM providers, this likely means + using `EIP-5202 `__, which is implemented + in the core ``ape-ethereum`` plugin. + + Args: + contract (:class:`~ape.contracts.base.ContractContainer`): The contract container + to declare. + + Returns: + :class:`~ape.api.transactions.ReceiptAPI`: The receipt of the declare transaction. + """ + transaction = self.provider.network.ecosystem.encode_contract_blueprint( contract.contract_type, *args, **kwargs ) @@ -274,7 +288,8 @@ def check_signature( data (Union[:class:`~ape.types.signatures.SignableMessage`, :class:`~ape.api.transactions.TransactionAPI`]): # noqa: E501 The message or transaction to verify. signature (Optional[:class:`~ape.types.signatures.MessageSignature`]): - The signature to check. + The signature to check. Defaults to ``None`` and is not needed when the first + argument is a transaction class. Returns: bool: ``True`` if the data was signed by this account. ``False`` otherwise. @@ -341,6 +356,7 @@ class AccountContainerAPI(BaseInterfaceModel): """ data_folder: Path + account_type: Type[AccountAPI] @property @@ -374,7 +390,7 @@ def __getitem__(self, address: AddressType) -> AccountAPI: Get an account by address. Args: - address (``AddressType``): The address to get. The type is an alias to + address (:class:`~ape.types.address.AddressType`): The address to get. The type is an alias to `ChecksumAddress `__. # noqa: E501 Raises: @@ -436,7 +452,7 @@ def __delitem__(self, address: AddressType): NotImplementError: When not overridden within a plugin. Args: - address (``AddressType``): The address of the account to delete. + address (:class:`~ape.types.address.AddressType`): The address of the account to delete. """ raise NotImplementedError("Must define this method to use `container.remove(acct)`.") @@ -448,7 +464,7 @@ def __contains__(self, address: AddressType) -> bool: IndexError: When the given account address is not in this container. Args: - address (``AddressType``): An account address. + address (:class:`~ape.types.address.AddressType`): An account address. Returns: bool: ``True`` if ``ape`` manages the account with the given address. diff --git a/src/ape/api/address.py b/src/ape/api/address.py index 7a2b83ea70..70dd2545ac 100644 --- a/src/ape/api/address.py +++ b/src/ape/api/address.py @@ -45,7 +45,8 @@ def address(self) -> AddressType: def __eq__(self, other: object) -> bool: """ - Compares :class:`~ape.api.BaseAddress` / ``str`` objects by converting to ``AddressType``. + Compares :class:`~ape.api.BaseAddress` or ``str`` objects by converting to + :class:`~ape.types.address.AddressType`. Returns: bool: comparison result @@ -84,7 +85,8 @@ def __str__(self) -> str: def __call__(self, **kwargs) -> "ReceiptAPI": """ - Call this address directly. + Call this address directly. For contracts, this may mean invoking their + default handler. Args: **kwargs: Transaction arguments, such as ``sender`` or ``data``. @@ -194,7 +196,7 @@ def address(self) -> AddressType: The raw address type. Returns: - ``AddressType``: An alias to + :class:`~ape.types.address.AddressType`: An alias to `ChecksumAddress `__. # noqa: E501 """ diff --git a/src/ape/api/compiler.py b/src/ape/api/compiler.py index b673dd1a91..60bcc604be 100644 --- a/src/ape/api/compiler.py +++ b/src/ape/api/compiler.py @@ -35,7 +35,9 @@ class CompilerAPI(BaseInterfaceModel): @property @abstractmethod def name(self) -> str: - ... + """ + The name of the compiler. + """ @property def config(self) -> PluginConfig: @@ -182,8 +184,8 @@ def supports_source_tracing(self) -> bool: def enrich_error(self, err: ContractLogicError) -> ContractLogicError: """ - Enrich a contract logic error using compiler information, such - known PC locations for compiler runtime errors, such as math errors. + Enrich a contract logic error using compiler information, such as + known PC locations for compiler runtime errors. Args: err (:class:`~ape.exceptions.ContractLogicError`): The exception @@ -223,7 +225,7 @@ def flatten_contract(self, path: Path, **kwargs) -> Content: # type: ignore[emp Args: path (``pathlib.Path``): The source path of the contract. - **kwargs: Additional compiler-specific settings. See specific + **kwargs (Any): Additional compiler-specific settings. See specific compiler plugins when applicable. Returns: diff --git a/src/ape/api/config.py b/src/ape/api/config.py index 69535987e4..f5ddad1d7a 100644 --- a/src/ape/api/config.py +++ b/src/ape/api/config.py @@ -12,6 +12,18 @@ class ConfigEnum(str, Enum): A configuration `Enum `__ type. Use this to limit the values of a config item, such as colors ``"RED"``, ``"BLUE"``, ``"GREEN"``, rather than any arbitrary ``str``. + + Usage example:: + + class MyEnum(ConfigEnum): + FOO = "FOO" + BAR = "BAR" + + class MyConfig(PluginConfig): + my_enum: MyEnum + + model = MyConfig(my_enum="FOO") + """ diff --git a/src/ape/api/convert.py b/src/ape/api/convert.py index e2048badfb..b60a05970f 100644 --- a/src/ape/api/convert.py +++ b/src/ape/api/convert.py @@ -13,7 +13,7 @@ def is_convertible(self, value: Any) -> bool: :meth:`ape.api.convert.ConverterAPI.convert`. Args: - value (str): The value to check. + value (Any): The value to check. Returns: bool: ``True`` when the given value can be converted. diff --git a/src/ape/api/explorers.py b/src/ape/api/explorers.py index cd59f00961..df3c19e433 100644 --- a/src/ape/api/explorers.py +++ b/src/ape/api/explorers.py @@ -22,10 +22,10 @@ def get_address_url(self, address: AddressType) -> str: Get an address URL, such as for a transaction. Args: - address (``AddressType``): The address to get the URL for. + address (:class:`~ape.types.address.AddressType`): The address. Returns: - str + str: The URL. """ @abstractmethod @@ -37,16 +37,16 @@ def get_transaction_url(self, transaction_hash: str) -> str: transaction_hash (str): The transaction hash. Returns: - str + str: The URL. """ @abstractmethod def get_contract_type(self, address: AddressType) -> Optional[ContractType]: """ - Get the contract type for a given address if it has been published in an explorer. + Get the contract type for a given address if it has been published to this explorer. Args: - address (``AddressType``): The contract address. + address (:class:`~ape.types.address.AddressType`): The contract address. Returns: Optional[``ContractType``]: If not published, returns ``None``. @@ -58,5 +58,5 @@ def publish_contract(self, address: AddressType): Publish a contract to the explorer. Args: - address (``AddressType``): The address of the deployed contract. + address (:class:`~ape.types.address.AddressType`): The address of the deployed contract. """ diff --git a/src/ape/api/networks.py b/src/ape/api/networks.py index 0f0b8c73ac..25780f3778 100644 --- a/src/ape/api/networks.py +++ b/src/ape/api/networks.py @@ -79,7 +79,7 @@ class EcosystemAPI(BaseInterfaceModel): data_folder: Path """The path to the ``.ape`` directory.""" - request_header: dict + request_header: Dict """A shareable HTTP header for network requests.""" fee_token_symbol: str @@ -96,6 +96,11 @@ def __repr__(self) -> str: @cached_property def custom_network(self) -> "NetworkAPI": + """ + A :class:`~ape.api.networks.NetworkAPI` for custom networks where the + network is either not known, unspecified, or does not have an Ape plugin. + """ + ethereum_class = None for plugin_name, ecosystem_class in self.plugin_manager.ecosystems: if plugin_name == "ethereum": @@ -124,10 +129,11 @@ def decode_address(cls, raw_address: RawAddress) -> AddressType: Convert a raw address to the ecosystem's native address type. Args: - raw_address (Union[str, int]): The address to convert. + raw_address (:class:`~ape.types.address.RawAddress`): The address to + convert. Returns: - ``AddressType`` + :class:`~ape.types.address.AddressType` """ @classmethod @@ -137,10 +143,10 @@ def encode_address(cls, address: AddressType) -> RawAddress: Convert the ecosystem's native address type to a raw integer or str address. Args: - address (Union[str, int]): The address to convert. + address (:class:`~ape.types.address.AddressType`): The address to convert. Returns: - Union[str, int] + :class:`~ape.types.address.RawAddress` """ @raises_not_implemented @@ -156,8 +162,8 @@ def encode_contract_blueprint( # type: ignore[empty-body] Args: contract_type (``ContractType``): The type of contract to create a blueprint for. This is the type of contract that will get created by factory contracts. - *args: Calldata, if applicable. - **kwargs: Transaction specifications, such as ``value``. + *args (Any): Calldata, if applicable. + **kwargs (Any): Transaction specifications, such as ``value``. Returns: :class:`~ape.ape.transactions.TransactionAPI` @@ -190,24 +196,24 @@ def serialize_transaction(self) -> bytes: return signed_txn @abstractmethod - def decode_receipt(self, data: dict) -> "ReceiptAPI": + def decode_receipt(self, data: Dict) -> "ReceiptAPI": """ Convert data to :class:`~ape.api.transactions.ReceiptAPI`. Args: - data (dict): A dictionary of Receipt properties. + data (Dict): A dictionary of Receipt properties. Returns: :class:`~ape.api.transactions.ReceiptAPI` """ @abstractmethod - def decode_block(self, data: dict) -> "BlockAPI": + def decode_block(self, data: Dict) -> "BlockAPI": """ Decode data to a :class:`~ape.api.providers.BlockAPI`. Args: - data (dict): A dictionary of data to decode. + data (Dict): A dictionary of data to decode. Returns: :class:`~ape.api.providers.BlockAPI` @@ -337,8 +343,8 @@ def encode_deployment( Args: deployment_bytecode (HexBytes): The bytecode to deploy. abi (ConstructorABI): The constructor interface of the contract. - *args: Constructor arguments. - **kwargs: Transaction arguments. + *args (Any): Constructor arguments. + **kwargs (Any): Transaction arguments. Returns: class:`~ape.api.transactions.TransactionAPI` @@ -349,14 +355,14 @@ def encode_transaction( self, address: AddressType, abi: MethodABI, *args, **kwargs ) -> "TransactionAPI": """ - Encode a transaction object from a contract function's abi and call arguments. - Update the transaction arguments with the overrides in ``kwargs`` as well. + Encode a transaction object from a contract function's ABI and call arguments. + Additionally, update the transaction arguments with the overrides in ``kwargs``. Args: - address (AddressType): The address of the contract. - abi (MethodABI): The function to call on the contract. - *args: Function arguments. - **kwargs: Transaction arguments. + address (:class:`~ape.types.address.AddressType`): The address of the contract. + abi (``MethodABI``): The function to call on the contract. + *args (Any): Function arguments. + **kwargs (Any): Transaction arguments. Returns: class:`~ape.api.transactions.TransactionAPI` @@ -411,7 +417,7 @@ def decode_calldata(self, abi: Union[ConstructorABI, MethodABI], calldata: bytes Decode method calldata. Args: - abi (MethodABI): The method called. + abi (Union[ConstructorABI, MethodABI]): The method called. calldata (bytes): The raw calldata bytes. Returns: @@ -517,7 +523,7 @@ def get_proxy_info(self, address: AddressType) -> Optional[ProxyInfoAPI]: Information about a proxy contract such as proxy type and implementation address. Args: - address (str): The address of the contract. + address (:class:`~ape.types.address.AddressType`): The address of the contract. Returns: Optional[:class:`~ape.api.networks.ProxyInfoAPI`]: Returns ``None`` if the contract @@ -565,15 +571,15 @@ def enrich_calltree(self, call: CallTreeNode, **kwargs) -> CallTreeNode: @raises_not_implemented def get_python_types( # type: ignore[empty-body] self, abi_type: ABIType - ) -> Union[Type, Tuple, List]: + ) -> Union[Type, Sequence]: """ Get the Python types for a given ABI type. Args: - abi_type (str): The ABI type to get the Python types for. + abi_type (``ABIType``): The ABI type to get the Python types for. Returns: - List[Type]: The Python types for the given ABI type. + Union[Type, Sequence]: The Python types for the given ABI type. """ @@ -582,7 +588,8 @@ class ProviderContextManager(ManagerAccessMixin): A context manager for temporarily connecting to a network. When entering the context, calls the :meth:`ape.api.providers.ProviderAPI.connect` method. And conversely, when exiting, calls the :meth:`ape.api.providers.ProviderPAI.disconnect` - method. + method, unless in a multi-chain context, in which case it disconnects all providers at + the very end of the Python session. The method :meth:`ape.api.networks.NetworkAPI.use_provider` returns an instance of this context manager. @@ -624,6 +631,10 @@ def __init__( @property def empty(self) -> bool: + """ + ``True`` when there are no providers in the context. + """ + return not self.connected_providers or not self.provider_stack def __enter__(self, *args, **kwargs): @@ -977,7 +988,8 @@ def use_provider( ... Args: - provider (str): The provider instance or the name of the provider to use. + provider (Union[str, :class:`~ape.api.providers.ProviderAPI`]): The provider + instance or the name of the provider to use. provider_settings (dict, optional): Settings to apply to the provider. Defaults to ``None``. disconnect_after (bool): Set to ``True`` to force a disconnect after ending @@ -1103,7 +1115,7 @@ def publish_contract(self, address: AddressType): :class:`~ape.exceptions.NetworkError`: When there is no explorer for this network. Args: - address (``AddressType``): The address of the contract. + address (:class:`~ape.types.address.AddressType`): The address of the contract. """ if not self.explorer: raise NetworkError("Unable to publish contract - no explorer plugin installed.") diff --git a/src/ape/api/providers.py b/src/ape/api/providers.py index a803117ec7..2edbc545f5 100644 --- a/src/ape/api/providers.py +++ b/src/ape/api/providers.py @@ -185,13 +185,13 @@ def connection_id(self) -> Optional[str]: return f"{self.network_choice}:{chain_id}" @abstractmethod - def update_settings(self, new_settings: dict): + def update_settings(self, new_settings: Dict): """ Change a provider's setting, such as configure a new port to run on. May require a reconnect. Args: - new_settings (dict): The new provider settings. + new_settings (Dict): The new provider settings. """ @property @@ -208,7 +208,7 @@ def get_balance(self, address: AddressType, block_id: Optional[BlockID] = None) Get the balance of an account. Args: - address (``AddressType``): The address of the account. + address (:class:`~ape.types.address.AddressType`): The address of the account. block_id (:class:`~ape.types.BlockID`): Optionally specify a block ID. Defaults to using the latest block. @@ -222,7 +222,7 @@ def get_code(self, address: AddressType, block_id: Optional[BlockID] = None) -> Get the bytes a contract. Args: - address (``AddressType``): The address of the contract. + address (:class:`~ape.types.address.AddressType`): The address of the contract. block_id (Optional[:class:`~ape.types.BlockID`]): The block ID for checking a previous account nonce. @@ -427,7 +427,7 @@ def get_transactions_by_account_nonce( # type: ignore[empty-body] Get account history for the given account. Args: - account (``AddressType``): The address of the account. + account (:class:`~ape.types.address.AddressType`): The address of the account. start_nonce (int): The nonce of the account to start the search with. stop_nonce (int): The nonce of the account to stop the search with. @@ -447,7 +447,7 @@ def get_contract_creation_receipts( # type: ignore[empty-body] Get all receipts where a contract address was created or re-created. Args: - address (``AddressType``): The address of the account. + address (:class:`~ape.types.address.AddressType`): The address of the account. start_block (int): The block number to start the search with. stop_block (int): The block number to stop the search with. contract_code (Optional[bytes]): The code of the contract at the stop block. @@ -610,7 +610,7 @@ def unlock_account(self, address: AddressType) -> bool: # type: ignore[empty-bo NotImplementedError: When this provider does not support unlocking an account. Args: - address (``AddressType``): The address to unlock. + address (:class:`~ape.types.address.AddressType`): The address to unlock. Returns: bool: ``True`` if successfully unlocked account and ``False`` otherwise. diff --git a/src/ape/cli/options.py b/src/ape/cli/options.py index 5044383693..2d4a5819cd 100644 --- a/src/ape/cli/options.py +++ b/src/ape/cli/options.py @@ -50,7 +50,9 @@ def abort(msg: str, base_error: Optional[Exception] = None) -> NoReturn: raise Abort(msg) -def verbosity_option(cli_logger: Optional[ApeLogger] = None, default: str = DEFAULT_LOG_LEVEL): +def verbosity_option( + cli_logger: Optional[ApeLogger] = None, default: str = DEFAULT_LOG_LEVEL +) -> Callable: """A decorator that adds a `--verbosity, -v` option to the decorated command. """ @@ -81,7 +83,7 @@ def set_level(ctx, param, value): def ape_cli_context( default_log_level: str = DEFAULT_LOG_LEVEL, obj_type: Type = ApeCliContextObject -): +) -> Callable: """ A ``click`` context object with helpful utilities. Use in your commands to get access to common utility features, @@ -171,7 +173,7 @@ def network_option( provider: Optional[Union[List[str], str]] = None, required: bool = False, **kwargs, -): +) -> Callable: """ A ``click.option`` for specifying a network. @@ -194,11 +196,11 @@ def decorator(f): # These are the available network object names you can request. network_object_names = ("ecosystem", "network", "provider") - # All kwargs in the defined @click.commmand(). + # All kwargs in the defined @click.command(). command_signature = inspect.signature(f) command_kwargs = [x.name for x in command_signature.parameters.values()] - # Any combinaiton of ["ecosystem", "network", "provider"] + # Any combination of ["ecosystem", "network", "provider"] requested_network_objects = [x for x in command_kwargs if x in network_object_names] # When using network_option, handle parsing now so we can pass to @@ -251,6 +253,9 @@ def callback(ctx, param, value): # NOTE: The following is needed for click internals. wrapped_f.__name__ = f.__name__ # type: ignore[attr-defined] + # NOTE: The following is needed for sphinx internals. + wrapped_f.__doc__ = f.__doc__ + # Add other click parameters. if hasattr(f, "__click_params__"): wrapped_f.__click_params__ = f.__click_params__ # type: ignore[attr-defined] @@ -283,7 +288,7 @@ def callback(ctx, param, value): return decorator -def skip_confirmation_option(help=""): +def skip_confirmation_option(help="") -> Callable: """ A ``click.option`` for skipping confirmation (``--yes``). @@ -308,7 +313,7 @@ def _account_callback(ctx, param, value): return value -def account_option(account_type: _ACCOUNT_TYPE_FILTER = None): +def account_option(account_type: _ACCOUNT_TYPE_FILTER = None) -> Callable: """ A CLI option that accepts either the account alias or the account number. If not given anything, it will prompt the user to select an account. @@ -341,7 +346,7 @@ def get_contract(contract_name: str) -> ContractType: return [get_contract(c) for c in value] if is_multiple else get_contract(value) -def contract_option(help=None, required=False, multiple=False): +def contract_option(help=None, required=False, multiple=False) -> Callable: """ Contract(s) from the current project. If you pass ``multiple=True``, you will get a list of contract types from the callback. @@ -356,7 +361,7 @@ def contract_option(help=None, required=False, multiple=False): ) -def output_format_option(default: OutputFormat = OutputFormat.TREE): +def output_format_option(default: OutputFormat = OutputFormat.TREE) -> Callable: """ A ``click.option`` for specifying a format to use when outputting data. @@ -373,7 +378,7 @@ def output_format_option(default: OutputFormat = OutputFormat.TREE): ) -def incompatible_with(incompatible_opts): +def incompatible_with(incompatible_opts) -> Type[click.Option]: """ Factory for creating custom ``click.Option`` subclasses that enforce incompatibility with the option strings passed to this function. diff --git a/src/ape/contracts/base.py b/src/ape/contracts/base.py index 3ef081d83c..0d70dc9291 100644 --- a/src/ape/contracts/base.py +++ b/src/ape/contracts/base.py @@ -594,9 +594,9 @@ def range( desired log set. Defaults to delegating to provider. search_topics (Optional[Dict]): Search topics, such as indexed event inputs, to query by. Defaults to getting all events. - extra_addresses (Optional[List[``AddressType``]]): Additional contract - addresses containing the same event type. Defaults to only looking at - the contract instance where this event is defined. + extra_addresses (Optional[List[:class:`~ape.types.address.AddressType`]]): + Additional contract addresses containing the same event type. Defaults to + only looking at the contract instance where this event is defined. Returns: Iterator[:class:`~ape.contracts.base.ContractLog`] @@ -884,7 +884,7 @@ def address(self) -> AddressType: The address of the contract. Returns: - ``AddressType`` + :class:`~ape.types.address.AddressType` """ return self._address diff --git a/src/ape/managers/accounts.py b/src/ape/managers/accounts.py index e1f50d4707..6c00eb07cf 100644 --- a/src/ape/managers/accounts.py +++ b/src/ape/managers/accounts.py @@ -343,7 +343,7 @@ def __contains__(self, address: AddressType) -> bool: Determine if the given address matches an account in ``ape``. Args: - address (``AddressType``): The address to check. + address (:class:`~ape.types.address.AddressType`): The address to check. Returns: bool: ``True`` when the given address is found. diff --git a/src/ape/managers/chain.py b/src/ape/managers/chain.py index 032d959ef7..4f152abb80 100644 --- a/src/ape/managers/chain.py +++ b/src/ape/managers/chain.py @@ -1304,7 +1304,7 @@ def get_creation_receipt( Get the receipt responsible for the initial creation of the contract. Args: - address (``AddressType``): The address of the contract. + address (:class:`~ape.types.address.AddressType`): The address of the contract. start_block (int): The block to start looking from. stop_block (Optional[int]): The block to stop looking at. diff --git a/src/ape/managers/compilers.py b/src/ape/managers/compilers.py index be6186cc93..21be1bad6f 100644 --- a/src/ape/managers/compilers.py +++ b/src/ape/managers/compilers.py @@ -97,12 +97,11 @@ def compile( Raises: :class:`~ape.exceptions.CompilerError`: When there is no compiler found for the given - extension as well as when there is a contract-type collision across compilers. + file-extension as well as when there are contract-type collisions across compilers. Args: - contract_filepaths (Sequence[Union[pathlib.Path], str]): The list of files to compile, - as ``pathlib.Path`` objects. You can also pass a list of `str` that will - automatically get turned to ``pathlib.Path`` objects. + contract_filepaths (Sequence[Union[pathlib.Path], str]): The files to compile, + as ``pathlib.Path`` objects or path-strs. settings (Optional[Dict]): Adhoc compiler settings. Defaults to None. Ensure the compiler name key is present in the dict for it to work. @@ -214,7 +213,7 @@ def compile_source( compiler_name (str): The name of the compiler to use. code (str): The source code to compile. settings (Optional[Dict]): Compiler settings. - **kwargs: Additional overrides for the ``ethpm_types.ContractType`` model. + **kwargs (Any): Additional overrides for the ``ethpm_types.ContractType`` model. Returns: ``ContractContainer``: A contract container ready to be deployed. @@ -297,7 +296,7 @@ def _get_contract_extensions(self, contract_filepaths: List[Path]) -> Set[str]: def enrich_error(self, err: ContractLogicError) -> ContractLogicError: """ Enrich a contract logic error using compiler information, such - known PC locations for compiler runtime errors, such as math errors. + known PC locations for compiler runtime errors. Args: err (:class:`~ape.exceptions.ContractLogicError`): The exception @@ -359,7 +358,7 @@ def can_trace_source(self, filename: str) -> bool: filename (str): The file to check. Returns: - bool + bool: ``True`` when the source is traceable. """ path = Path(filename) if not path.is_file(): diff --git a/src/ape/managers/converters.py b/src/ape/managers/converters.py index be1c77c43f..41c3d59175 100644 --- a/src/ape/managers/converters.py +++ b/src/ape/managers/converters.py @@ -78,7 +78,8 @@ def convert(self, value: str) -> int: class AddressAPIConverter(ConverterAPI): """ - A converter that converts an :class:`~ape.api.address.BaseAddress` to a ``AddressType``. + A converter that converts an :class:`~ape.api.address.BaseAddress` + to a :class`~ape.types.address.AddressType`. """ def is_convertible(self, value: Any) -> bool: @@ -86,13 +87,13 @@ def is_convertible(self, value: Any) -> bool: def convert(self, value: BaseAddress) -> AddressType: """ - Convert the given value to ``AddressType``. + Convert the given value to :class:`~ape.types.address.AddressType`. Args: value (str): The value to convert. Returns: - ``AddressType``: An alias to + :class:`~ape.types.address.AddressType`: An alias to `ChecksumAddress `__. # noqa: E501 """ @@ -101,7 +102,8 @@ def convert(self, value: BaseAddress) -> AddressType: class HexAddressConverter(ConverterAPI): """ - A converter that converts a checksummed address ``str`` to a ``AddressType``. + A converter that converts a checksummed address ``str`` to a + :class:`~ape.types.address.AddressType`. """ def is_convertible(self, value: Any) -> bool: @@ -109,13 +111,13 @@ def is_convertible(self, value: Any) -> bool: def convert(self, value: str) -> AddressType: """ - Convert the given value to a ``AddressType``. + Convert the given value to a :class:`~ape.types.address.AddressType`. Args: value (str): The address ``str`` to convert. Returns: - ``AddressType`` + :class:`~ape.types.address.AddressType` """ return AddressType(to_checksum_address(value)) @@ -123,7 +125,7 @@ def convert(self, value: str) -> AddressType: class BytesAddressConverter(ConverterAPI): """ - A converter that converts a raw bytes address to an ``AddressType``. + A converter that converts a raw bytes address to an :class:`~ape.types.address.AddressType`. """ def is_convertible(self, value: Any) -> bool: @@ -135,7 +137,7 @@ def convert(self, value: bytes) -> AddressType: class IntAddressConverter(ConverterAPI): """ - A converter that converts an integer address to an ``AddressType``. + A converter that converts an integer address to an :class:`~ape.types.address.AddressType`. """ def is_convertible(self, value: Any) -> bool: @@ -226,7 +228,7 @@ def _converters(self) -> Dict[Type, List[ConverterAPI]]: def is_type(self, value: Any, type: Type) -> bool: """ Check if the value is the given type. - If given an ``AddressType``, will also check + If given an :class:`~ape.types.address.AddressType`, will also check that it is checksummed. Args: diff --git a/src/ape/managers/project/manager.py b/src/ape/managers/project/manager.py index 5deacb8c34..f7c9604fd1 100644 --- a/src/ape/managers/project/manager.py +++ b/src/ape/managers/project/manager.py @@ -43,6 +43,7 @@ class ProjectManager(BaseManager): """The project path.""" _cached_projects: Dict[str, ProjectAPI] = {} + _getattr_contracts: bool = True def __init__( self, @@ -519,14 +520,15 @@ def __getattr__(self, attr_name: str) -> Any: return result - def _get_attr(self, attr_name: str): + def _get_attr(self, attr_name: str) -> Any: # Fixes anomaly when accessing non-ContractType attributes. # Returns normal attribute if exists. Raises 'AttributeError' otherwise. try: return self.__getattribute__(attr_name) except AttributeError: - # Check if a contract. - pass + if not self._getattr_contracts: + # Raise the attribute error as if this method didn't exist. + raise try: # NOTE: Will compile project (if needed) @@ -690,7 +692,7 @@ def load_contracts( scripts or tests in ``ape``, such as from ``ape run`` or ``ape test``. Args: - file_paths (Optional[Union[List[Path], Path]]): + file_paths (Optional[Union[Iterable[Path], Path]]): Provide one or more contract file-paths to load. If excluded, will load all the contracts. use_cache (Optional[bool]): Set to ``False`` to force a re-compile. diff --git a/src/ape/managers/project/types.py b/src/ape/managers/project/types.py index 5a90d81894..54c4e6bb69 100644 --- a/src/ape/managers/project/types.py +++ b/src/ape/managers/project/types.py @@ -21,7 +21,7 @@ class _ProjectSources: def __init__( self, cached_manifest: PackageManifest, - active_sources: List[Path], + active_sources: Sequence[Path], contracts_folder: Path, cache_folder: Path, ): diff --git a/src/ape/types/address.py b/src/ape/types/address.py index a0cf701037..9718ccf3b4 100644 --- a/src/ape/types/address.py +++ b/src/ape/types/address.py @@ -40,7 +40,9 @@ def __eth_pydantic_validate__(cls, value: Any, info: Optional[ValidationInfo] = AddressType = Annotated[ChecksumAddress, _AddressValidator] -AddressType.__name__ = "AddressType" +""" +"A checksum address in Ape." +""" __all__ = [ diff --git a/src/ape/types/signatures.py b/src/ape/types/signatures.py index 45d5ae172b..9510f9c90c 100644 --- a/src/ape/types/signatures.py +++ b/src/ape/types/signatures.py @@ -107,7 +107,7 @@ def recover_signer(msg: SignableMessage, sig: MessageSignature) -> AddressType: :class:`~ape.types.MessageSignature`MessageSignature: Signature of the message. Returns: - ``AddressType``: address of message signer. + :class:`~ape.types.address.AddressType`: address of message signer. """ return Account.recover_message(msg, sig) diff --git a/src/ape_ethereum/ecosystem.py b/src/ape_ethereum/ecosystem.py index 9bca4eec81..a4b13039a8 100644 --- a/src/ape_ethereum/ecosystem.py +++ b/src/ape_ethereum/ecosystem.py @@ -339,7 +339,7 @@ def str_to_slot(text): return None - def decode_receipt(self, data: dict) -> ReceiptAPI: + def decode_receipt(self, data: Dict) -> ReceiptAPI: status = data.get("status") if status: status = self.conversion_manager.convert(status, int) @@ -400,7 +400,7 @@ def decode_block(self, data: Dict) -> BlockAPI: return Block.model_validate(data) - def _python_type_for_abi_type(self, abi_type: ABIType) -> Union[Type, Tuple, List]: + def _python_type_for_abi_type(self, abi_type: ABIType) -> Union[Type, Sequence]: # NOTE: An array can be an array of tuples, so we start with an array check if str(abi_type.type).endswith("]"): # remove one layer of the potential onion of array @@ -944,7 +944,7 @@ def _enrich_returndata( call.outputs = output_val return call - def get_python_types(self, abi_type: ABIType) -> Union[Type, Tuple, List]: + def get_python_types(self, abi_type: ABIType) -> Union[Type, Sequence]: return self._python_type_for_abi_type(abi_type) diff --git a/src/ape_geth/provider.py b/src/ape_geth/provider.py index 30e6b559ed..7a112aada7 100644 --- a/src/ape_geth/provider.py +++ b/src/ape_geth/provider.py @@ -197,11 +197,11 @@ def wait(self, *args, **kwargs): class GethNetworkConfig(PluginConfig): # Make sure you are running the right networks when you try for these - mainnet: dict = DEFAULT_SETTINGS.copy() - goerli: dict = DEFAULT_SETTINGS.copy() - sepolia: dict = DEFAULT_SETTINGS.copy() + mainnet: Dict = DEFAULT_SETTINGS.copy() + goerli: Dict = DEFAULT_SETTINGS.copy() + sepolia: Dict = DEFAULT_SETTINGS.copy() # Make sure to run via `geth --dev` (or similar) - local: dict = {**DEFAULT_SETTINGS.copy(), "chain_id": DEFAULT_TEST_CHAIN_ID} + local: Dict = {**DEFAULT_SETTINGS.copy(), "chain_id": DEFAULT_TEST_CHAIN_ID} class GethConfig(PluginConfig): diff --git a/src/ape_networks/_cli.py b/src/ape_networks/_cli.py index 32297ca8df..230945f261 100644 --- a/src/ape_networks/_cli.py +++ b/src/ape_networks/_cli.py @@ -48,6 +48,9 @@ def gen(): @_filter_option("network", _lazy_get("network")) @_filter_option("provider", _lazy_get("provider")) def _list(cli_ctx, output_format, ecosystem_filter, network_filter, provider_filter): + """ + List all the registered ecosystems, networks, and providers. + """ network_data = cli_ctx.network_manager.get_network_data( ecosystem_filter=ecosystem_filter, network_filter=network_filter, @@ -102,12 +105,13 @@ def make_sub_tree(data: Dict, create_tree: Callable) -> Tree: ) from err -@cli.command() +@cli.command(short_help="Start a node process") @ape_cli_context() @network_option(default="ethereum:local:geth") def run(cli_ctx, provider): """ - Start a node process + Start a subprocess node as if running independently + and stream stdout and stderr. """ # Ignore extra loggers, such as web3 loggers. cli_ctx.logger._extra_loggers = {} diff --git a/tests/functional/test_config.py b/tests/functional/test_config.py index f93b64113c..32fdafda3e 100644 --- a/tests/functional/test_config.py +++ b/tests/functional/test_config.py @@ -3,7 +3,7 @@ import pytest from pydantic_settings import SettingsConfigDict -from ape.api import PluginConfig +from ape.api import ConfigEnum, PluginConfig from ape.api.networks import LOCAL_NETWORK_NAME from ape.managers.config import CONFIG_FILE_NAME, DeploymentConfigCollection, merge_configs from ape.types import GasLimit @@ -234,3 +234,15 @@ class CustomConfig(PluginConfig): assert "foo" in config assert config.foo == "123" assert config["foo"] == "123" + + +def test_config_enum(): + class MyEnum(ConfigEnum): + FOO = "FOO" + BAR = "BAR" + + class MyConfig(PluginConfig): + my_enum: MyEnum + + actual = MyConfig(my_enum="FOO") + assert actual.my_enum == MyEnum.FOO