Skip to content

Commit

Permalink
perf: lazify imports for ape --help (#155)
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Oct 29, 2024
1 parent 7bea8bb commit 7f79752
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 44 deletions.
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: check-yaml

Expand All @@ -10,7 +10,7 @@ repos:
- id: isort

- repo: https://github.com/psf/black
rev: 24.8.0
rev: 24.10.0
hooks:
- id: black
name: black
Expand All @@ -21,13 +21,13 @@ repos:
- id: flake8

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.1
rev: v1.13.0
hooks:
- id: mypy
additional_dependencies: [types-requests, types-setuptools, pydantic]
additional_dependencies: [types-requests, types-setuptools, flake8-pydantic, flake8-type-checking]

- repo: https://github.com/executablebooks/mdformat
rev: 0.7.17
rev: 0.7.18
hooks:
- id: mdformat
additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-pyproject]
Expand Down
37 changes: 34 additions & 3 deletions ape_solidity/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
from ape import plugins
from typing import Any

from ._utils import Extension
from .compiler import SolidityCompiler, SolidityConfig
from ape import plugins


@plugins.register(plugins.Config)
def config_class():
from .compiler import SolidityConfig

return SolidityConfig


@plugins.register(plugins.CompilerPlugin)
def register_compiler():
from ._utils import Extension
from .compiler import SolidityCompiler

return (Extension.SOL.value,), SolidityCompiler


def __getattr__(name: str) -> Any:
if name == "Extension":
from ._utils import Extension

return Extension

elif name == "SolidityCompiler":
from .compiler import SolidityCompiler

return SolidityCompiler

elif name == "SolidityConfig":
from .compiler import SolidityConfig

return SolidityConfig

else:
raise AttributeError(name)


__all__ = [
"Extension",
"SolidityCompiler",
"SolidityConfig",
]
2 changes: 1 addition & 1 deletion ape_solidity/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import ape
import click
from ape.cli import ape_cli_context, project_option
from ape.cli.options import ape_cli_context, project_option


@click.group
Expand Down
34 changes: 19 additions & 15 deletions ape_solidity/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
from typing import TYPE_CHECKING, Optional

from ape.exceptions import CompilerError, ProjectError
from ape.managers import ProjectManager
from ape.utils.basemodel import BaseModel, ManagerAccessMixin, classproperty
from ape.utils.os import get_relative_path
from pydantic import field_serializer

from ape_solidity._utils import get_single_import_lines

if TYPE_CHECKING:
from ape.managers.project import ProjectManager

from ape_solidity.compiler import SolidityCompiler


Expand All @@ -26,7 +27,7 @@ class ApeSolidityModel(BaseModel, ApeSolidityMixin):
pass


def _create_import_remapping(project: ProjectManager) -> dict[str, str]:
def _create_import_remapping(project: "ProjectManager") -> dict[str, str]:
prefix = f"{get_relative_path(project.contracts_folder, project.path)}"
specified = project.dependencies.install()

Expand Down Expand Up @@ -102,22 +103,22 @@ def __init__(self):
# Cache project paths to import remapping.
self._cache: dict[str, dict[str, str]] = {}

def __getitem__(self, project: ProjectManager) -> dict[str, str]:
def __getitem__(self, project: "ProjectManager") -> dict[str, str]:
if remapping := self._cache.get(f"{project.path}"):
return remapping

return self.add_project(project)

def add_project(self, project: ProjectManager) -> dict[str, str]:
def add_project(self, project: "ProjectManager") -> dict[str, str]:
remapping = _create_import_remapping(project)
return self.add(project, remapping)

def add(self, project: ProjectManager, remapping: dict[str, str]):
def add(self, project: "ProjectManager", remapping: dict[str, str]):
self._cache[f"{project.path}"] = remapping
return remapping

@classmethod
def get_import_remapping(cls, project: ProjectManager):
def get_import_remapping(cls, project: "ProjectManager"):
return _create_import_remapping(project)


Expand Down Expand Up @@ -147,7 +148,7 @@ def value(self) -> str:
return self.raw_value

@property
def dependency(self) -> Optional[ProjectManager]:
def dependency(self) -> Optional["ProjectManager"]:
if name := self.dependency_name:
if version := self.dependency_version:
return self.local_project.dependencies[name][version]
Expand All @@ -159,8 +160,8 @@ def parse_line(
cls,
value: str,
reference: Path,
project: ProjectManager,
dependency: Optional[ProjectManager] = None,
project: "ProjectManager",
dependency: Optional["ProjectManager"] = None,
) -> "ImportStatementMetadata":
quote = '"' if '"' in value else "'"
sep = "\\" if "\\" in value else "/"
Expand All @@ -186,14 +187,17 @@ def __hash__(self) -> int:
return hash(path)

def _resolve_source(
self, reference: Path, project: ProjectManager, dependency: Optional[ProjectManager] = None
self,
reference: Path,
project: "ProjectManager",
dependency: Optional["ProjectManager"] = None,
):
if not self._resolve_dependency(project, dependency=dependency):
# Handle non-dependencies.
self._resolve_import_remapping(project)
self._resolve_path(reference, project)

def _resolve_import_remapping(self, project: ProjectManager):
def _resolve_import_remapping(self, project: "ProjectManager"):
if self.value.startswith("."):
# Relative paths should not use import-remappings.
return
Expand All @@ -213,7 +217,7 @@ def _resolve_import_remapping(self, project: ProjectManager):
valid_matches, key=lambda x: len(x[0])
)

def _resolve_path(self, reference: Path, project: ProjectManager):
def _resolve_path(self, reference: Path, project: "ProjectManager"):
base_path = None
if self.value.startswith("."):
base_path = reference.parent
Expand All @@ -236,7 +240,7 @@ def _resolve_path(self, reference: Path, project: ProjectManager):
self.source_id = f"{get_relative_path(self.path, project.path)}"

def _resolve_dependency(
self, project: ProjectManager, dependency: Optional[ProjectManager] = None
self, project: "ProjectManager", dependency: Optional["ProjectManager"] = None
) -> bool:
config_project = dependency or project
# NOTE: Dependency is set if we are getting dependencies of dependencies.
Expand Down Expand Up @@ -340,9 +344,9 @@ def _serialize_import_statements(self, statements, info):
def from_source_files(
cls,
source_files: Iterable[Path],
project: ProjectManager,
project: "ProjectManager",
statements: Optional[dict[tuple[Path, str], set[ImportStatementMetadata]]] = None,
dependency: Optional[ProjectManager] = None,
dependency: Optional["ProjectManager"] = None,
) -> "SourceTree":
statements = statements or {}
for path in source_files:
Expand Down
15 changes: 9 additions & 6 deletions ape_solidity/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
from collections.abc import Iterable
from enum import Enum
from pathlib import Path
from typing import Optional, Union
from typing import TYPE_CHECKING, Optional, Union

from ape.exceptions import CompilerError
from ape.utils import pragma_str_to_specifier_set
from packaging.specifiers import SpecifierSet
from packaging.version import Version
from solcx.install import get_executable
from solcx.wrapper import get_solc_version as get_solc_version_from_binary

if TYPE_CHECKING:
from packaging.specifiers import SpecifierSet


OUTPUT_SELECTION = [
"abi",
"bin-runtime",
Expand Down Expand Up @@ -62,7 +65,7 @@ def get_single_import_lines(source_path: Path) -> list[str]:
return list(import_set)


def get_pragma_spec_from_path(source_file_path: Union[Path, str]) -> Optional[SpecifierSet]:
def get_pragma_spec_from_path(source_file_path: Union[Path, str]) -> Optional["SpecifierSet"]:
"""
Extracts pragma information from Solidity source code.
Expand All @@ -80,7 +83,7 @@ def get_pragma_spec_from_path(source_file_path: Union[Path, str]) -> Optional[Sp
return get_pragma_spec_from_str(source_str)


def get_pragma_spec_from_str(source_str: str) -> Optional[SpecifierSet]:
def get_pragma_spec_from_str(source_str: str) -> Optional["SpecifierSet"]:
if not (
pragma_match := next(
re.finditer(r"(?:\n|^)\s*pragma\s*solidity\s*([^;\n]*)", source_str), None
Expand All @@ -106,11 +109,11 @@ def add_commit_hash(version: Union[str, Version]) -> Version:
return get_solc_version_from_binary(solc, with_commit_hash=True)


def get_versions_can_use(pragma_spec: SpecifierSet, options: Iterable[Version]) -> list[Version]:
def get_versions_can_use(pragma_spec: "SpecifierSet", options: Iterable[Version]) -> list[Version]:
return sorted(list(pragma_spec.filter(options)), reverse=True)


def select_version(pragma_spec: SpecifierSet, options: Iterable[Version]) -> Optional[Version]:
def select_version(pragma_spec: "SpecifierSet", options: Iterable[Version]) -> Optional[Version]:
choices = get_versions_can_use(pragma_spec, options)
return choices[0] if choices else None

Expand Down
15 changes: 8 additions & 7 deletions ape_solidity/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
from collections import defaultdict
from collections.abc import Iterable, Iterator, Sequence
from pathlib import Path
from typing import Any, Optional, Union
from typing import TYPE_CHECKING, Any, Optional, Union

from ape.api import CompilerAPI, PluginConfig
from ape.contracts import ContractInstance
from ape.exceptions import CompilerError, ConfigError, ContractLogicError
from ape.logging import logger
from ape.managers.project import LocalProject, ProjectManager
Expand All @@ -15,7 +14,6 @@
from eth_pydantic_types import HexBytes
from eth_utils import add_0x_prefix, is_0x_prefixed
from ethpm_types.source import Compiler, Content
from packaging.specifiers import SpecifierSet
from packaging.version import Version
from pydantic import model_validator
from requests.exceptions import ConnectionError
Expand Down Expand Up @@ -50,6 +48,11 @@
SolcInstallError,
)

if TYPE_CHECKING:
from ape.contracts import ContractInstance
from packaging.specifiers import SpecifierSet


LICENSES_PATTERN = re.compile(r"(// SPDX-License-Identifier:\s*([^\n]*)\s)")

# Comment patterns
Expand Down Expand Up @@ -234,7 +237,7 @@ def _get_configured_version(
def _ape_version(self) -> Version:
return Version(version.split(".dev")[0].strip())

def add_library(self, *contracts: ContractInstance, project: Optional[ProjectManager] = None):
def add_library(self, *contracts: "ContractInstance", project: Optional[ProjectManager] = None):
"""
Set a library contract type address. This is useful when deploying a library
in a local network and then adding the address afterward. Now, when
Expand Down Expand Up @@ -521,8 +524,6 @@ def _compile(
input_contract_names.append(name)

for source_id, contracts_out in contracts.items():
# ast_data = output["sources"][source_id]["ast"]

for contract_name, ct_data in contracts_out.items():
if contract_name not in input_contract_names:
# Only return ContractTypes explicitly asked for.
Expand Down Expand Up @@ -784,7 +785,7 @@ def get_version_map_from_imports(
# is more predictable. Also, remove any lingering empties.
return {k: result[k] for k in sorted(result) if result[k]}

def _get_pramga_spec_from_str(self, source_str: str) -> Optional[SpecifierSet]:
def _get_pramga_spec_from_str(self, source_str: str) -> Optional["SpecifierSet"]:
if not (pragma_spec := get_pragma_spec_from_str(source_str)):
return None

Expand Down
8 changes: 5 additions & 3 deletions ape_solidity/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from enum import IntEnum
from typing import Union
from typing import TYPE_CHECKING, Union

from ape.exceptions import CompilerError, ConfigError, ContractLogicError
from ape.logging import LogLevel, logger
from solcx.exceptions import SolcError

if TYPE_CHECKING:
from solcx.exceptions import SolcError


class SolcInstallError(CompilerError):
Expand All @@ -25,7 +27,7 @@ class SolcCompileError(CompilerError):
account Ape's logging verbosity.
"""

def __init__(self, solc_error: SolcError):
def __init__(self, solc_error: "SolcError"):
self.solc_error = solc_error

def __str__(self) -> str:
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[flake8]
max-line-length = 100
ignore = E704,W503,PYD002,TC003,TC006
exclude =
venv*
docs
build
tests/node_modules
type-checking-pydantic-enabled = True
8 changes: 5 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
"pytest-benchmark", # For performance tests
],
"lint": [
"black>=24.8.0,<25", # Auto-formatter and linter
"mypy>=1.11.1,<2", # Static type analyzer
"black>=24.10.0,<25", # Auto-formatter and linter
"mypy>=1.13.0,<2", # Static type analyzer
"types-requests", # Needed for mypy type shed
"types-setuptools", # Needed for mypy type shed
"flake8>=7.1.1,<8", # Style linter
"flake8-pydantic", # For detecting issues with Pydantic models
"flake8-type-checking", # Detect imports to move in/out of type-checking blocks
"isort>=5.13.2,<6", # Import sorting linter
"mdformat>=0.7.17", # Auto-formatter for markdown
"mdformat>=0.7.18", # Auto-formatter for markdown
"mdformat-gfm>=0.3.5", # Needed for formatting GitHub-flavored markdown
"mdformat-frontmatter>=0.4.1", # Needed for frontmatters-style headers in issue templates
"mdformat-pyproject>=0.0.1", # Allows configuring in pyproject.toml
Expand Down
2 changes: 1 addition & 1 deletion tests/test_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ def test_compile_outputs_compiler_data_to_manifest(project, compiler):
actual = project.manifest.compilers[0]
assert actual.name == "solidity"
assert "CompilesOnce" in actual.contractTypes
assert actual.version == "0.8.27+commit.40a35a09"
assert actual.version == "0.8.28+commit.7893614a"
# Compiling again should not add the same compiler again.
_ = [c for c in compiler.compile((path,), project=project)]
length_again = len(project.manifest.compilers or [])
Expand Down

0 comments on commit 7f79752

Please sign in to comment.