Skip to content

Commit

Permalink
perf: make help faster
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Oct 23, 2024
1 parent a257cac commit 8e7e373
Show file tree
Hide file tree
Showing 49 changed files with 650 additions and 533 deletions.
72 changes: 27 additions & 45 deletions src/ape/__init__.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,13 @@
import signal
import threading
from typing import Any

if threading.current_thread() is threading.main_thread():
# If we are in the main thread, we can safely set the signal handler
signal.signal(signal.SIGINT, lambda s, f: _sys.exit(130))

import sys as _sys

from ape.managers.project import ProjectManager as Project
from ape.pytest.contextmanagers import RevertsContextManager
from ape.utils import ManagerAccessMixin as _ManagerAccessMixin

# Wiring together the application

config = _ManagerAccessMixin.config_manager
"""
The active configs for the current project. See :class:`ape.managers.config.ConfigManager`.
"""

# Main types we export for the user
compilers = _ManagerAccessMixin.compiler_manager
"""Manages compilers for the current project. See
:class:`ape.managers.compilers.CompilerManager`."""

networks = _ManagerAccessMixin.network_manager
"""Manages the networks for the current project. See
:class:`ape.managers.networks.NetworkManager`."""

chain = _ManagerAccessMixin.chain_manager
"""
The current connected blockchain; requires an active provider.
Useful for development purposes, such as controlling the state of the blockchain.
Also handy for querying data about the chain and managing local caches.
"""

accounts = _ManagerAccessMixin.account_manager
"""Manages accounts for the current project. See :class:`ape.managers.accounts.AccountManager`."""

project = _ManagerAccessMixin.local_project
"""The currently active project. See :class:`ape.managers.project.ProjectManager`."""

Contract = chain.contracts.instance_at
"""User-facing class for instantiating contracts."""

convert = _ManagerAccessMixin.conversion_manager.convert
"""Conversion utility function. See :class:`ape.managers.converters.ConversionManager`."""

reverts = RevertsContextManager
"""
Catch and expect contract logic reverts. Resembles ``pytest.raises()``.
"""

from importlib import import_module

__all__ = [
"accounts",
Expand All @@ -64,3 +21,28 @@
"Project", # So you can load other projects
"reverts",
]


def __getattr__(name: str) -> Any:
if name not in __all__:
raise AttributeError(name)

elif name == "reverts":
contextmanagers = import_module("ape.pytest.contextmanagers")
return contextmanagers.RevertsContextManager

elif name == "Contract":
access = import_module("ape.utils.basemodel").ManagerAccessMixin
return access.chain_manager.contracts.instance_at

else:
key = name
if name == "project":
key = "local_project"
elif name.endswith("s"):
key = f"{name[:-1]}_manager"
else:
key = f"{key}_manager"

basemodel = import_module("ape.utils.basemodel")
return getattr(basemodel.ManagerAccessMixin, key)
96 changes: 47 additions & 49 deletions src/ape/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import warnings
from collections.abc import Iterable
from gettext import gettext
from importlib import import_module
from importlib.metadata import entry_points
from pathlib import Path
from typing import Any, Optional
Expand All @@ -13,11 +14,10 @@
import yaml
from click import Context

from ape.cli import ape_cli_context
from ape.cli.options import ape_cli_context
from ape.exceptions import Abort, ApeException, ConfigError, handle_ape_exception
from ape.logging import logger
from ape.plugins._utils import PluginMetadataList, clean_plugin_name
from ape.utils.basemodel import ManagerAccessMixin
from ape.utils.basemodel import ManagerAccessMixin as access

_DIFFLIB_CUT_OFF = 0.6

Expand All @@ -30,16 +30,16 @@ def display_config(ctx, param, value):
click.echo("# Current configuration")

# NOTE: Using json-mode as yaml.dump requires JSON-like structure.
model = ManagerAccessMixin.local_project.config_manager.model_dump(mode="json")
model = access.local_project.config.model_dump(mode="json")

click.echo(yaml.dump(model))

ctx.exit() # NOTE: Must exit to bypass running ApeCLI


def _validate_config():
project = access.local_project
try:
_ = ManagerAccessMixin.local_project.config
_ = project.config
except ConfigError as err:
rich.print(err)
# Exit now to avoid weird problems.
Expand Down Expand Up @@ -68,40 +68,40 @@ def format_commands(self, ctx, formatter) -> None:

commands.append((subcommand, cmd))

# Allow for 3 times the default spacing.
if len(commands):
limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)

# Split the commands into 3 sections.
sections: dict[str, list[tuple[str, str]]] = {
"Core": [],
"Plugin": [],
"3rd-Party Plugin": [],
}

pl_metadata = PluginMetadataList.load(
ManagerAccessMixin.plugin_manager, include_available=False
)

for cli_name, cmd in commands:
help = cmd.get_short_help_str(limit)
plugin = pl_metadata.get_plugin(cli_name)
if not plugin:
continue

if plugin.in_core:
sections["Core"].append((cli_name, help))
elif plugin.is_installed and not plugin.is_third_party:
sections["Plugin"].append((cli_name, help))
else:
sections["3rd-Party Plugin"].append((cli_name, help))

for title, rows in sections.items():
if not rows:
continue

with formatter.section(gettext(f"{title} Commands")):
formatter.write_dl(rows)
if not commands:
return None

limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)

# Split the commands into 3 sections.
sections: dict[str, list[tuple[str, str]]] = {
"Core": [],
"Plugin": [],
"3rd-Party Plugin": [],
}
plugin_utils = import_module("ape.plugins._utils")
metadata_cls = plugin_utils.PluginMetadataList
plugin_manager = access.plugin_manager
pl_metadata = metadata_cls.load(plugin_manager, include_available=False)
for cli_name, cmd in commands:
help = cmd.get_short_help_str(limit)
plugin = pl_metadata.get_plugin(cli_name, check_available=False)
if plugin is None:
continue

if plugin.in_core:
sections["Core"].append((cli_name, help))
elif plugin.is_installed and not plugin.is_third_party:
sections["Plugin"].append((cli_name, help))
else:
sections["3rd-Party Plugin"].append((cli_name, help))

for title, rows in sections.items():
if not rows:
continue

with formatter.section(gettext(f"{title} Commands")):
formatter.write_dl(rows)

def invoke(self, ctx) -> Any:
try:
Expand Down Expand Up @@ -158,20 +158,18 @@ def commands(self) -> dict:
warnings.simplefilter("ignore")
eps = _entry_points.get(self._CLI_GROUP_NAME, []) # type: ignore

self._commands = {clean_plugin_name(cmd.name): cmd.load for cmd in eps}
commands = {cmd.name.replace("_", "-").replace("ape-", ""): cmd.load for cmd in eps}
self._commands = {k: commands[k] for k in sorted(commands)}
return self._commands

def list_commands(self, ctx) -> list[str]:
return list(sorted(self.commands))
return [k for k in self.commands]

def get_command(self, ctx, name) -> Optional[click.Command]:
if name in self.commands:
try:
return self.commands[name]()
except Exception as err:
logger.warn_from_exception(
err, f"Unable to load CLI endpoint for plugin 'ape_{name}'"
)
try:
return self.commands[name]()
except Exception as err:
logger.warn_from_exception(err, f"Unable to load CLI endpoint for plugin 'ape_{name}'")

# NOTE: don't return anything so Click displays proper error
return None
Expand Down
9 changes: 3 additions & 6 deletions src/ape/api/compiler.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from abc import abstractmethod
from collections.abc import Iterable, Iterator
from functools import cached_property
from pathlib import Path
Expand All @@ -13,12 +14,8 @@
from ape.exceptions import APINotImplementedError, ContractLogicError
from ape.types.coverage import ContractSourceCoverage
from ape.types.trace import SourceTraceback
from ape.utils import (
BaseInterfaceModel,
abstractmethod,
log_instead_of_fail,
raises_not_implemented,
)
from ape.utils.basemodel import BaseInterfaceModel
from ape.utils.misc import log_instead_of_fail, raises_not_implemented

if TYPE_CHECKING:
from ape.managers.project import ProjectManager
Expand Down
3 changes: 2 additions & 1 deletion src/ape/api/convert.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from abc import abstractmethod
from typing import Any, Generic, TypeVar

from ape.utils import BaseInterfaceModel, abstractmethod
from ape.utils.basemodel import BaseInterfaceModel

ConvertedType = TypeVar("ConvertedType")

Expand Down
3 changes: 2 additions & 1 deletion src/ape/api/projects.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from abc import abstractmethod
from functools import cached_property
from pathlib import Path
from typing import Optional

from pydantic import Field, field_validator

from ape.api.config import ApeConfig
from ape.utils import BaseInterfaceModel, abstractmethod
from ape.utils.basemodel import BaseInterfaceModel


class DependencyAPI(BaseInterfaceModel):
Expand Down
Loading

0 comments on commit 8e7e373

Please sign in to comment.