Skip to content

Commit

Permalink
fix: Python & NPM dependencies when not installed in Python/NPM cause…
Browse files Browse the repository at this point in the history
… `ape pm list` to fail (#2419)
  • Loading branch information
antazoey authored Dec 18, 2024
1 parent a3f5f6c commit be8f67e
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/ape/api/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class DependencyAPI(BaseInterfaceModel):
def package_id(self) -> str:
"""
The full name of the package, used for storage.
Example: ``OpenZeppelin/openzepplin-contracts``.
Example: ``OpenZeppelin/openzeppelin-contracts``.
"""

@property
Expand Down
22 changes: 18 additions & 4 deletions src/ape/managers/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,13 @@ def installed(self) -> bool:
if self._installation is not None:
return True

elif self.project_path.is_dir():
try:
project_path = self.project_path
except ProjectError:
# Fails when version ID errors out (bad config / missing required install etc.)
return False

if project_path.is_dir():
if any(x for x in self.project_path.iterdir() if not x.name.startswith(".")):
return True

Expand Down Expand Up @@ -1202,13 +1208,22 @@ def get_project_dependencies(
"""

for api in self.config_apis:
try:
api_version_id = api.version_id
except Exception:
api_version_id = None

if (name is not None and api.name != name and api.package_id != name) or (
version is not None and api.version_id != version
version is not None and api_version_id != version
):
continue

# Ensure the dependency API data is known.
dependency = self.add(api)
if api_version_id is not None:
dependency = self.add(api)
else:
# Errored.
dependency = Dependency(api)

if allow_install:
try:
Expand Down Expand Up @@ -1464,7 +1479,6 @@ def add(self, dependency: Union[dict, DependencyAPI]) -> Dependency:
Returns:
class:`~ape.managers.project.Dependency`
"""

api = self.decode_dependency(**dependency) if isinstance(dependency, dict) else dependency
self.packages_cache.cache_api(api)

Expand Down
2 changes: 1 addition & 1 deletion src/ape_pm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def dependencies():
yield "github", _dependencies.GithubDependency
yield "local", _dependencies.LocalDependency
yield "npm", _dependencies.NpmDependency
yield ("python", "pypi"), _dependencies.PythonDependency
yield ("python", "pypi", "site_package"), _dependencies.PythonDependency


@plugins.register(plugins.ProjectPlugin)
Expand Down
8 changes: 6 additions & 2 deletions src/ape_pm/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ def _list(cli_ctx, list_all):
"""
List installed packages
"""

dm = cli_ctx.dependency_manager
packages = []
dependencies = [*list(dm.get_project_dependencies(use_cache=True, allow_install=False))]
Expand Down Expand Up @@ -56,9 +55,14 @@ def _list(cli_ctx, list_all):
else dependency.package_id
)

try:
version = dependency.version
except ProjectError:
version = "<error>"

item = {
"name": name,
"version": dependency.version,
"version": version,
"installed": is_installed,
"compiled": is_compiled,
}
Expand Down
24 changes: 24 additions & 0 deletions tests/functional/test_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,22 @@ def test_add_dependency_with_dependencies(project, with_dependencies_project_pat
assert actual.version == "local"


def test_get_project_dependencies(project, with_dependencies_project_path):
installed_package = {"name": "web3", "site_package": "web3"}
not_installed_package = {
"name": "apethisisnotarealpackageape",
"site_package": "apethisisnotarealpackageape",
}
with project.temp_config(dependencies=[installed_package, not_installed_package]):
dm = project.dependencies
actual = list(dm.get_project_dependencies())
assert len(actual) == 2
assert actual[0].name == "web3"
assert actual[0].installed
assert actual[1].name == "apethisisnotarealpackageape"
assert not actual[1].installed


def test_install(project, mocker):
with project.isolate_in_tempdir() as tmp_project:
contracts_path = tmp_project.path / "src"
Expand Down Expand Up @@ -664,6 +680,14 @@ def test_installed(self, dependency):
dependency.install()
assert dependency.installed

def test_installed_version_id_fails(self, project):
api = PythonDependency(
site_package="apethisdependencyisnotinstalled",
name="apethisdependencyisnotinstalled",
)
dependency = Dependency(api, project)
assert not dependency.installed

def test_compile(self, project):
with create_tempdir() as path:
api = LocalDependency(local=path, name="ooga", version="1.0.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ dependencies:
config_override:
contracts_folder: sources

- name: dependency-that-is-not-installed
site_package: apedependencythatisnotinstalledape

compile:
# NOTE: this should say `include_dependencies: false` below.
# (it gets replaced with `true` in a test temporarily)
Expand Down
5 changes: 4 additions & 1 deletion tests/integration/cli/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,12 @@ def test_compile_only_dependency(ape_cli, runner, integ_project, clean_cache, ap
"--include-dependencies",
)
result = runner.invoke(ape_cli, arguments, catch_exceptions=False)
assert result.exit_code == 0, result.output
assert result.exit_code == 1, result.output # exit_code=1 because 1 dependency is bad.
assert expected_log_message in result.output

error_str = "Dependency 'apedependencythatisnotinstalledape' not installed."
assert error_str in result.output


@skip_projects_except("with-contracts")
def test_raw_compiler_output_bytecode(integ_project):
Expand Down
10 changes: 6 additions & 4 deletions tests/integration/cli/test_pm.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,9 @@ def test_list(pm_runner, integ_project):

# NOTE: Not using f-str here so we can see the spacing.
expected = """
NAME VERSION INSTALLED COMPILED
dependency-in-project-only local False False
NAME VERSION INSTALLED COMPILED
apedependencythatisnotinstalledape <error> False False
dependency-in-project-only local False False
""".strip()
assert expected in result.output

Expand All @@ -318,8 +319,9 @@ def test_list(pm_runner, integ_project):
dependency.install()

expected = """
NAME VERSION INSTALLED COMPILED
dependency-in-project-only local True False
NAME VERSION INSTALLED COMPILED
apedependencythatisnotinstalledape <error> False False
dependency-in-project-only local True False
""".strip()
result = pm_runner.invoke("list")
assert result.exit_code == 0, result.output
Expand Down

0 comments on commit be8f67e

Please sign in to comment.