Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: lazify imports for ape --help #155

Merged
merged 5 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading