diff --git a/src/ape/managers/networks.py b/src/ape/managers/networks.py index e987f73db3..005b5fd314 100644 --- a/src/ape/managers/networks.py +++ b/src/ape/managers/networks.py @@ -195,12 +195,13 @@ def custom_networks(self) -> list[dict]: Custom network data defined in various ape-config files or added adhoc to the network manager. """ + return [*self._custom_networks_from_config, *self._custom_networks] + + @cached_property + def _custom_networks_from_config(self) -> list[dict]: return [ - *[ - n.model_dump(by_alias=True) - for n in self.config_manager.get_config("networks").get("custom", []) - ], - *self._custom_networks, + n.model_dump(by_alias=True) + for n in self.config_manager.get_config("networks").get("custom", []) ] @property @@ -209,14 +210,23 @@ def ecosystems(self) -> dict[str, "EcosystemAPI"]: All the registered ecosystems in ``ape``, such as ``ethereum``. """ plugin_ecosystems = self._plugin_ecosystems + custom_ecosystems = self._custom_ecosystems + return {**custom_ecosystems, **plugin_ecosystems} + + @cached_property + def _plugin_ecosystems(self) -> dict[str, "EcosystemAPI"]: + # Load plugins. + plugins = self.plugin_manager.ecosystems + return {n: cls(name=n) for n, cls in plugins} # type: ignore[operator] - # Load config-based custom ecosystems. - # NOTE: Non-local projects will automatically add their custom networks - # to `self.custom_networks`. + @cached_property + def _custom_ecosystems(self) -> dict[str, "EcosystemAPI"]: custom_networks: list = self.custom_networks + plugin_ecosystems = self._plugin_ecosystems + custom_ecosystems: dict[str, "EcosystemAPI"] = {} for custom_network in custom_networks: ecosystem_name = custom_network["ecosystem"] - if ecosystem_name in plugin_ecosystems: + if ecosystem_name in plugin_ecosystems or ecosystem_name in custom_ecosystems: # Already included in a prior network. continue @@ -240,15 +250,9 @@ def ecosystems(self) -> dict[str, "EcosystemAPI"]: update={"name": ecosystem_name}, cache_clear=("_networks_from_plugins", "_networks_from_evmchains"), ) - plugin_ecosystems[ecosystem_name] = ecosystem_cls + custom_ecosystems[ecosystem_name] = ecosystem_cls - return plugin_ecosystems - - @cached_property - def _plugin_ecosystems(self) -> dict[str, "EcosystemAPI"]: - # Load plugins. - plugins = self.plugin_manager.ecosystems - return {n: cls(name=n) for n, cls in plugins} # type: ignore[operator] + return custom_ecosystems def create_custom_provider( self, @@ -438,30 +442,45 @@ def get_ecosystem(self, ecosystem_name: str) -> "EcosystemAPI": Returns: :class:`~ape.api.networks.EcosystemAPI` """ + # NOTE: This method purposely avoids "just checking self.ecosystems" + # for performance reasons and exiting the search as early as possible. + ecosystem_name = ecosystem_name.lower().replace(" ", "-") + try: + return self._plugin_ecosystems[ecosystem_name] + except KeyError: + pass - if ecosystem_name in self.ecosystem_names: - return self.ecosystems[ecosystem_name] + # Check if custom. + try: + return self._custom_ecosystems[ecosystem_name] + except KeyError: + pass - elif ecosystem_name.lower().replace(" ", "-") in PUBLIC_CHAIN_META: - ecosystem_name = ecosystem_name.lower().replace(" ", "-") - symbol = None - for net in PUBLIC_CHAIN_META[ecosystem_name].values(): - if not (native_currency := net.get("nativeCurrency")): - continue + if ecosystem := self._get_ecosystem_from_evmchains(ecosystem_name): + return ecosystem - if "symbol" not in native_currency: - continue + raise EcosystemNotFoundError(ecosystem_name, options=self.ecosystem_names) - symbol = native_currency["symbol"] - break + def _get_ecosystem_from_evmchains(self, ecosystem_name: str) -> Optional["EcosystemAPI"]: + if ecosystem_name not in PUBLIC_CHAIN_META: + return None - symbol = symbol or "ETH" + symbol = None + for net in PUBLIC_CHAIN_META[ecosystem_name].values(): + if not (native_currency := net.get("nativeCurrency")): + continue + + if "symbol" not in native_currency: + continue - # Is an EVM chain, can automatically make a class using evm-chains. - evm_class = self._plugin_ecosystems["ethereum"].__class__ - return evm_class(name=ecosystem_name, fee_token_symbol=symbol) + symbol = native_currency["symbol"] + break - raise EcosystemNotFoundError(ecosystem_name, options=self.ecosystem_names) + symbol = symbol or "ETH" + + # Is an EVM chain, can automatically make a class using evm-chains. + evm_class = self._plugin_ecosystems["ethereum"].__class__ + return evm_class(name=ecosystem_name, fee_token_symbol=symbol) def get_provider_from_choice( self, @@ -583,9 +602,9 @@ def default_ecosystem_name(self) -> str: if name := self._default_ecosystem_name: return name - return self.config_manager.default_ecosystem or "ethereum" + return self.local_project.config.default_ecosystem or "ethereum" - @property + @cached_property def default_ecosystem(self) -> "EcosystemAPI": """ The default ecosystem. Call @@ -594,7 +613,7 @@ def default_ecosystem(self) -> "EcosystemAPI": only a single ecosystem installed, such as Ethereum, then get that ecosystem. """ - return self.ecosystems[self.default_ecosystem_name] + return self.get_ecosystem(self.default_ecosystem_name) def set_default_ecosystem(self, ecosystem_name: str): """ diff --git a/src/ape/managers/plugins.py b/src/ape/managers/plugins.py index c20383f040..bec91e742f 100644 --- a/src/ape/managers/plugins.py +++ b/src/ape/managers/plugins.py @@ -1,6 +1,7 @@ from collections.abc import Generator, Iterable, Iterator from functools import cached_property from importlib import import_module +from itertools import chain from typing import Any, Optional from ape.exceptions import ApeAttributeError @@ -123,10 +124,8 @@ def _register_plugins(self): if self.__registered: return - plugins = list({n.replace("-", "_") for n in get_plugin_dists()}) - plugin_modules = tuple([*plugins, *CORE_PLUGINS]) - - for module_name in plugin_modules: + plugins = ({n.replace("-", "_") for n in get_plugin_dists()}) + for module_name in chain(plugins, iter(CORE_PLUGINS)): try: module = import_module(module_name) pluggy_manager.register(module) diff --git a/src/ape/managers/project.py b/src/ape/managers/project.py index 9fd69439db..8841f16fd6 100644 --- a/src/ape/managers/project.py +++ b/src/ape/managers/project.py @@ -2281,10 +2281,10 @@ def project_api(self) -> ProjectAPI: return default_project # ape-config.yaml does no exist. Check for another ProjectAPI type. - project_classes: list[type[ProjectAPI]] = [ - t[1] for t in list(self.plugin_manager.projects) # type: ignore - ] - plugins = [t for t in project_classes if not issubclass(t, ApeProject)] + project_classes: Iterator[type[ProjectAPI]] = ( + t[1] for t in self.plugin_manager.projects # type: ignore + ) + plugins = (t for t in project_classes if not issubclass(t, ApeProject)) for api in plugins: if instance := api.attempt_validate(path=self._base_path): return instance