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

Allow brownie run outside of a project #722

Merged
merged 3 commits into from
Aug 18, 2020
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
3 changes: 0 additions & 3 deletions brownie/_cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"])

Expand Down
36 changes: 24 additions & 12 deletions brownie/project/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ast
import importlib
import sys
import warnings
from hashlib import sha1
from pathlib import Path
Expand Down Expand Up @@ -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
root_path = Path(".").resolve().root
sys.path.insert(0, root_path)

try:
script = script.absolute().relative_to(project._path)
module = _import_from_path(script)

name = module.__name__
Expand All @@ -58,19 +60,29 @@ def run(
)
return getattr(module, method_name)(*args, **kwargs)
finally:
# cleanup namespace
project._remove_from_main_namespace()
# cleanup namespace and sys.path
sys.path.remove(root_path)
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.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():
Expand All @@ -81,12 +93,12 @@ def _get_path(path_str: str) -> Tuple[Path, 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:
Expand Down
6 changes: 3 additions & 3 deletions tests/cli/test_cli_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",)
Expand All @@ -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):
Expand Down
6 changes: 0 additions & 6 deletions tests/project/test_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import pytest

from brownie.exceptions import ProjectNotFound
from brownie.project.scripts import run


Expand Down Expand Up @@ -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")
Expand Down