From 25a49d82327e5b7ee757c71b4d54e8e61fb41b37 Mon Sep 17 00:00:00 2001 From: Mihai Date: Thu, 11 Apr 2024 16:38:07 -0400 Subject: [PATCH 01/30] feat: use uv if it exists, oteherwise pip --- setup.py | 3 +-- src/ape/cli/__init__.py | 4 +++ src/ape/plugins/__init__.py | 32 +++++++++++++++++++++++ src/ape/plugins/_utils.py | 3 ++- src/ape_plugins/_cli.py | 51 ++++++++++++++++++++++++++----------- 5 files changed, 75 insertions(+), 18 deletions(-) diff --git a/setup.py b/setup.py index eab553124b..e8a24ac6eb 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,6 @@ "sphinx-plausible>=0.1.2,<0.2", ], "release": [ # `release` GitHub Action job uses this - "setuptools", # Installation tool "wheel", # Packaging tool "twine==3.8.0", # Package upload tool ], @@ -76,7 +75,6 @@ with open("./README.md") as readme: long_description = readme.read() - setup( name="eth-ape", use_scm_version=True, @@ -112,6 +110,7 @@ "PyYAML>=5.0,<7", "requests>=2.28.1,<3", "rich>=12.5.1,<14", + "setuptools", # Installation tool "SQLAlchemy>=1.4.35", "tqdm>=4.62.3,<5.0", "traitlets>=5.3.0", diff --git a/src/ape/cli/__init__.py b/src/ape/cli/__init__.py index c3dd09ef97..87a07281c0 100644 --- a/src/ape/cli/__init__.py +++ b/src/ape/cli/__init__.py @@ -1,3 +1,5 @@ +import sys +from shutil import which from ape.cli.arguments import ( contract_file_paths_argument, existing_alias_argument, @@ -28,6 +30,8 @@ ) from ape.cli.paramtype import AllFilePaths, Path +PIP_COMMAND = ["uv", "pip"] if which("uv") else [sys.executable, "-m", "pip"] + __all__ = [ "account_option", "AccountAliasPromptChoice", diff --git a/src/ape/plugins/__init__.py b/src/ape/plugins/__init__.py index 5cc46eeaa4..0535beef56 100644 --- a/src/ape/plugins/__init__.py +++ b/src/ape/plugins/__init__.py @@ -4,6 +4,7 @@ from typing import Any, Callable, Generator, Iterator, List, Optional, Set, Tuple, Type from ape.__modules__ import __modules__ +from ape.cli import PIP_COMMAND from ape.exceptions import ApeAttributeError from ape.logging import logger from ape.utils.basemodel import _assert_not_ipython_check @@ -147,6 +148,7 @@ def get_plugin_name_and_hookfn(h): return h.plugin_name, getattr(h.plugin, attr_name)() for plugin_name, results in map(get_plugin_name_and_hookfn, hookimpls): + print(f"plugin_name: {plugin_name:<20} results: {results}") # NOTE: Some plugins return a tuple and some return iterators if not isinstance(results, Generator): validated_plugin = self._validate_plugin(plugin_name, results) @@ -164,6 +166,36 @@ def registered_plugins(self) -> Set[str]: self._register_plugins() return {x[0] for x in pluggy_manager.list_name_plugin()} + @functools.cached_property + def _plugin_modules(self) -> Tuple[str, ...]: + core_plugin_module_names = {n for _, n, _ in pkgutil.iter_modules() if n.startswith("ape_")} + if PIP_COMMAND[0] != "uv": + # NOTE: Unable to use pkgutil.iter_modules() for installed plugins + # because it does not work with editable installs. + # See https://github.com/python/cpython/issues/99805. + result = subprocess.check_output( + ["pip", "list", "--format", "freeze", "--disable-pip-version-check"] + ) + packages = result.decode("utf8").splitlines() + installed_plugin_module_names = { + p.split("==")[0].replace("-", "_") for p in packages if p.startswith("ape-") + } + else: + result = subprocess.check_output(["uv", "pip", "list"]) + # format is in the output of: + # Package Version Editable project location + # ------------------------- ------------------------------- ------------------------- + # aiosignal 1.3.1 + # annotated-types 0.6.0 + # skip the header + packages = result.decode("utf8").splitlines()[2:] + installed_plugin_module_names = { + p.split(" ")[0].replace("-", "_") for p in packages if p.startswith("ape-") + } + + # NOTE: Returns tuple because this shouldn't change. + return tuple(installed_plugin_module_names.union(core_plugin_module_names)) + def _register_plugins(self): if self.__registered: return diff --git a/src/ape/plugins/_utils.py b/src/ape/plugins/_utils.py index 3c42581e11..718cf18ea9 100644 --- a/src/ape/plugins/_utils.py +++ b/src/ape/plugins/_utils.py @@ -9,6 +9,7 @@ from pydantic import field_validator, model_validator from ape.__modules__ import __modules__ +from ape.cli import PIP_COMMAND from ape.logging import logger from ape.plugins import clean_plugin_name from ape.utils import BaseInterfaceModel, get_package_version, github_client, log_instead_of_fail @@ -371,7 +372,7 @@ def _prepare_install( logger.warning(f"Plugin '{self.name}' is not an trusted plugin.") result_handler = ModifyPluginResultHandler(self) - pip_arguments = [sys.executable, "-m", "pip", "install"] + pip_arguments = PIP_COMMAND + ["install"] if upgrade: logger.info(f"Upgrading '{self.name}' plugin ...") diff --git a/src/ape_plugins/_cli.py b/src/ape_plugins/_cli.py index 3a28d4ff3b..5cb96807a1 100644 --- a/src/ape_plugins/_cli.py +++ b/src/ape_plugins/_cli.py @@ -6,7 +6,7 @@ import click from packaging.version import Version -from ape.cli import ape_cli_context, skip_confirmation_option +from ape.cli import ape_cli_context, skip_confirmation_option, PIP_COMMAND from ape.logging import logger from ape.managers.config import CONFIG_FILE_NAME from ape.plugins._utils import ( @@ -190,7 +190,7 @@ def uninstall(cli_ctx, plugins, skip_confirmation): skip_confirmation or click.confirm(f"Remove plugin '{plugin}'?") ): cli_ctx.logger.info(f"Uninstalling '{plugin.name}'...") - args = [sys.executable, "-m", "pip", "uninstall", "-y", plugin.package_name, "--quiet"] + args = PIP_COMMAND + ["uninstall", "-y", plugin.package_name, "--quiet"] # NOTE: Be *extremely careful* with this command, as it modifies the user's # installed packages, to potentially catastrophic results @@ -227,6 +227,37 @@ def change_version(version): _change_version(version) +def _install(name, spec) -> int: + """ + Helper function to install or update a Python package using pip. + + Args: + name (str): The package name. + spec (str): Version specifier, e.g., '==1.0.0', '>=1.0.0', etc. + """ + args = PIP_COMMAND + ["install", f"{name}{spec}", "--quiet"] + + # Run the installation process and capture output for error checking + completed_process = subprocess.run( + args, + capture_output=True, + text=True, # Output as string + check=False, # Allow manual error handling + ) + + # Check for installation errors + if completed_process.returncode != 0: + message = f"Failed to install/update {name}" + if completed_process.stdout: + message += f": {completed_process.stdout}" + logger.error(message) + sys.exit(completed_process.returncode) + else: + logger.info(f"Successfully installed/updated {name}") + + return completed_process.returncode + + def _change_version(spec: str): # Update all the plugins. # This will also update core Ape. @@ -235,20 +266,10 @@ def _change_version(spec: str): for plugin in _get_distributions(): logger.info(f"Updating {plugin} ...") name = plugin.split("=")[0].strip() - subprocess.call([sys.executable, "-m", "pip", "install", f"{name}{spec}", "--quiet"]) + _install(name, spec) # This check is for verifying the update and shouldn't actually do anything. logger.info("Updating Ape core ...") - completed_process = subprocess.run( - [sys.executable, "-m", "pip", "install", f"eth-ape{spec}", "--quiet"] - ) - if completed_process.returncode != 0: - message = "Update failed" - if output := completed_process.stdout: - message = f"{message}: {output.decode('utf8')}" - - logger.error(message) - sys.exit(completed_process.returncode) - - else: + returncode = _install("eth-ape", spec) + if returncode == 0: logger.success("Ape and all plugins have successfully upgraded.") From 11c32b3b4c98e79f168f5bfa3ea136329cf417a8 Mon Sep 17 00:00:00 2001 From: Mihai Date: Sun, 14 Apr 2024 21:45:56 -0400 Subject: [PATCH 02/30] chore: lint with isort --- src/ape/cli/__init__.py | 1 + src/ape_plugins/_cli.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ape/cli/__init__.py b/src/ape/cli/__init__.py index 87a07281c0..a19cb71cfc 100644 --- a/src/ape/cli/__init__.py +++ b/src/ape/cli/__init__.py @@ -1,5 +1,6 @@ import sys from shutil import which + from ape.cli.arguments import ( contract_file_paths_argument, existing_alias_argument, diff --git a/src/ape_plugins/_cli.py b/src/ape_plugins/_cli.py index 5cb96807a1..18f4506e0e 100644 --- a/src/ape_plugins/_cli.py +++ b/src/ape_plugins/_cli.py @@ -6,7 +6,7 @@ import click from packaging.version import Version -from ape.cli import ape_cli_context, skip_confirmation_option, PIP_COMMAND +from ape.cli import PIP_COMMAND, ape_cli_context, skip_confirmation_option from ape.logging import logger from ape.managers.config import CONFIG_FILE_NAME from ape.plugins._utils import ( From af082fef2f148a589c84057afca020e667422b44 Mon Sep 17 00:00:00 2001 From: Mihai Date: Sun, 14 Apr 2024 21:51:46 -0400 Subject: [PATCH 03/30] chore: lint with flake8 --- src/ape/plugins/__init__.py | 1 - src/ape/plugins/_utils.py | 1 - 2 files changed, 2 deletions(-) diff --git a/src/ape/plugins/__init__.py b/src/ape/plugins/__init__.py index 0535beef56..a7310c353d 100644 --- a/src/ape/plugins/__init__.py +++ b/src/ape/plugins/__init__.py @@ -148,7 +148,6 @@ def get_plugin_name_and_hookfn(h): return h.plugin_name, getattr(h.plugin, attr_name)() for plugin_name, results in map(get_plugin_name_and_hookfn, hookimpls): - print(f"plugin_name: {plugin_name:<20} results: {results}") # NOTE: Some plugins return a tuple and some return iterators if not isinstance(results, Generator): validated_plugin = self._validate_plugin(plugin_name, results) diff --git a/src/ape/plugins/_utils.py b/src/ape/plugins/_utils.py index 718cf18ea9..af03445247 100644 --- a/src/ape/plugins/_utils.py +++ b/src/ape/plugins/_utils.py @@ -1,4 +1,3 @@ -import sys from enum import Enum from functools import cached_property from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Tuple From 47498d6ff5033732979a00f6f3bf19c500c9966a Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 01:12:14 -0400 Subject: [PATCH 04/30] chore: remove setuptools dependency attempt #1 --- setup.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index e8a24ac6eb..77a540567d 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ "sphinx-plausible>=0.1.2,<0.2", ], "release": [ # `release` GitHub Action job uses this + "setuptools", # Installation tool "wheel", # Packaging tool "twine==3.8.0", # Package upload tool ], @@ -106,11 +107,10 @@ "pydantic-settings>=2.0.3,<3", "PyGithub>=1.59,<2", "pytest>=6.0,<8.0", - "python-dateutil>=2.8.2,<3", + "python-dateutil>=2.8.2", "PyYAML>=5.0,<7", "requests>=2.28.1,<3", - "rich>=12.5.1,<14", - "setuptools", # Installation tool + "rich>=12.5.1", "SQLAlchemy>=1.4.35", "tqdm>=4.62.3,<5.0", "traitlets>=5.3.0", @@ -120,15 +120,15 @@ "eth-abi>=5.1.0,<6", "eth-account>=0.10.0,<0.11", "eth-typing>=3.5.2,<4", - "eth-utils>=2.3.1,<3", + "eth-utils>=2.3.1", "py-geth>=4.4.0,<5", - "web3[tester]>=6.16.0,<6.17.1", + "web3[tester]>=6.16.0", # ** Dependencies maintained by ApeWorX ** - "eip712>=0.2.7,<0.3", + "eip712 @ git+https://github.com/wakamex/eip712.git", "ethpm-types>=0.6.9,<0.7", "eth_pydantic_types>=0.1.0,<0.2", "evmchains>=0.0.6,<0.1", - "evm-trace>=0.1.3,<0.2", + "evm-trace @ git+https://github.com/wakamex/evm-trace.git", ], entry_points={ "console_scripts": ["ape=ape._cli:cli"], From c84ed29b79d99f49d38fb4d412be2bd50fe37d2a Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 01:19:19 -0400 Subject: [PATCH 05/30] chore: bump pandas --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 77a540567d..2048bd8ca6 100644 --- a/setup.py +++ b/setup.py @@ -101,7 +101,7 @@ "ipython>=8.5.0,<9", "lazyasd>=0.1.4", "packaging>=23.0,<24", - "pandas>=1.3.0,<2", + "pandas>=2", "pluggy>=1.3,<2", "pydantic>=2.5.2,<3", "pydantic-settings>=2.0.3,<3", From 5b1dffbd4602bfc8c5334b492db21a1e101c9394 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 01:50:49 -0400 Subject: [PATCH 06/30] chore: remove setuptools dependency attempt #2 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 2048bd8ca6..799990b042 100644 --- a/setup.py +++ b/setup.py @@ -119,6 +119,7 @@ # ** Dependencies maintained by Ethereum Foundation ** "eth-abi>=5.1.0,<6", "eth-account>=0.10.0,<0.11", + "eth-tester @ git+https://github.com/wakamex/eth-tester.git", "eth-typing>=3.5.2,<4", "eth-utils>=2.3.1", "py-geth>=4.4.0,<5", From a2d06335a3b90c01f9f5321e510e27dd12b5348d Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 02:19:57 -0400 Subject: [PATCH 07/30] chore: uncap eth_account --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 799990b042..322c96af1e 100644 --- a/setup.py +++ b/setup.py @@ -118,7 +118,7 @@ "watchdog>=3.0,<4", # ** Dependencies maintained by Ethereum Foundation ** "eth-abi>=5.1.0,<6", - "eth-account>=0.10.0,<0.11", + "eth-account>=0.10.0", "eth-tester @ git+https://github.com/wakamex/eth-tester.git", "eth-typing>=3.5.2,<4", "eth-utils>=2.3.1", From 2aa9753ea6a5162cf4ad918901153b004f8c8ad0 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 02:32:50 -0400 Subject: [PATCH 08/30] allow installing pre-releases in workflows --- .github/workflows/publish.yaml | 2 +- .github/workflows/test.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 9fcbe79649..06e8e93edf 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -20,7 +20,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e .[release] + pip install --pre -e .[release] - name: Build run: python setup.py sdist bdist_wheel diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 276a79524f..ef036db724 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,7 +24,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pip install .[lint] + pipi install --pre .[lint] - name: Run Black run: black --check . @@ -52,7 +52,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pip install .[lint,test] + pipi install --pre .[lint,test] - name: Run MyPy run: mypy . @@ -92,7 +92,7 @@ jobs: run: | python -m pip install --upgrade pip pip uninstall eth-ape --yes - pip install .[test] + pipi install --pre .[test] - name: Run Tests run: pytest -m "not fuzzing" -s --cov=src -n auto --dist loadgroup @@ -114,7 +114,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pip install .[test] + pipi install --pre .[test] - name: Run Tests run: pytest -m "fuzzing" --no-cov -s From cd9bfa1b382ec3c7e82252f10789fdb1348da833 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 02:34:20 -0400 Subject: [PATCH 09/30] chore: fix typo --- .github/workflows/test.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ef036db724..5915786e9d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,7 +24,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pipi install --pre .[lint] + pip install --pre .[lint] - name: Run Black run: black --check . @@ -52,7 +52,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pipi install --pre .[lint,test] + pip install --pre .[lint,test] - name: Run MyPy run: mypy . @@ -92,7 +92,7 @@ jobs: run: | python -m pip install --upgrade pip pip uninstall eth-ape --yes - pipi install --pre .[test] + pip install --pre .[test] - name: Run Tests run: pytest -m "not fuzzing" -s --cov=src -n auto --dist loadgroup @@ -114,7 +114,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pipi install --pre .[test] + pip install --pre .[test] - name: Run Tests run: pytest -m "fuzzing" --no-cov -s From c769119146344e18d8eead316d5e8e9317e075f3 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 02:42:11 -0400 Subject: [PATCH 10/30] feat: install with uv in workflow --- .github/workflows/test.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5915786e9d..bb17b2ec68 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -23,8 +23,8 @@ jobs: - name: Install Dependencies run: | - python -m pip install --upgrade pip - pip install --pre .[lint] + python -m pip install --upgrade uv + uv pip install .[lint] - name: Run Black run: black --check . @@ -51,8 +51,8 @@ jobs: - name: Install Dependencies run: | - python -m pip install --upgrade pip - pip install --pre .[lint,test] + python -m pip install --upgrade uv + uv pip install .[lint,test] - name: Run MyPy run: mypy . @@ -90,9 +90,9 @@ jobs: - name: Install Dependencies run: | - python -m pip install --upgrade pip - pip uninstall eth-ape --yes - pip install --pre .[test] + python -m pip install --upgrade uv + uv pip uninstall eth-ape --yes + uv pip install .[test] - name: Run Tests run: pytest -m "not fuzzing" -s --cov=src -n auto --dist loadgroup @@ -113,8 +113,8 @@ jobs: - name: Install Dependencies run: | - python -m pip install --upgrade pip - pip install --pre .[test] + python -m pip install --upgrade uv + uv pip install --pre .[test] - name: Run Tests run: pytest -m "fuzzing" --no-cov -s From e56a4704d12975221a881205b0f17667b4b6afa5 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 02:44:03 -0400 Subject: [PATCH 11/30] chore: make venv --- .github/workflows/test.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index bb17b2ec68..375aabeaee 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,6 +24,8 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade uv + uv venv .venv + source .venv/bin/activate uv pip install .[lint] - name: Run Black @@ -52,6 +54,8 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade uv + uv venv .venv + source .venv/bin/activate uv pip install .[lint,test] - name: Run MyPy @@ -91,7 +95,8 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade uv - uv pip uninstall eth-ape --yes + uv venv .venv + source .venv/bin/activate uv pip install .[test] - name: Run Tests @@ -114,6 +119,8 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade uv + uv venv .venv + source .venv/bin/activate uv pip install --pre .[test] - name: Run Tests From 9c51369ed668d7c9921b3101a54a28908f9e3d4a Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 02:47:23 -0400 Subject: [PATCH 12/30] chore: activate venv at each step --- .github/workflows/test.yaml | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 375aabeaee..ada020b6a1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -29,16 +29,24 @@ jobs: uv pip install .[lint] - name: Run Black - run: black --check . + run: | + source .venv/bin/activate + black --check . - name: Run isort - run: isort --check-only . + run: | + source .venv/bin/activate + isort --check-only . - name: Run flake8 - run: flake8 . + run: | + source .venv/bin/activate + flake8 . - name: Run mdformat - run: mdformat . --check + run: | + source .venv/bin/activate + mdformat . --check type-check: runs-on: ubuntu-latest @@ -59,7 +67,9 @@ jobs: uv pip install .[lint,test] - name: Run MyPy - run: mypy . + run: | + source .venv/bin/activate + mypy . functional: runs-on: ${{ matrix.os }} @@ -100,7 +110,9 @@ jobs: uv pip install .[test] - name: Run Tests - run: pytest -m "not fuzzing" -s --cov=src -n auto --dist loadgroup + run: | + source .venv/bin/activate + pytest -m "not fuzzing" -s --cov=src -n auto --dist loadgroup fuzzing: runs-on: ubuntu-latest @@ -124,4 +136,6 @@ jobs: uv pip install --pre .[test] - name: Run Tests - run: pytest -m "fuzzing" --no-cov -s + run: | + source .venv/bin/activate + pytest -m "fuzzing" --no-cov -s From 4af0ec4423cf889330362196dfe804c423cc76b4 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 09:12:13 -0400 Subject: [PATCH 13/30] move PIP_COMMAND to plugins._utils --- src/ape/cli/__init__.py | 7 ++----- src/ape/plugins/__init__.py | 2 +- src/ape/plugins/_utils.py | 5 ++++- src/ape_plugins/_cli.py | 3 ++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ape/cli/__init__.py b/src/ape/cli/__init__.py index a19cb71cfc..98f5b884d7 100644 --- a/src/ape/cli/__init__.py +++ b/src/ape/cli/__init__.py @@ -1,6 +1,3 @@ -import sys -from shutil import which - from ape.cli.arguments import ( contract_file_paths_argument, existing_alias_argument, @@ -30,8 +27,7 @@ verbosity_option, ) from ape.cli.paramtype import AllFilePaths, Path - -PIP_COMMAND = ["uv", "pip"] if which("uv") else [sys.executable, "-m", "pip"] +from ape.plugins._utils import PIP_COMMAND __all__ = [ "account_option", @@ -55,6 +51,7 @@ "output_format_option", "OutputFormat", "Path", + "PIP_COMMAND", "PromptChoice", "select_account", "skip_confirmation_option", diff --git a/src/ape/plugins/__init__.py b/src/ape/plugins/__init__.py index a7310c353d..e99d5c69e3 100644 --- a/src/ape/plugins/__init__.py +++ b/src/ape/plugins/__init__.py @@ -4,7 +4,6 @@ from typing import Any, Callable, Generator, Iterator, List, Optional, Set, Tuple, Type from ape.__modules__ import __modules__ -from ape.cli import PIP_COMMAND from ape.exceptions import ApeAttributeError from ape.logging import logger from ape.utils.basemodel import _assert_not_ipython_check @@ -19,6 +18,7 @@ from .pluggy_patch import plugin_manager as pluggy_manager from .project import DependencyPlugin, ProjectPlugin from .query import QueryPlugin +from ._utils import PIP_COMMAND class PluginError(Exception): diff --git a/src/ape/plugins/_utils.py b/src/ape/plugins/_utils.py index af03445247..8c59dc38aa 100644 --- a/src/ape/plugins/_utils.py +++ b/src/ape/plugins/_utils.py @@ -1,5 +1,7 @@ +import sys from enum import Enum from functools import cached_property +from shutil import which from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Tuple import click @@ -8,7 +10,6 @@ from pydantic import field_validator, model_validator from ape.__modules__ import __modules__ -from ape.cli import PIP_COMMAND from ape.logging import logger from ape.plugins import clean_plugin_name from ape.utils import BaseInterfaceModel, get_package_version, github_client, log_instead_of_fail @@ -19,6 +20,8 @@ # Plugins maintained OSS by ApeWorX (and trusted) CORE_PLUGINS = {p for p in __modules__ if p != "ape"} +# Use `uv pip` if installed, otherwise `python -m pip` +PIP_COMMAND = ["uv", "pip"] if which("uv") else [sys.executable, "-m", "pip"] class ApeVersion: diff --git a/src/ape_plugins/_cli.py b/src/ape_plugins/_cli.py index 18f4506e0e..94c7157aa4 100644 --- a/src/ape_plugins/_cli.py +++ b/src/ape_plugins/_cli.py @@ -6,11 +6,12 @@ import click from packaging.version import Version -from ape.cli import PIP_COMMAND, ape_cli_context, skip_confirmation_option +from ape.cli import ape_cli_context, skip_confirmation_option from ape.logging import logger from ape.managers.config import CONFIG_FILE_NAME from ape.plugins._utils import ( ModifyPluginResultHandler, + PIP_COMMAND, PluginMetadata, PluginMetadataList, PluginType, From ee4f6132a920901cb6ad7e6284a064d5a0ccb6a8 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 09:13:50 -0400 Subject: [PATCH 14/30] merge lists Co-authored-by: antazoey --- src/ape/plugins/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ape/plugins/_utils.py b/src/ape/plugins/_utils.py index 8c59dc38aa..e2d7f50946 100644 --- a/src/ape/plugins/_utils.py +++ b/src/ape/plugins/_utils.py @@ -374,7 +374,7 @@ def _prepare_install( logger.warning(f"Plugin '{self.name}' is not an trusted plugin.") result_handler = ModifyPluginResultHandler(self) - pip_arguments = PIP_COMMAND + ["install"] + pip_arguments = [*PIP_COMMAND, "install"] if upgrade: logger.info(f"Upgrading '{self.name}' plugin ...") From ec63a865495b43370544e70a5dff1dfc9283782c Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 09:14:56 -0400 Subject: [PATCH 15/30] merge more lists --- src/ape_plugins/_cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ape_plugins/_cli.py b/src/ape_plugins/_cli.py index 94c7157aa4..32389d84a1 100644 --- a/src/ape_plugins/_cli.py +++ b/src/ape_plugins/_cli.py @@ -191,7 +191,7 @@ def uninstall(cli_ctx, plugins, skip_confirmation): skip_confirmation or click.confirm(f"Remove plugin '{plugin}'?") ): cli_ctx.logger.info(f"Uninstalling '{plugin.name}'...") - args = PIP_COMMAND + ["uninstall", "-y", plugin.package_name, "--quiet"] + args = [*PIP_COMMAND,"uninstall", "-y", plugin.package_name, "--quiet"] # NOTE: Be *extremely careful* with this command, as it modifies the user's # installed packages, to potentially catastrophic results @@ -236,7 +236,7 @@ def _install(name, spec) -> int: name (str): The package name. spec (str): Version specifier, e.g., '==1.0.0', '>=1.0.0', etc. """ - args = PIP_COMMAND + ["install", f"{name}{spec}", "--quiet"] + args = [*PIP_COMMAND, "install", f"{name}{spec}", "--quiet"] # Run the installation process and capture output for error checking completed_process = subprocess.run( From 06368d0f6c6e85739b00d9b186a28e1ac42a043f Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 10:48:28 -0400 Subject: [PATCH 16/30] reset dependencies --- setup.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 322c96af1e..72de80f333 100644 --- a/setup.py +++ b/setup.py @@ -76,6 +76,7 @@ with open("./README.md") as readme: long_description = readme.read() + setup( name="eth-ape", use_scm_version=True, @@ -101,16 +102,16 @@ "ipython>=8.5.0,<9", "lazyasd>=0.1.4", "packaging>=23.0,<24", - "pandas>=2", + "pandas>=1.3.0,<2", "pluggy>=1.3,<2", "pydantic>=2.5.2,<3", "pydantic-settings>=2.0.3,<3", "PyGithub>=1.59,<2", "pytest>=6.0,<8.0", - "python-dateutil>=2.8.2", + "python-dateutil>=2.8.2,<3", "PyYAML>=5.0,<7", "requests>=2.28.1,<3", - "rich>=12.5.1", + "rich>=12.5.1,<14", "SQLAlchemy>=1.4.35", "tqdm>=4.62.3,<5.0", "traitlets>=5.3.0", @@ -118,18 +119,17 @@ "watchdog>=3.0,<4", # ** Dependencies maintained by Ethereum Foundation ** "eth-abi>=5.1.0,<6", - "eth-account>=0.10.0", - "eth-tester @ git+https://github.com/wakamex/eth-tester.git", + "eth-account>=0.10.0,<0.11", "eth-typing>=3.5.2,<4", - "eth-utils>=2.3.1", + "eth-utils>=2.3.1,<3", "py-geth>=4.4.0,<5", - "web3[tester]>=6.16.0", + "web3[tester]>=6.16.0,<7", # ** Dependencies maintained by ApeWorX ** - "eip712 @ git+https://github.com/wakamex/eip712.git", - "ethpm-types>=0.6.9,<0.7", - "eth_pydantic_types>=0.1.0,<0.2", - "evmchains>=0.0.6,<0.1", - "evm-trace @ git+https://github.com/wakamex/evm-trace.git", + "eip712>=0.2.3,<0.4", + "ethpm-types>=0.6.7,<0.7", + "eth_pydantic_types>=0.1.0a5,<0.2", + "evmchains>=0.0.2,<0.1", + "evm-trace>=0.1.2", ], entry_points={ "console_scripts": ["ape=ape._cli:cli"], From 629cb23c9dfc8b0fcbcaa8ec3ce304aab9a35c53 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 10:55:23 -0400 Subject: [PATCH 17/30] reset github workflows --- .github/workflows/publish.yaml | 2 +- .github/workflows/test.yaml | 53 ++++++++++------------------------ 2 files changed, 17 insertions(+), 38 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 06e8e93edf..9fcbe79649 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -20,7 +20,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install --pre -e .[release] + pip install -e .[release] - name: Build run: python setup.py sdist bdist_wheel diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ada020b6a1..276a79524f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -23,30 +23,20 @@ jobs: - name: Install Dependencies run: | - python -m pip install --upgrade uv - uv venv .venv - source .venv/bin/activate - uv pip install .[lint] + python -m pip install --upgrade pip + pip install .[lint] - name: Run Black - run: | - source .venv/bin/activate - black --check . + run: black --check . - name: Run isort - run: | - source .venv/bin/activate - isort --check-only . + run: isort --check-only . - name: Run flake8 - run: | - source .venv/bin/activate - flake8 . + run: flake8 . - name: Run mdformat - run: | - source .venv/bin/activate - mdformat . --check + run: mdformat . --check type-check: runs-on: ubuntu-latest @@ -61,15 +51,11 @@ jobs: - name: Install Dependencies run: | - python -m pip install --upgrade uv - uv venv .venv - source .venv/bin/activate - uv pip install .[lint,test] + python -m pip install --upgrade pip + pip install .[lint,test] - name: Run MyPy - run: | - source .venv/bin/activate - mypy . + run: mypy . functional: runs-on: ${{ matrix.os }} @@ -104,15 +90,12 @@ jobs: - name: Install Dependencies run: | - python -m pip install --upgrade uv - uv venv .venv - source .venv/bin/activate - uv pip install .[test] + python -m pip install --upgrade pip + pip uninstall eth-ape --yes + pip install .[test] - name: Run Tests - run: | - source .venv/bin/activate - pytest -m "not fuzzing" -s --cov=src -n auto --dist loadgroup + run: pytest -m "not fuzzing" -s --cov=src -n auto --dist loadgroup fuzzing: runs-on: ubuntu-latest @@ -130,12 +113,8 @@ jobs: - name: Install Dependencies run: | - python -m pip install --upgrade uv - uv venv .venv - source .venv/bin/activate - uv pip install --pre .[test] + python -m pip install --upgrade pip + pip install .[test] - name: Run Tests - run: | - source .venv/bin/activate - pytest -m "fuzzing" --no-cov -s + run: pytest -m "fuzzing" --no-cov -s From 2d3a0474c0ac624f4253492be09a137d829f8d21 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 18:25:26 -0400 Subject: [PATCH 18/30] remove PIP_COMMAND from __init__ --- src/ape/cli/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ape/cli/__init__.py b/src/ape/cli/__init__.py index 98f5b884d7..c3dd09ef97 100644 --- a/src/ape/cli/__init__.py +++ b/src/ape/cli/__init__.py @@ -27,7 +27,6 @@ verbosity_option, ) from ape.cli.paramtype import AllFilePaths, Path -from ape.plugins._utils import PIP_COMMAND __all__ = [ "account_option", @@ -51,7 +50,6 @@ "output_format_option", "OutputFormat", "Path", - "PIP_COMMAND", "PromptChoice", "select_account", "skip_confirmation_option", From 04e257f1331dcb43797f437c56d8c01a885594ba Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 18:29:23 -0400 Subject: [PATCH 19/30] lint with black --- src/ape_plugins/_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ape_plugins/_cli.py b/src/ape_plugins/_cli.py index 32389d84a1..7d15e69538 100644 --- a/src/ape_plugins/_cli.py +++ b/src/ape_plugins/_cli.py @@ -191,7 +191,7 @@ def uninstall(cli_ctx, plugins, skip_confirmation): skip_confirmation or click.confirm(f"Remove plugin '{plugin}'?") ): cli_ctx.logger.info(f"Uninstalling '{plugin.name}'...") - args = [*PIP_COMMAND,"uninstall", "-y", plugin.package_name, "--quiet"] + args = [*PIP_COMMAND, "uninstall", "-y", plugin.package_name, "--quiet"] # NOTE: Be *extremely careful* with this command, as it modifies the user's # installed packages, to potentially catastrophic results From da6ff689191c6f23c5ce2391437d2278f8c54388 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 18:29:52 -0400 Subject: [PATCH 20/30] lint with isort --- src/ape/plugins/__init__.py | 2 +- src/ape_plugins/_cli.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ape/plugins/__init__.py b/src/ape/plugins/__init__.py index e99d5c69e3..c38bc976f8 100644 --- a/src/ape/plugins/__init__.py +++ b/src/ape/plugins/__init__.py @@ -9,6 +9,7 @@ from ape.utils.basemodel import _assert_not_ipython_check from ape.utils.misc import log_instead_of_fail +from ._utils import PIP_COMMAND from .account import AccountPlugin from .compiler import CompilerPlugin from .config import Config @@ -18,7 +19,6 @@ from .pluggy_patch import plugin_manager as pluggy_manager from .project import DependencyPlugin, ProjectPlugin from .query import QueryPlugin -from ._utils import PIP_COMMAND class PluginError(Exception): diff --git a/src/ape_plugins/_cli.py b/src/ape_plugins/_cli.py index 7d15e69538..7ea76f79e6 100644 --- a/src/ape_plugins/_cli.py +++ b/src/ape_plugins/_cli.py @@ -10,8 +10,8 @@ from ape.logging import logger from ape.managers.config import CONFIG_FILE_NAME from ape.plugins._utils import ( - ModifyPluginResultHandler, PIP_COMMAND, + ModifyPluginResultHandler, PluginMetadata, PluginMetadataList, PluginType, From c911bd74e0c6dbb7d2adc3e1641a0a5777945bfc Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 18:44:34 -0400 Subject: [PATCH 21/30] move PluginManager to src/ape/managers/plugins.py and clean_plugin_name to ape/plugins/_utils to avoid circular import --- src/ape/_cli.py | 3 +- src/ape/api/networks.py | 4 +- src/ape/managers/plugins.py | 154 +++++++++++++++++++++++++++++++++ src/ape/managers/query.py | 2 +- src/ape/plugins/__init__.py | 167 ++---------------------------------- src/ape/plugins/_utils.py | 8 +- 6 files changed, 167 insertions(+), 171 deletions(-) create mode 100644 src/ape/managers/plugins.py diff --git a/src/ape/_cli.py b/src/ape/_cli.py index a4d85abfb9..3b6aabf588 100644 --- a/src/ape/_cli.py +++ b/src/ape/_cli.py @@ -11,8 +11,7 @@ from ape.cli import ape_cli_context from ape.exceptions import Abort, ApeException, handle_ape_exception from ape.logging import logger -from ape.plugins import clean_plugin_name -from ape.plugins._utils import PluginMetadataList +from ape.plugins._utils import PluginMetadataList, clean_plugin_name from ape.utils.basemodel import ManagerAccessMixin _DIFFLIB_CUT_OFF = 0.6 diff --git a/src/ape/api/networks.py b/src/ape/api/networks.py index 03a9b1c6a3..d916a97dfc 100644 --- a/src/ape/api/networks.py +++ b/src/ape/api/networks.py @@ -966,10 +966,10 @@ def providers(self): # -> Dict[str, Partial[ProviderAPI]] Dict[str, partial[:class:`~ape.api.providers.ProviderAPI`]] """ - from ape.plugins import clean_plugin_name + from ape.plugins._utils import clean_plugin_name providers = {} - for plugin_name, plugin_tuple in self.plugin_manager.providers: + for _, plugin_tuple in self.plugin_manager.providers: ecosystem_name, network_name, provider_class = plugin_tuple provider_name = clean_plugin_name(provider_class.__module__.split(".")[0]) diff --git a/src/ape/managers/plugins.py b/src/ape/managers/plugins.py new file mode 100644 index 0000000000..c9b9a04d63 --- /dev/null +++ b/src/ape/managers/plugins.py @@ -0,0 +1,154 @@ +import importlib +import pkgutil +import subprocess +from typing import Generator, Iterator, List, Optional, Set, Tuple + +from ape.__modules__ import __modules__ +from ape.exceptions import ApeAttributeError +from ape.logging import logger +from ape.plugins._utils import PIP_COMMAND, clean_plugin_name +from ape.utils.basemodel import _assert_not_ipython_check +from ape.utils.misc import log_instead_of_fail + + +class PluginManager: + _unimplemented_plugins: List[str] = [] + + def __init__(self) -> None: + self.__registered = False + + @log_instead_of_fail(default="") + def __repr__(self) -> str: + return f"<{PluginManager.__name__}>" + + def __getattr__(self, attr_name: str) -> Iterator[Tuple[str, Tuple]]: + _assert_not_ipython_check(attr_name) + + # NOTE: The first time this method is called, the actual + # plugin registration occurs. Registration only happens once. + self._register_plugins() + + if not hasattr(pluggy_manager.hook, attr_name): + raise ApeAttributeError(f"{PluginManager.__name__} has no attribute '{attr_name}'.") + + # Do this to get access to the package name + hook_fn = getattr(pluggy_manager.hook, attr_name) + hookimpls = hook_fn.get_hookimpls() + + def get_plugin_name_and_hookfn(h): + return h.plugin_name, getattr(h.plugin, attr_name)() + + for plugin_name, results in map(get_plugin_name_and_hookfn, hookimpls): + # NOTE: Some plugins return a tuple and some return iterators + if not isinstance(results, Generator): + validated_plugin = self._validate_plugin(plugin_name, results) + if validated_plugin: + yield validated_plugin + else: + # Only if it's an iterator, provider results as a series + for result in results: + validated_plugin = self._validate_plugin(plugin_name, result) + if validated_plugin: + yield validated_plugin + + @property + def registered_plugins(self) -> Set[str]: + self._register_plugins() + return {x[0] for x in pluggy_manager.list_name_plugin()} + + @functools.cached_property + def _plugin_modules(self) -> Tuple[str, ...]: + core_plugin_module_names = {n for _, n, _ in pkgutil.iter_modules() if n.startswith("ape_")} + if PIP_COMMAND[0] != "uv": + # NOTE: Unable to use pkgutil.iter_modules() for installed plugins + # because it does not work with editable installs. + # See https://github.com/python/cpython/issues/99805. + result = subprocess.check_output( + ["pip", "list", "--format", "freeze", "--disable-pip-version-check"] + ) + packages = result.decode("utf8").splitlines() + installed_plugin_module_names = { + p.split("==")[0].replace("-", "_") for p in packages if p.startswith("ape-") + } + else: + result = subprocess.check_output(["uv", "pip", "list"]) + # format is in the output of: + # Package Version Editable project location + # ------------------------- ------------------------------- ------------------------- + # aiosignal 1.3.1 + # annotated-types 0.6.0 + # skip the header + packages = result.decode("utf8").splitlines()[2:] + installed_plugin_module_names = { + p.split(" ")[0].replace("-", "_") for p in packages if p.startswith("ape-") + } + + # NOTE: Returns tuple because this shouldn't change. + return tuple(installed_plugin_module_names.union(core_plugin_module_names)) + + def _register_plugins(self): + if self.__registered: + return + + for module_name in self._plugin_modules: + try: + module = importlib.import_module(module_name) + pluggy_manager.register(module) + except Exception as err: + if module_name in __modules__: + # Always raise core plugin registration errors. + raise + + logger.warn_from_exception(err, f"Error loading plugin package '{module_name}'.") + + self.__registered = True + + def _validate_plugin(self, plugin_name: str, plugin_cls) -> Optional[Tuple[str, Tuple]]: + if valid_impl(plugin_cls): + return clean_plugin_name(plugin_name), plugin_cls + else: + self._warn_not_fully_implemented_error(plugin_cls, plugin_name) + return None + + def _warn_not_fully_implemented_error(self, results, plugin_name): + if plugin_name in self._unimplemented_plugins: + # Already warned + return + + unimplemented_methods = [] + + # Find the best API name to warn about. + if isinstance(results, (list, tuple)): + classes = [p for p in results if hasattr(p, "__name__")] + if classes: + # Likely only ever a single class in a registration, but just in case. + api_name = " - ".join([p.__name__ for p in classes if hasattr(p, "__name__")]) + for api_cls in classes: + if ( + abstract_methods := getattr(api_cls, "__abstractmethods__", None) + ) and isinstance(abstract_methods, dict): + unimplemented_methods.extend(api_cls.__abstractmethods__) + + else: + # This would only happen if the registration consisted of all primitives. + api_name = " - ".join(results) + + elif hasattr(results, "__name__"): + api_name = results.__name__ + if (abstract_methods := getattr(results, "__abstractmethods__", None)) and isinstance( + abstract_methods, dict + ): + unimplemented_methods.extend(results.__abstractmethods__) + + else: + api_name = results + + message = f"'{api_name}' from '{plugin_name}' is not fully implemented." + if unimplemented_methods: + methods_str = ", ".join(unimplemented_methods) + message = f"{message} Remaining abstract methods: '{methods_str}'." + + logger.warning(message) + + # Record so we don't warn repeatedly + self._unimplemented_plugins.append(plugin_name) diff --git a/src/ape/managers/query.py b/src/ape/managers/query.py index 44401f2d76..d7b18018e0 100644 --- a/src/ape/managers/query.py +++ b/src/ape/managers/query.py @@ -15,7 +15,7 @@ from ape.contracts.base import ContractLog, LogFilter from ape.exceptions import QueryEngineError from ape.logging import logger -from ape.plugins import clean_plugin_name +from ape.plugins._utils import clean_plugin_name from ape.utils import ManagerAccessMixin, cached_property, singledispatchmethod diff --git a/src/ape/plugins/__init__.py b/src/ape/plugins/__init__.py index c38bc976f8..cf77749169 100644 --- a/src/ape/plugins/__init__.py +++ b/src/ape/plugins/__init__.py @@ -1,15 +1,14 @@ import functools +<<<<<<< HEAD import importlib from importlib.metadata import distributions from typing import Any, Callable, Generator, Iterator, List, Optional, Set, Tuple, Type +======= +from typing import Any, Callable, Type +>>>>>>> 797ed67c (move PluginManager to src/ape/managers/plugins.py and clean_plugin_name to ape/plugins/_utils to avoid circular import) -from ape.__modules__ import __modules__ -from ape.exceptions import ApeAttributeError -from ape.logging import logger -from ape.utils.basemodel import _assert_not_ipython_check -from ape.utils.misc import log_instead_of_fail +from ape.managers.plugins import PluginManager -from ._utils import PIP_COMMAND from .account import AccountPlugin from .compiler import CompilerPlugin from .config import Config @@ -46,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")] @@ -120,159 +115,7 @@ def valid_impl(api_class: Any) -> bool: return len(api_class.__abstractmethods__) == 0 -class PluginManager: - _unimplemented_plugins: List[str] = [] - - def __init__(self) -> None: - self.__registered = False - - @log_instead_of_fail(default="") - def __repr__(self) -> str: - return f"<{PluginManager.__name__}>" - - def __getattr__(self, attr_name: str) -> Iterator[Tuple[str, Tuple]]: - _assert_not_ipython_check(attr_name) - - # NOTE: The first time this method is called, the actual - # plugin registration occurs. Registration only happens once. - self._register_plugins() - - if not hasattr(pluggy_manager.hook, attr_name): - raise ApeAttributeError(f"{PluginManager.__name__} has no attribute '{attr_name}'.") - - # Do this to get access to the package name - hook_fn = getattr(pluggy_manager.hook, attr_name) - hookimpls = hook_fn.get_hookimpls() - - def get_plugin_name_and_hookfn(h): - return h.plugin_name, getattr(h.plugin, attr_name)() - - for plugin_name, results in map(get_plugin_name_and_hookfn, hookimpls): - # NOTE: Some plugins return a tuple and some return iterators - if not isinstance(results, Generator): - validated_plugin = self._validate_plugin(plugin_name, results) - if validated_plugin: - yield validated_plugin - else: - # Only if it's an iterator, provider results as a series - for result in results: - validated_plugin = self._validate_plugin(plugin_name, result) - if validated_plugin: - yield validated_plugin - - @property - def registered_plugins(self) -> Set[str]: - self._register_plugins() - return {x[0] for x in pluggy_manager.list_name_plugin()} - - @functools.cached_property - def _plugin_modules(self) -> Tuple[str, ...]: - core_plugin_module_names = {n for _, n, _ in pkgutil.iter_modules() if n.startswith("ape_")} - if PIP_COMMAND[0] != "uv": - # NOTE: Unable to use pkgutil.iter_modules() for installed plugins - # because it does not work with editable installs. - # See https://github.com/python/cpython/issues/99805. - result = subprocess.check_output( - ["pip", "list", "--format", "freeze", "--disable-pip-version-check"] - ) - packages = result.decode("utf8").splitlines() - installed_plugin_module_names = { - p.split("==")[0].replace("-", "_") for p in packages if p.startswith("ape-") - } - else: - result = subprocess.check_output(["uv", "pip", "list"]) - # format is in the output of: - # Package Version Editable project location - # ------------------------- ------------------------------- ------------------------- - # aiosignal 1.3.1 - # annotated-types 0.6.0 - # skip the header - packages = result.decode("utf8").splitlines()[2:] - installed_plugin_module_names = { - p.split(" ")[0].replace("-", "_") for p in packages if p.startswith("ape-") - } - - # NOTE: Returns tuple because this shouldn't change. - return tuple(installed_plugin_module_names.union(core_plugin_module_names)) - - 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]) - - 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__: - # Always raise core plugin registration errors. - raise - - logger.warn_from_exception(err, f"Error loading plugin package '{module_name}'.") - - self.__registered = True - - def _validate_plugin(self, plugin_name: str, plugin_cls) -> Optional[Tuple[str, Tuple]]: - if valid_impl(plugin_cls): - return clean_plugin_name(plugin_name), plugin_cls - else: - self._warn_not_fully_implemented_error(plugin_cls, plugin_name) - return None - - def _warn_not_fully_implemented_error(self, results, plugin_name): - if plugin_name in self._unimplemented_plugins: - # Already warned - return - - unimplemented_methods = [] - - # Find the best API name to warn about. - if isinstance(results, (list, tuple)): - classes = [p for p in results if hasattr(p, "__name__")] - if classes: - # Likely only ever a single class in a registration, but just in case. - api_name = " - ".join([p.__name__ for p in classes if hasattr(p, "__name__")]) - for api_cls in classes: - if ( - abstract_methods := getattr(api_cls, "__abstractmethods__", None) - ) and isinstance(abstract_methods, dict): - unimplemented_methods.extend(api_cls.__abstractmethods__) - - else: - # This would only happen if the registration consisted of all primitives. - api_name = " - ".join(results) - - elif hasattr(results, "__name__"): - api_name = results.__name__ - if (abstract_methods := getattr(results, "__abstractmethods__", None)) and isinstance( - abstract_methods, dict - ): - unimplemented_methods.extend(results.__abstractmethods__) - - else: - api_name = results - - message = f"'{api_name}' from '{plugin_name}' is not fully implemented." - if unimplemented_methods: - methods_str = ", ".join(unimplemented_methods) - message = f"{message} Remaining abstract methods: '{methods_str}'." - - logger.warning(message) - - # Record so we don't warn repeatedly - self._unimplemented_plugins.append(plugin_name) - - __all__ = [ "PluginManager", - "clean_plugin_name", "register", ] diff --git a/src/ape/plugins/_utils.py b/src/ape/plugins/_utils.py index e2d7f50946..efa2a0e814 100644 --- a/src/ape/plugins/_utils.py +++ b/src/ape/plugins/_utils.py @@ -11,7 +11,6 @@ from ape.__modules__ import __modules__ from ape.logging import logger -from ape.plugins import clean_plugin_name from ape.utils import BaseInterfaceModel, get_package_version, github_client, log_instead_of_fail from ape.utils.basemodel import BaseModel from ape.utils.misc import _get_distributions @@ -24,6 +23,10 @@ PIP_COMMAND = ["uv", "pip"] if which("uv") else [sys.executable, "-m", "pip"] +def clean_plugin_name(name: str) -> str: + return name.replace("_", "-").replace("ape-", "") + + class ApeVersion: def __str__(self) -> str: return str(self.version) @@ -221,9 +224,6 @@ def validate_name(cls, values): # Just some small validation so you can't put a repo # that isn't this plugin here. NOTE: Forks should still work. raise ValueError("Plugin mismatch with remote git version.") - - version = version - elif not version: # Only check name for version constraint if not in version. # NOTE: This happens when using the CLI to provide version constraints. From 6e9cccb3c61adfd0f71756c2281f1edcba4df5e4 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 18:58:17 -0400 Subject: [PATCH 22/30] add .venv to flake8 ignore --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 664d5c307e..f455fce233 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,7 @@ [flake8] max-line-length = 100 exclude = + .venv venv* docs build From 3029c53864a126db00a0093c7477c4d6ca1e5bf7 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 15 Apr 2024 18:59:19 -0400 Subject: [PATCH 23/30] move valid_impl and PluginManager import out of ape/plugins/__init__ --- src/ape/managers/__init__.py | 2 +- src/ape/managers/plugins.py | 26 +++++++++++++++++++++++++- src/ape/plugins/__init__.py | 33 +-------------------------------- src/ape/utils/basemodel.py | 2 +- 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/ape/managers/__init__.py b/src/ape/managers/__init__.py index 114b8978b6..7513040e03 100644 --- a/src/ape/managers/__init__.py +++ b/src/ape/managers/__init__.py @@ -1,6 +1,5 @@ from pathlib import Path as _Path -from ape.plugins import PluginManager from ape.utils import USER_AGENT, ManagerAccessMixin from .accounts import AccountManager @@ -9,6 +8,7 @@ from .config import ConfigManager from .converters import ConversionManager from .networks import NetworkManager +from .plugins import PluginManager from .project import DependencyManager, ProjectManager from .query import QueryManager diff --git a/src/ape/managers/plugins.py b/src/ape/managers/plugins.py index c9b9a04d63..59629e8ae7 100644 --- a/src/ape/managers/plugins.py +++ b/src/ape/managers/plugins.py @@ -1,16 +1,40 @@ +import functools import importlib import pkgutil import subprocess -from typing import Generator, Iterator, List, Optional, Set, Tuple +from typing import Any, Generator, Iterator, List, Optional, Set, Tuple from ape.__modules__ import __modules__ from ape.exceptions import ApeAttributeError from ape.logging import logger from ape.plugins._utils import PIP_COMMAND, clean_plugin_name +from ape.plugins.pluggy_patch import plugin_manager as pluggy_manager from ape.utils.basemodel import _assert_not_ipython_check from ape.utils.misc import log_instead_of_fail +def valid_impl(api_class: Any) -> bool: + """ + Check if an API class is valid. The class must not have any unimplemented + abstract methods. + + Args: + api_class (any) + + Returns: + bool + """ + + if isinstance(api_class, tuple): + return all(valid_impl(c) for c in api_class) + + # Is not an ABC base class or abstractdataclass + if not hasattr(api_class, "__abstractmethods__"): + return True # not an abstract class + + return len(api_class.__abstractmethods__) == 0 + + class PluginManager: _unimplemented_plugins: List[str] = [] diff --git a/src/ape/plugins/__init__.py b/src/ape/plugins/__init__.py index cf77749169..54171a6135 100644 --- a/src/ape/plugins/__init__.py +++ b/src/ape/plugins/__init__.py @@ -1,13 +1,5 @@ import functools -<<<<<<< HEAD -import importlib -from importlib.metadata import distributions -from typing import Any, Callable, Generator, Iterator, List, Optional, Set, Tuple, Type -======= -from typing import Any, Callable, Type ->>>>>>> 797ed67c (move PluginManager to src/ape/managers/plugins.py and clean_plugin_name to ape/plugins/_utils to avoid circular import) - -from ape.managers.plugins import PluginManager +from typing import Callable, Type from .account import AccountPlugin from .compiler import CompilerPlugin @@ -93,29 +85,6 @@ def check_hook(plugin_type, hookimpl_kwargs, fn): return functools.partial(check_hook, plugin_type, hookimpl_kwargs) -def valid_impl(api_class: Any) -> bool: - """ - Check if an API class is valid. The class must not have any unimplemented - abstract methods. - - Args: - api_class (any) - - Returns: - bool - """ - - if isinstance(api_class, tuple): - return all(valid_impl(c) for c in api_class) - - # Is not an ABC base class or abstractdataclass - if not hasattr(api_class, "__abstractmethods__"): - return True # not an abstract class - - return len(api_class.__abstractmethods__) == 0 - - __all__ = [ - "PluginManager", "register", ] diff --git a/src/ape/utils/basemodel.py b/src/ape/utils/basemodel.py index ad9227e2de..3c78bdb5fd 100644 --- a/src/ape/utils/basemodel.py +++ b/src/ape/utils/basemodel.py @@ -30,9 +30,9 @@ from ape.managers.config import ConfigManager from ape.managers.converters import ConversionManager from ape.managers.networks import NetworkManager + from ape.managers.plugins import PluginManager from ape.managers.project import DependencyManager, ProjectManager from ape.managers.query import QueryManager - from ape.plugins import PluginManager from ape.pytest.runners import PytestApeRunner From 417ad576cd8957ebf22f89d0c5871a330d38ef21 Mon Sep 17 00:00:00 2001 From: Mihai Date: Wed, 17 Apr 2024 11:28:38 -0400 Subject: [PATCH 24/30] fix rebase --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 72de80f333..270cfe7a26 100644 --- a/setup.py +++ b/setup.py @@ -123,11 +123,11 @@ "eth-typing>=3.5.2,<4", "eth-utils>=2.3.1,<3", "py-geth>=4.4.0,<5", - "web3[tester]>=6.16.0,<7", + "web3[tester]>=6.16.0,<6.17.1", # ** Dependencies maintained by ApeWorX ** - "eip712>=0.2.3,<0.4", - "ethpm-types>=0.6.7,<0.7", - "eth_pydantic_types>=0.1.0a5,<0.2", + "eip712>=0.2.7,<0.4", + "ethpm-types>=0.6.9,<0.7", + "eth_pydantic_types>=0.1.0,<0.2", "evmchains>=0.0.2,<0.1", "evm-trace>=0.1.2", ], From d158695789e689c507e10c5f8ba333d9d344d06d Mon Sep 17 00:00:00 2001 From: Mihai Date: Wed, 17 Apr 2024 11:52:26 -0400 Subject: [PATCH 25/30] update dependencies from main --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 270cfe7a26..eab553124b 100644 --- a/setup.py +++ b/setup.py @@ -125,11 +125,11 @@ "py-geth>=4.4.0,<5", "web3[tester]>=6.16.0,<6.17.1", # ** Dependencies maintained by ApeWorX ** - "eip712>=0.2.7,<0.4", + "eip712>=0.2.7,<0.3", "ethpm-types>=0.6.9,<0.7", "eth_pydantic_types>=0.1.0,<0.2", - "evmchains>=0.0.2,<0.1", - "evm-trace>=0.1.2", + "evmchains>=0.0.6,<0.1", + "evm-trace>=0.1.3,<0.2", ], entry_points={ "console_scripts": ["ape=ape._cli:cli"], From c8fea1c2aa9ef6784d7422f9f9a7bb6ba2e4eaa3 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 23 Apr 2024 09:39:00 -0500 Subject: [PATCH 26/30] chore: put in missed merge --- src/ape/managers/plugins.py | 41 +++++++++---------------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/src/ape/managers/plugins.py b/src/ape/managers/plugins.py index 59629e8ae7..7eb819605f 100644 --- a/src/ape/managers/plugins.py +++ b/src/ape/managers/plugins.py @@ -2,6 +2,7 @@ import importlib import pkgutil import subprocess +from importlib.metadata import distributions from typing import Any, Generator, Iterator, List, Optional, Set, Tuple from ape.__modules__ import __modules__ @@ -80,41 +81,19 @@ def registered_plugins(self) -> Set[str]: self._register_plugins() return {x[0] for x in pluggy_manager.list_name_plugin()} - @functools.cached_property - def _plugin_modules(self) -> Tuple[str, ...]: - core_plugin_module_names = {n for _, n, _ in pkgutil.iter_modules() if n.startswith("ape_")} - if PIP_COMMAND[0] != "uv": - # NOTE: Unable to use pkgutil.iter_modules() for installed plugins - # because it does not work with editable installs. - # See https://github.com/python/cpython/issues/99805. - result = subprocess.check_output( - ["pip", "list", "--format", "freeze", "--disable-pip-version-check"] - ) - packages = result.decode("utf8").splitlines() - installed_plugin_module_names = { - p.split("==")[0].replace("-", "_") for p in packages if p.startswith("ape-") - } - else: - result = subprocess.check_output(["uv", "pip", "list"]) - # format is in the output of: - # Package Version Editable project location - # ------------------------- ------------------------------- ------------------------- - # aiosignal 1.3.1 - # annotated-types 0.6.0 - # skip the header - packages = result.decode("utf8").splitlines()[2:] - installed_plugin_module_names = { - p.split(" ")[0].replace("-", "_") for p in packages if p.startswith("ape-") - } - - # NOTE: Returns tuple because this shouldn't change. - return tuple(installed_plugin_module_names.union(core_plugin_module_names)) - def _register_plugins(self): if self.__registered: return - for module_name in self._plugin_modules: + 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]) + + for module_name in plugin_modules: try: module = importlib.import_module(module_name) pluggy_manager.register(module) From 93ae0cdeffb94ce246629bcd80877cc53cec1ea7 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 23 Apr 2024 09:42:27 -0500 Subject: [PATCH 27/30] docs: fix str --- src/ape_plugins/_cli.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ape_plugins/_cli.py b/src/ape_plugins/_cli.py index 7ea76f79e6..9e7e4c385a 100644 --- a/src/ape_plugins/_cli.py +++ b/src/ape_plugins/_cli.py @@ -233,8 +233,11 @@ def _install(name, spec) -> int: Helper function to install or update a Python package using pip. Args: - name (str): The package name. - spec (str): Version specifier, e.g., '==1.0.0', '>=1.0.0', etc. + name (str): The package name. + spec (str): Version specifier, e.g., '==1.0.0', '>=1.0.0', etc. + + Returns: + The process return-code. """ args = [*PIP_COMMAND, "install", f"{name}{spec}", "--quiet"] From 399bf392885ffcfebd16cb0820e166416ab3e280 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 23 Apr 2024 09:42:56 -0500 Subject: [PATCH 28/30] chore: missed lint --- src/ape/managers/plugins.py | 5 +---- src/ape_plugins/_cli.py | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/ape/managers/plugins.py b/src/ape/managers/plugins.py index 7eb819605f..82bf32448b 100644 --- a/src/ape/managers/plugins.py +++ b/src/ape/managers/plugins.py @@ -1,14 +1,11 @@ -import functools import importlib -import pkgutil -import subprocess from importlib.metadata import distributions from typing import Any, Generator, Iterator, List, Optional, Set, Tuple from ape.__modules__ import __modules__ from ape.exceptions import ApeAttributeError from ape.logging import logger -from ape.plugins._utils import PIP_COMMAND, clean_plugin_name +from ape.plugins._utils import clean_plugin_name from ape.plugins.pluggy_patch import plugin_manager as pluggy_manager from ape.utils.basemodel import _assert_not_ipython_check from ape.utils.misc import log_instead_of_fail diff --git a/src/ape_plugins/_cli.py b/src/ape_plugins/_cli.py index 9e7e4c385a..cdca353412 100644 --- a/src/ape_plugins/_cli.py +++ b/src/ape_plugins/_cli.py @@ -239,11 +239,11 @@ def _install(name, spec) -> int: Returns: The process return-code. """ - args = [*PIP_COMMAND, "install", f"{name}{spec}", "--quiet"] + arguments = [*PIP_COMMAND, "install", f"{name}{spec}", "--quiet"] # Run the installation process and capture output for error checking completed_process = subprocess.run( - args, + arguments, capture_output=True, text=True, # Output as string check=False, # Allow manual error handling From 0e71415239fa8d8e9c79293d3de7f13389c86934 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 23 Apr 2024 10:13:56 -0500 Subject: [PATCH 29/30] test: uv tests --- src/ape/plugins/_utils.py | 21 ++++++++++-- src/ape_plugins/_cli.py | 4 +-- tests/functional/test_plugins.py | 57 ++++++++++++++++++++++++-------- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src/ape/plugins/_utils.py b/src/ape/plugins/_utils.py index b949a4501d..0afe20da55 100644 --- a/src/ape/plugins/_utils.py +++ b/src/ape/plugins/_utils.py @@ -210,6 +210,12 @@ class PluginMetadata(BaseInterfaceModel): version: Optional[str] = None """The version requested, if there is one.""" + pip_command: List[str] = PIP_COMMAND + """ + The pip base command to use. + (NOTE: is a field mainly for testing purposes). + """ + @model_validator(mode="before") @classmethod def validate_name(cls, values): @@ -224,6 +230,7 @@ def validate_name(cls, values): # Just some small validation so you can't put a repo # that isn't this plugin here. NOTE: Forks should still work. raise ValueError("Plugin mismatch with remote git version.") + elif not version: # Only check name for version constraint if not in version. # NOTE: This happens when using the CLI to provide version constraints. @@ -236,7 +243,8 @@ def validate_name(cls, values): name, version = _split_name_and_version(name) break - return {"name": clean_plugin_name(name), "version": version} + pip_cmd = values.get("pip_command", PIP_COMMAND) + return {"name": clean_plugin_name(name), "version": version, "pip_command": pip_cmd} @cached_property def package_name(self) -> str: @@ -373,7 +381,7 @@ def _prepare_install( logger.warning(f"Plugin '{self.name}' is not an trusted plugin.") result_handler = ModifyPluginResultHandler(self) - pip_arguments = [*PIP_COMMAND, "install"] + pip_arguments = [*self.pip_command, "install"] if upgrade: logger.info(f"Upgrading '{self.name}' plugin ...") @@ -406,6 +414,15 @@ def _prepare_install( ) return None + def _get_uninstall_args(self) -> List[str]: + arguments = [*self.pip_command, "uninstall"] + + if self.pip_command[0] != "uv": + arguments.append("-y") + + arguments.extend((self.package_name, "--quiet")) + return arguments + class ModifyPluginResultHandler: def __init__(self, plugin: PluginMetadata): diff --git a/src/ape_plugins/_cli.py b/src/ape_plugins/_cli.py index cdca353412..a45704c6fb 100644 --- a/src/ape_plugins/_cli.py +++ b/src/ape_plugins/_cli.py @@ -191,12 +191,12 @@ def uninstall(cli_ctx, plugins, skip_confirmation): skip_confirmation or click.confirm(f"Remove plugin '{plugin}'?") ): cli_ctx.logger.info(f"Uninstalling '{plugin.name}'...") - args = [*PIP_COMMAND, "uninstall", "-y", plugin.package_name, "--quiet"] + arguments = plugin._get_uninstall_args() # NOTE: Be *extremely careful* with this command, as it modifies the user's # installed packages, to potentially catastrophic results # NOTE: This is not abstracted into another function *on purpose* - result = subprocess.call(args) + result = subprocess.call(arguments) failures_occurred = not result_handler.handle_uninstall_result(result) if failures_occurred: diff --git a/tests/functional/test_plugins.py b/tests/functional/test_plugins.py index c18c248691..efda410c13 100644 --- a/tests/functional/test_plugins.py +++ b/tests/functional/test_plugins.py @@ -25,6 +25,9 @@ "specifier", (f"<{ape_version[0]}", f">0.1,<{ape_version[0]}", f"==0.{int(ape_version[2]) - 1}"), ) +parametrize_pip_cmd = pytest.mark.parametrize( + "pip_command", [["python", "-m", "pip"], ["uv", "pip"]] +) @pytest.fixture(autouse=True) @@ -163,36 +166,48 @@ def test_is_available(self): metadata = PluginMetadata(name="foobar") assert not metadata.is_available - def test_prepare_install(self): - metadata = PluginMetadata(name=list(AVAILABLE_PLUGINS)[0]) + @parametrize_pip_cmd + def test_prepare_install(self, pip_command): + metadata = PluginMetadata(name=list(AVAILABLE_PLUGINS)[0], pip_command=pip_command) actual = metadata._prepare_install(skip_confirmation=True) assert actual is not None arguments = actual.get("args", []) - expected = [ - "-m", + shared = [ "pip", "install", f"ape-available>=0.{ape_version.minor},<0.{ape_version.minor + 1}", "--quiet", ] - assert "python" in arguments[0] - assert arguments[1:] == expected - - def test_prepare_install_upgrade(self): - metadata = PluginMetadata(name=list(AVAILABLE_PLUGINS)[0]) + if arguments[0] == "uv": + expected = ["uv", *shared] + assert arguments == expected + else: + expected = ["-m", *shared] + assert "python" in arguments[0] + assert arguments[1:] == expected + + @parametrize_pip_cmd + def test_prepare_install_upgrade(self, pip_command): + metadata = PluginMetadata(name=list(AVAILABLE_PLUGINS)[0], pip_command=pip_command) actual = metadata._prepare_install(upgrade=True, skip_confirmation=True) assert actual is not None arguments = actual.get("args", []) - expected = [ - "-m", + shared = [ "pip", "install", "--upgrade", f"ape-available>=0.{ape_version.minor},<0.{ape_version.minor + 1}", "--quiet", ] - assert "python" in arguments[0] - assert arguments[1:] == expected + + if pip_command[0].startswith("uv"): + expected = ["uv", *shared] + assert arguments == expected + + else: + expected = ["-m", *shared] + assert "python" in arguments[0] + assert arguments[1:] == expected @mark_specifiers_less_than_ape def test_prepare_install_version_smaller_than_ape(self, specifier, ape_caplog): @@ -223,6 +238,22 @@ def test_check_installed_dist_no_name_attr(self, mocker, get_dists_patch): # This triggers looping through the dist w/o a name attr. metadata.check_installed() + @parametrize_pip_cmd + def test_get_uninstall_args(self, pip_command): + metadata = PluginMetadata(name="dontmatter", pip_command=pip_command) + arguments = metadata._get_uninstall_args() + pip_cmd_len = len(metadata.pip_command) + + for idx, pip_pt in enumerate(pip_command): + assert arguments[idx] == pip_pt + + expected = ["uninstall"] + if pip_command[0] == "python": + expected.append("-y") + + expected.extend(("ape-dontmatter", "--quiet")) + assert arguments[pip_cmd_len:] == expected + class TestApePluginsRepr: def test_str(self, plugin_metadata): From bd50c363b6dec934a15b8e0258922335d0642081 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 23 Apr 2024 10:56:04 -0500 Subject: [PATCH 30/30] fix: bump trie pkg_resources bug --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index b37b438c1c..1b9405b878 100644 --- a/setup.py +++ b/setup.py @@ -122,6 +122,7 @@ "eth-typing>=3.5.2,<4", "eth-utils>=2.3.1,<3", "py-geth>=4.4.0,<5", + "trie>=3.0.0,<4", # Peer: stricter pin needed for uv support. "web3[tester]>=6.17.2,<7", # ** Dependencies maintained by ApeWorX ** "eip712>=0.2.7,<0.3",