Skip to content

Commit

Permalink
fix: better error handling for bad dependencies: config (ApeWorX#2132)
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Jun 13, 2024
1 parent 24e9787 commit 0519ffd
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 6 deletions.
16 changes: 14 additions & 2 deletions src/ape/api/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from collections.abc import Iterator
from collections.abc import Iterable, Iterator
from enum import Enum
from functools import cached_property
from pathlib import Path
Expand Down Expand Up @@ -197,7 +197,7 @@ class ApeConfig(ExtraAttributesMixin, BaseSettings, ManagerAccessMixin):
@classmethod
def validate_model(cls, model):
model = model or {}
fixed_model = {}
fixed_model: dict = {}
for key, val in model.items():
# Allows hyphens to work anywhere where underscores are.
fixed_model[key.replace("-", "_")] = val
Expand All @@ -207,7 +207,19 @@ def validate_model(cls, model):
# problems when moving the project around (as happens in local
# dependencies).
fixed_deps = []
dependencies_list = fixed_model.get("dependencies", [])
if not isinstance(dependencies_list, Iterable):
raise ConfigError(
"Expecting dependencies: to be iterable. "
f"Received: {type(dependencies_list).__name__}"
)

for dep in fixed_model.get("dependencies", []):
if not isinstance(dep, dict):
raise ConfigError(
f"Expecting mapping for dependency. Received: {type(dep).__name__}."
)

fixed_dep = {**dep}
if "project" not in fixed_dep:
fixed_dep["project"] = project
Expand Down
2 changes: 2 additions & 0 deletions src/ape/managers/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -2058,6 +2058,8 @@ def __getattr__(self, item: str) -> Any:
if path.stem != item:
continue

if message and message[-1] not in (".", "?", "!"):
message = f"{message}."
message = (
f"{message} However, there is a source file named '{path.name}', "
"did you mean to reference a contract name from this source file?"
Expand Down
4 changes: 2 additions & 2 deletions src/ape/pytest/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ def add_option(*names, **kwargs):
name_str = ", ".join(names)
if "already added" in str(err):
raise ConfigError(
f"Another pytest plugin besides `ape_test` uses an option with "
"Another pytest plugin besides `ape_test` uses an option with "
f"one of '{name_str}'. Note that Ape does not support being "
f"installed alongside Brownie; please use separate environments!"
"installed alongside Brownie; please use separate environments!"
)

raise ConfigError(f"Failed adding option {name_str}: {err}") from err
Expand Down
9 changes: 8 additions & 1 deletion src/ape/utils/basemodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,9 +442,16 @@ def get_attribute_with_extras(obj: Any, name: str) -> Any:

if extras_checked:
extras_str = ", ".join(sorted(extras_checked))
message = f"{message}. Also checked extra(s) '{extras_str}'."
suffix = f"Also checked extra(s) '{extras_str}'"
if suffix not in message:
if message and message[-1] not in (".", "?", "!"):
message = f"{message}."
message = f"{message} {suffix}"

_recursion_checker.reset(name)
if message and message[-1] not in (".", "?", "!"):
message = f"{message}."

attr_err = ApeAttributeError(message)
if base_err:
raise attr_err from base_err
Expand Down
18 changes: 18 additions & 0 deletions tests/functional/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,21 @@ def test_write_to_disk_txt(config):
path = base_path / "config.txt"
with pytest.raises(ConfigError, match=f"Unsupported destination file type '{path}'."):
config.write_to_disk(path)


def test_dependencies_not_list_of_dicts(project):
# NOTE: `project:` is a not a user-facing config, only
# for internal Ape.
data = {"dependencies": 123, "project": str(project.path)}
expected = "Expecting dependencies: to be iterable. Received: int"
with pytest.raises(ConfigError, match=expected):
_ = ApeConfig.model_validate(data)


def test_dependencies_list_of_non_dicts(project):
# NOTE: `project:` is a not a user-facing config, only
# for internal Ape.
data = {"dependencies": [123, 123], "project": str(project.path)}
expected = "Expecting mapping for dependency. Received: int."
with pytest.raises(ConfigError, match=expected):
_ = ApeConfig.model_validate(data)
2 changes: 1 addition & 1 deletion tests/functional/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def test_getattr_same_name_as_source_file(project_with_source_files_contract):
r"Also checked extra\(s\) 'contracts'\. "
r"However, there is a source file named 'ContractA\.sol', "
r"did you mean to reference a contract name from this source file\? "
r"Else, could it be from one of the missing compilers for extensions: ."
r"Else, could it be from one of the missing compilers for extensions: .*"
)
with pytest.raises(AttributeError, match=expected):
_ = project_with_source_files_contract.ContractA
Expand Down

0 comments on commit 0519ffd

Please sign in to comment.