Skip to content

Commit

Permalink
use uv if it exists, oteherwise pip
Browse files Browse the repository at this point in the history
  • Loading branch information
wakamex committed Apr 15, 2024
1 parent 1d88392 commit e0a23df
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 29 deletions.
2 changes: 0 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
with open("./README.md") as readme:
long_description = readme.read()


setup(
name="eth-ape",
use_scm_version=True,
Expand All @@ -101,7 +100,6 @@
"lazyasd>=0.1.4",
"packaging>=23.0,<24",
"pandas>=1.3.0,<2",
"pip",
"pluggy>=1.3,<2",
"pydantic>=2.5.2,<3",
"pydantic-settings>=2.0.3,<3",
Expand Down
4 changes: 4 additions & 0 deletions src/ape/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import sys
from shutil import which
from ape.cli.arguments import (
contract_file_paths_argument,
existing_alias_argument,
Expand Down Expand Up @@ -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",
Expand Down
37 changes: 26 additions & 11 deletions src/ape/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,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
Expand Down Expand Up @@ -148,6 +149,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)
Expand All @@ -167,19 +169,32 @@ def registered_plugins(self) -> Set[str]:

@functools.cached_property
def _plugin_modules(self) -> Tuple[str, ...]:
# 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-")
}
core_plugin_module_names = {
n for _, n, ispkg in pkgutil.iter_modules() if n.startswith("ape_")
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))
Expand Down
3 changes: 2 additions & 1 deletion src/ape/plugins/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -361,7 +362,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 ...")
Expand Down
49 changes: 34 additions & 15 deletions src/ape_plugins/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -226,6 +226,35 @@ 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.
Expand All @@ -235,20 +264,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.")

0 comments on commit e0a23df

Please sign in to comment.