From ba71e34614489362a26016da13bf8fa4029c290e Mon Sep 17 00:00:00 2001 From: antazoey Date: Tue, 23 Apr 2024 08:15:36 -0600 Subject: [PATCH] refactor!: separate CORE_PLUGINS from setup.py (#2016) --- setup.py | 21 ++++++++++---------- src/ape/__modules__.py | 15 -------------- src/ape/exceptions.py | 23 +++++++++++++++++++++ src/ape/plugins/__init__.py | 23 ++++++++++----------- src/ape/plugins/_utils.py | 32 +++++++++++++++++++++++------- src/ape_plugins/exceptions.py | 26 ------------------------ tests/functional/test_plugins.py | 2 +- tests/functional/test_trace.py | 3 ++- tests/integration/cli/test_test.py | 2 +- 9 files changed, 73 insertions(+), 74 deletions(-) delete mode 100644 src/ape/__modules__.py delete mode 100644 src/ape_plugins/exceptions.py diff --git a/setup.py b/setup.py index ebe87b6584..d44f85d401 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,14 @@ #!/usr/bin/env python +import re from pathlib import Path -from typing import Dict from setuptools import find_packages, setup -here = Path(__file__).parent.absolute() -packages_data: Dict = {} -with open(here / "src" / "ape" / "__modules__.py", encoding="utf8") as modules_file: - exec(modules_file.read(), packages_data) +_HERE = Path(__file__).parent.absolute() +_CORE_PLUGIN_PATTERN = re.compile(r"\bape_\w+(?!\S)") +_PACKAGES = find_packages("src") +_MODULES = {p for p in _PACKAGES if re.match(_CORE_PLUGIN_PATTERN, p)} +_MODULES.add("ape") extras_require = { "test": [ # `test` GitHub Action jobs uses this @@ -52,7 +53,7 @@ ], "dev": [ # commitizen: Manage commits and publishing releases - (here / "cz-requirement.txt").read_text().strip(), + (_HERE / "cz-requirement.txt").read_text().strip(), "pre-commit", # Ensure that linters are run prior to committing "pytest-watch", # `ptw` test watcher/runner "ipdb", # Debugger (Must use `export PYTHONBREAKPOINT=ipdb.set_trace`) @@ -60,7 +61,7 @@ # NOTE: These are extras that someone can install to get up and running quickly w/ ape # They should be kept up to date with what works and what doesn't out of the box # Usage example: `pipx install eth-ape[recommended-plugins]` - "recommended-plugins": (here / "recommended-plugins.txt").read_text().splitlines(), + "recommended-plugins": (_HERE / "recommended-plugins.txt").read_text().splitlines(), } # NOTE: `pip install -e .[dev]` to install package @@ -148,13 +149,13 @@ }, python_requires=">=3.9,<4", extras_require=extras_require, - py_modules=packages_data["__modules__"], + py_modules=list(_MODULES), license="Apache-2.0", zip_safe=False, keywords="ethereum", - packages=find_packages("src"), + packages=_PACKAGES, package_dir={"": "src"}, - package_data={p: ["py.typed"] for p in packages_data["__modules__"]}, + package_data={p: ["py.typed"] for p in _MODULES}, classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", diff --git a/src/ape/__modules__.py b/src/ape/__modules__.py deleted file mode 100644 index 57a8ce6358..0000000000 --- a/src/ape/__modules__.py +++ /dev/null @@ -1,15 +0,0 @@ -__modules__ = [ - "ape", - "ape_accounts", - "ape_cache", - "ape_compile", - "ape_console", - "ape_ethereum", - "ape_init", - "ape_networks", - "ape_node", - "ape_plugins", - "ape_run", - "ape_test", - "ape_pm", -] diff --git a/src/ape/exceptions.py b/src/ape/exceptions.py index 0e7d3668dc..d2f4bf6e4c 100644 --- a/src/ape/exceptions.py +++ b/src/ape/exceptions.py @@ -643,6 +643,29 @@ def __init__( super().__init__(provider, *args, **kwargs) +class PluginInstallError(ApeException): + """ + An error to use when installing a plugin fails. + """ + + +class PluginVersionError(PluginInstallError): + """ + An error related to specified plugin version. + """ + + def __init__( + self, operation: str, reason: Optional[str] = None, resolution: Optional[str] = None + ): + message = f"Unable to {operation} plugin." + if reason: + message = f"{message}\nReason: {reason}" + if resolution: + message = f"{message}\nTo resolve: {resolution}" + + super().__init__(message) + + def handle_ape_exception(err: ApeException, base_paths: list[Path]) -> bool: """ Handle a transaction error by showing relevant stack frames, diff --git a/src/ape/plugins/__init__.py b/src/ape/plugins/__init__.py index 5cc46eeaa4..bdcd67ef50 100644 --- a/src/ape/plugins/__init__.py +++ b/src/ape/plugins/__init__.py @@ -3,9 +3,9 @@ from importlib.metadata import distributions from typing import Any, Callable, Generator, Iterator, List, Optional, Set, Tuple, Type -from ape.__modules__ import __modules__ from ape.exceptions import ApeAttributeError from ape.logging import logger +from ape.plugins._utils import CORE_PLUGINS, clean_plugin_name from ape.utils.basemodel import _assert_not_ipython_check from ape.utils.misc import log_instead_of_fail @@ -45,10 +45,6 @@ class AllPluginHooks( pluggy_manager.add_hookspecs(AllPluginHooks) -def clean_plugin_name(name: str) -> str: - return name.replace("_", "-").replace("ape-", "") - - def get_hooks(plugin_type): return [name for name, method in plugin_type.__dict__.items() if hasattr(method, "ape_spec")] @@ -168,20 +164,21 @@ def _register_plugins(self): if self.__registered: return - plugins = [ - x.name.replace("-", "_") - for x in distributions() - if getattr(x, "name", "").startswith("ape-") - ] - locals = [p for p in __modules__ if p != "ape"] - plugin_modules = tuple([*plugins, *locals]) + plugins: list[str] = [] + for dist in distributions(): + if not (name := getattr(dist, "name", "")): + continue + elif name.startswith("ape-"): + plugins.append(name.replace("-", "_")) + + plugin_modules = tuple([*plugins, *CORE_PLUGINS]) for module_name in plugin_modules: try: module = importlib.import_module(module_name) pluggy_manager.register(module) except Exception as err: - if module_name in __modules__: + if module_name in CORE_PLUGINS: # Always raise core plugin registration errors. raise diff --git a/src/ape/plugins/_utils.py b/src/ape/plugins/_utils.py index c02cbd3142..c73163b878 100644 --- a/src/ape/plugins/_utils.py +++ b/src/ape/plugins/_utils.py @@ -1,6 +1,8 @@ +import re import sys from enum import Enum from functools import cached_property +from pathlib import Path from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Tuple import click @@ -8,18 +10,28 @@ from packaging.version import Version from pydantic import field_validator, model_validator -from ape.__modules__ import __modules__ +from ape.exceptions import PluginVersionError from ape.logging import logger -from ape.plugins import clean_plugin_name from ape.utils import BaseInterfaceModel, get_package_version, log_instead_of_fail from ape.utils._github import github_client from ape.utils.basemodel import BaseModel from ape.utils.misc import _get_distributions from ape.version import version as ape_version_str -from ape_plugins.exceptions import PluginVersionError # Plugins maintained OSS by ApeWorX (and trusted) -CORE_PLUGINS = {p for p in __modules__ if p != "ape"} +PLUGIN_PATTERN = re.compile(r"\bape_\w+(?!\S)") +CORE_PLUGINS = [ + "ape", + *[ + f.name + for f in Path(__file__).parent.parent.parent.iterdir() + if f.name.startswith("ape_") and f.is_dir() and re.match(PLUGIN_PATTERN, f.name) + ], +] + + +def clean_plugin_name(name: str) -> str: + return name.replace("_", "-").replace("ape-", "") class ApeVersion: @@ -345,12 +357,18 @@ def __str__(self): version_key = f"=={self.version}" if self.version and self.version[0].isnumeric() else "" return f"{self.name}{version_key}" - def check_installed(self, use_cache: bool = True): + def check_installed(self, use_cache: bool = True) -> bool: if not use_cache: _get_distributions.cache_clear() - ape_packages = [n.name for n in _get_distributions()] - return self.package_name in ape_packages + pkg_name = self.package_name + for dist in _get_distributions(): + if not (name := getattr(dist, "name", "")): + continue + elif name == pkg_name: + return True + + return False def _prepare_install( self, upgrade: bool = False, skip_confirmation: bool = False diff --git a/src/ape_plugins/exceptions.py b/src/ape_plugins/exceptions.py deleted file mode 100644 index ca474eba6e..0000000000 --- a/src/ape_plugins/exceptions.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Optional - -from ape.exceptions import ApeException - - -class PluginInstallError(ApeException): - """ - An error to use when installing a plugin fails. - """ - - -class PluginVersionError(PluginInstallError): - """ - An error related to specified plugin version. - """ - - def __init__( - self, operation: str, reason: Optional[str] = None, resolution: Optional[str] = None - ): - message = f"Unable to {operation} plugin." - if reason: - message = f"{message}\nReason: {reason}" - if resolution: - message = f"{message}\nTo resolve: {resolution}" - - super().__init__(message) diff --git a/tests/functional/test_plugins.py b/tests/functional/test_plugins.py index 79d22d9fdf..f88d92531d 100644 --- a/tests/functional/test_plugins.py +++ b/tests/functional/test_plugins.py @@ -3,6 +3,7 @@ import pytest +from ape.exceptions import PluginVersionError from ape.logging import LogLevel from ape.plugins._utils import ( ApePluginsRepr, @@ -13,7 +14,6 @@ PluginType, ape_version, ) -from ape_plugins.exceptions import PluginVersionError CORE_PLUGINS = ("run",) AVAILABLE_PLUGINS = ("available", "installed") diff --git a/tests/functional/test_trace.py b/tests/functional/test_trace.py index 49728d8f1a..f3bfc08cc1 100644 --- a/tests/functional/test_trace.py +++ b/tests/functional/test_trace.py @@ -50,7 +50,8 @@ def test_get_gas_report_transfer(gas_tracker, sender, receiver): def test_transaction_trace_create(vyper_contract_instance): - trace = TransactionTrace(transaction_hash=vyper_contract_instance.creation_metadata.txn_hash) + tx_hash = vyper_contract_instance.creation_metadata.txn_hash + trace = TransactionTrace(transaction_hash=tx_hash) actual = f"{trace}" expected = r"VyperContract\.__new__\(num=0\) \[\d+ gas\]" assert re.match(expected, actual) diff --git a/tests/integration/cli/test_test.py b/tests/integration/cli/test_test.py index 43d18d7c67..bc34892a54 100644 --- a/tests/integration/cli/test_test.py +++ b/tests/integration/cli/test_test.py @@ -386,7 +386,7 @@ def test_fails(): pytester.makepyfile(test) stdin = "print(foo)\nexit\n" monkeypatch.setattr("sys.stdin", io.StringIO(stdin)) - result = pytester.runpytest_subprocess("--interactive", "-s") + result = pytester.runpytest("--interactive", "-s") result.assert_outcomes(failed=1) actual = str(result.stdout) assert secret in actual