From ff0fc399ecb0b3325bd175cccb9652febe03b946 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Tue, 18 Aug 2020 03:41:12 +0300 Subject: [PATCH 1/3] feat: allow `brownie run` outside of projects --- brownie/_cli/run.py | 3 --- brownie/project/scripts.py | 29 +++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/brownie/_cli/run.py b/brownie/_cli/run.py index e9fb34234..4070a8a72 100644 --- a/brownie/_cli/run.py +++ b/brownie/_cli/run.py @@ -7,7 +7,6 @@ from brownie import network, project from brownie._cli.console import Console from brownie._config import CONFIG, _update_argv_from_docopt -from brownie.exceptions import ProjectNotFound from brownie.project.scripts import _get_path, run from brownie.test.output import _build_gas_profile_output from brownie.utils import color @@ -39,8 +38,6 @@ def main(): active_project = project.load() active_project.load_config() print(f"{active_project._name} is the active project.") - else: - raise ProjectNotFound network.connect(CONFIG.argv["network"]) diff --git a/brownie/project/scripts.py b/brownie/project/scripts.py index 66e26d119..5781e849e 100644 --- a/brownie/project/scripts.py +++ b/brownie/project/scripts.py @@ -2,6 +2,7 @@ import ast import importlib +import sys import warnings from hashlib import sha1 from pathlib import Path @@ -37,16 +38,17 @@ def run( if kwargs is None: kwargs = {} - if not get_loaded_projects(): - raise ProjectNotFound("Cannot run a script without an active project") - script, project = _get_path(script_path) # temporarily add project objects to the main namespace, so the script can import them - project._add_to_main_namespace() + if project is not None: + project._add_to_main_namespace() + + # modify sys.path to ensure script can be imported + sys.path.insert(0, script.absolute().parent.as_posix()) + script = Path(script.stem) try: - script = script.absolute().relative_to(project._path) module = _import_from_path(script) name = module.__name__ @@ -58,14 +60,25 @@ def run( ) return getattr(module, method_name)(*args, **kwargs) finally: - # cleanup namespace - project._remove_from_main_namespace() + # cleanup namespace and sys.path + del sys.path[0] + if project is not None: + project._remove_from_main_namespace() -def _get_path(path_str: str) -> Tuple[Path, Project]: +def _get_path(path_str: str) -> Tuple[Path, Optional[Project]]: # Returns path to a python module path = Path(path_str).with_suffix(".py") + if not get_loaded_projects(): + if not path.exists(): + raise FileNotFoundError(f"Cannot find {path_str}") + return path, None + + if path.parts[:1] == ("scripts",): + # for scripts in projects, + path = path.relative_to("scripts") + if not path.is_absolute(): for project in get_loaded_projects(): script_path = project._path.joinpath(project._structure["scripts"]).joinpath(path) From 80ce431626dcdb815d2f09ad826677a21dd44616 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Tue, 18 Aug 2020 20:44:26 +0300 Subject: [PATCH 2/3] fix: use absolute paths for run --- brownie/project/scripts.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/brownie/project/scripts.py b/brownie/project/scripts.py index 5781e849e..858fe3774 100644 --- a/brownie/project/scripts.py +++ b/brownie/project/scripts.py @@ -45,8 +45,8 @@ def run( project._add_to_main_namespace() # modify sys.path to ensure script can be imported - sys.path.insert(0, script.absolute().parent.as_posix()) - script = Path(script.stem) + root_path = Path(".").resolve().root + sys.path.insert(0, root_path) try: module = _import_from_path(script) @@ -61,7 +61,7 @@ def run( return getattr(module, method_name)(*args, **kwargs) finally: # cleanup namespace and sys.path - del sys.path[0] + sys.path.remove(root_path) if project is not None: project._remove_from_main_namespace() @@ -73,17 +73,16 @@ def _get_path(path_str: str) -> Tuple[Path, Optional[Project]]: if not get_loaded_projects(): if not path.exists(): raise FileNotFoundError(f"Cannot find {path_str}") - return path, None - - if path.parts[:1] == ("scripts",): - # for scripts in projects, - path = path.relative_to("scripts") + return path.resolve(), None if not path.is_absolute(): for project in get_loaded_projects(): - script_path = project._path.joinpath(project._structure["scripts"]).joinpath(path) + if path.parts[:1] == (project._structure["scripts"],): + script_path = project._path.joinpath(path) + else: + script_path = project._path.joinpath(project._structure["scripts"]).joinpath(path) if script_path.exists(): - return script_path, project + return script_path.resolve(), project raise FileNotFoundError(f"Cannot find {path_str}") if not path.exists(): @@ -94,12 +93,12 @@ def _get_path(path_str: str) -> Tuple[Path, Optional[Project]]: except StopIteration: raise ProjectNotFound(f"{path_str} is not part of an active project") - return path, project + return path.resolve(), project def _import_from_path(path: Path) -> ModuleType: # Imports a module from the given path - import_str = ".".join(path.parts[:-1] + (path.stem,)) + import_str = ".".join(path.parts[1:-1] + (path.stem,)) if import_str in _import_cache: importlib.reload(_import_cache[import_str]) else: From 53be3e7a8785c7a855c2a48ba0a42528e874423b Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Tue, 18 Aug 2020 20:44:55 +0300 Subject: [PATCH 3/3] test: remove obsoleted test case --- tests/cli/test_cli_main.py | 6 +++--- tests/project/test_scripts.py | 6 ------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/cli/test_cli_main.py b/tests/cli/test_cli_main.py index 7a75fe9c1..e7113409c 100644 --- a/tests/cli/test_cli_main.py +++ b/tests/cli/test_cli_main.py @@ -94,7 +94,7 @@ def test_cli_compile_and_analyze_projectnotfound_exception(cli_tester): assert cli_tester.mock_subroutines.call_count == 0 -def test_cli_run_with_projectnotfound_exception(cli_tester): +def test_cli_run_with_missing_file(cli_tester): cli_tester.monkeypatch.setattr("brownie.run", cli_tester.mock_subroutines) subtargets = ("brownie.network.connect",) @@ -104,8 +104,8 @@ def test_cli_run_with_projectnotfound_exception(cli_tester): with pytest.raises(SystemExit): cli_tester.run_and_test_parameters("run testfile", parameters=None) - assert cli_tester.mock_subroutines.called is False - assert cli_tester.mock_subroutines.call_count == 0 + assert cli_tester.mock_subroutines.called is True + assert cli_tester.mock_subroutines.call_count == 1 def test_cli_ethpm(cli_tester, testproject): diff --git a/tests/project/test_scripts.py b/tests/project/test_scripts.py index ebff04c33..1b92f56b4 100644 --- a/tests/project/test_scripts.py +++ b/tests/project/test_scripts.py @@ -2,7 +2,6 @@ import pytest -from brownie.exceptions import ProjectNotFound from brownie.project.scripts import run @@ -43,11 +42,6 @@ def test_multiple_projects(testproject, otherproject): run("other") -def test_no_project(): - with pytest.raises(ProjectNotFound): - run("foo") - - def test_no_script(testproject): with pytest.raises(FileNotFoundError): run("scripts/foo")