Skip to content

Commit

Permalink
Merge a4b558a into master
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] authored May 27, 2021
2 parents 7c089a7 + a4b558a commit 9cd1007
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 87 deletions.
52 changes: 38 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,20 +117,36 @@ $ vien call main.py

# "create" command

`vien create` сreates a virtual environment that will correspond the current
working directory. The **working directory** in this case is assumed to be
your **project directory**. Subsequent calls to other `vien` commands in the
same directory will use the same virtual environment.
`vien create` сreates a virtual environment that will correspond the
**project directory**. Subsequent calls to `vien`
with the same project directory will use the same virtual environment.

``` bash
$ cd /path/to/myProject
$ cd /abc/myProject
$ vien create
```

By default `vien` will use the Python interpreter that running `vien` itself as
the interpreter for the virtual environment.
By default, the current **working directory** is assumed to be the
**project directory**.

Alternatively you can use `-p` parameter.

``` bash
$ vien -p /abc/myProject create
```


If you have more than one Python version, you can provide an argument to point
The `-p` parameter works with all commands, not just `create`.

``` bash
$ cd /other/working/dir
$ vien -p /abc/myProject create
$ vien -p /abc/myProject shell
```

### "create": choose the Python version

If you have more than one Python installed, you can provide an argument to point
to the proper interpreter.

``` bash
Expand All @@ -144,6 +160,12 @@ be executed in the shell as `python3.8`, you can try
$ vien create python3.8
```

When `create` is called with no argument, `vien` will use the Python
interpreter that is running `vien` itself. For example, if you used Python 3.9
to `pip install vien`, then it is the Python 3.9 runs `vien`, and this
Python 3.9 will be used in the virtual environment.


# "shell" command

`vien shell` starts interactive bash session in the virtual environment.
Expand Down Expand Up @@ -239,10 +261,10 @@ $ vien call -m /abc/myProject/pkg/sub/module.py
```

- `module.py` must be located somewhere inside the `/abc/myProject`
- parent subdirectories such as `pkg` an `sub` must be importable, i.e. must contain
- parent subdirectories such as `pkg` and `sub` must be importable, i.e. must contain
`__init__.py`
- the project directory will be inserted into `$PYTHONPATH` making the module
visible
- the project directory will be inserted into `$PYTHONPATH`, making
`pkg.sub.module` resolvable from `/abc/myProject` to a file

The project directory can be specified not only by the working directory,
but also by the `-p` parameter.
Expand Down Expand Up @@ -361,9 +383,11 @@ vien -p /abc/myProject run python3 /abc/myProject/main.py


If `--project-dir` is specified as a **relative path**, its interpretation depends
on the command. For the `call` command, this is considered a path relative to
the parent directory of the `.py` file being run. For other commands, this is
a path relative to the current working directory.
on the command.
- For the `call` command, this is a path relative to
the parent directory of the `.py` file being run
- For other commands, this is
a path relative to the current working directory

# Virtual environments location

Expand Down
58 changes: 32 additions & 26 deletions tests/test_arg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from tests.common import is_posix
from vien._main import get_project_dir
from vien._parsed_args import Parsed, Commands, items_after
from vien._parsed_args import ParsedArgs, Commands, _iter_after


def windows_too(args: List[str]) -> List[str]:
Expand All @@ -20,21 +20,21 @@ def windows_too(args: List[str]) -> List[str]:
implemented for Windows.
"""
if is_windows:
return [Parsed.PARAM_WINDOWS_ALL_ARGS] + args
return [ParsedArgs.PARAM_WINDOWS_ALL_ARGS] + args
else:
return args


class TestItemsAfter(unittest.TestCase):
def test_items_after(self):
self.assertEqual(list(items_after(['A', 'B', 'C'], 'A')),
self.assertEqual(list(_iter_after(['A', 'B', 'C'], 'A')),
['B', 'C'])
self.assertEqual(list(items_after(['A', 'B', 'C'], 'B')),
self.assertEqual(list(_iter_after(['A', 'B', 'C'], 'B')),
['C'])
self.assertEqual(list(items_after(['A', 'B', 'C'], 'C')),
self.assertEqual(list(_iter_after(['A', 'B', 'C'], 'C')),
[])
with self.assertRaises(LookupError):
list(items_after(['A', 'B', 'C'], 'X'))
list(_iter_after(['A', 'B', 'C'], 'X'))


class TestWindowsAllArgs(unittest.TestCase):
Expand All @@ -43,106 +43,112 @@ def test_windowsallargs_fails_on_posix(self):
# is the PARAM_WINDOWS_ALL_ARGS is set, we must run windows.
# Otherwise, AssertionError is thrown
with self.assertRaises(AssertionError):
Parsed([Parsed.PARAM_WINDOWS_ALL_ARGS, 'shell'])
ParsedArgs([ParsedArgs.PARAM_WINDOWS_ALL_ARGS, 'shell'])


class TestProjectDir(unittest.TestCase):

def test_run_short_left(self):
pd = Parsed(windows_too('-p a/b/c run python3 myfile.py'.split()))
pd = ParsedArgs(windows_too('-p a/b/c run python3 myfile.py'.split()))
self.assertEqual(pd.project_dir_arg, 'a/b/c')

def test_run_long_left(self):
pd = Parsed(
pd = ParsedArgs(
windows_too('--project-dir a/b/c run python3 myfile.py'.split()))
self.assertEqual(pd.project_dir_arg, 'a/b/c')

def test_call_short_right(self):
pd = Parsed('call -p a/b/c myfile.py'.split())
pd = ParsedArgs('call -p a/b/c myfile.py'.split())
self.assertEqual(pd.project_dir_arg, 'a/b/c')

def test_call_long_right(self):
pd = Parsed('call --project-dir a/b/c myfile.py'.split())
pd = ParsedArgs('call --project-dir a/b/c myfile.py'.split())
self.assertEqual(pd.project_dir_arg, 'a/b/c')

def test_call_short_left(self):
pd = Parsed('-p a/b/c call myfile.py'.split())
pd = ParsedArgs('-p a/b/c call myfile.py'.split())
self.assertEqual(pd.project_dir_arg, 'a/b/c')

def test_call_long_left(self):
pd = Parsed('--project-dir a/b/c call myfile.py'.split())
pd = ParsedArgs('--project-dir a/b/c call myfile.py'.split())
self.assertEqual(pd.project_dir_arg, 'a/b/c')

def test_call_short_both(self):
pd = Parsed('-p a/b/c call -p d/e/f myfile.py'.split())
pd = ParsedArgs('-p a/b/c call -p d/e/f myfile.py'.split())
self.assertEqual(pd.project_dir_arg, 'd/e/f')


class TestParseCall(unittest.TestCase):

def test_outdated_p(self):
pd = Parsed('-p a/b/c call -p d/e/f myfile.py arg1 arg2'.split())
pd = ParsedArgs('-p a/b/c call -p d/e/f myfile.py arg1 arg2'.split())
self.assertEqual(pd.args_to_python, ['myfile.py', 'arg1', 'arg2'])

def test_p(self):
pd = Parsed('-p a/b/c call -d myfile.py a b c'.split())
pd = ParsedArgs('-p a/b/c call -d myfile.py a b c'.split())
self.assertEqual(pd.args_to_python, ['-d', 'myfile.py', 'a', 'b', 'c'])

def test_unrecoginzed(self):
"""Unrecognized arguments that are NOT after the 'call' word."""
with self.assertRaises(SystemExit) as ce:
Parsed('-labuda call myfile.py a b c'.split())
ParsedArgs('-labuda call myfile.py a b c'.split())
self.assertEqual(ce.exception.code, 2)

def test_call_field(self):
pd = ParsedArgs('-p a/b/c call -m myfile.py arg1 arg2'.split())
self.assertIsNotNone(pd.call)
self.assertEqual(pd.call.filename, "myfile.py")
self.assertEqual(pd.call.before_filename, "-m")


class TestParseShell(unittest.TestCase):
def test_no_args(self):
pd = Parsed(windows_too('shell'.split()))
pd = ParsedArgs(windows_too('shell'.split()))
self.assertEqual(pd.command, Commands.shell)
self.assertEqual(pd.shell_delay, None)
self.assertEqual(pd.shell_input, None)

def test_input(self):
pd = Parsed(windows_too(['shell', '--input', 'cd / && ls']))
pd = ParsedArgs(windows_too(['shell', '--input', 'cd / && ls']))
self.assertEqual(pd.shell_input, 'cd / && ls')

def test_delay(self):
pd = Parsed(windows_too('shell --delay 1.2'.split()))
pd = ParsedArgs(windows_too('shell --delay 1.2'.split()))
self.assertEqual(pd.shell_delay, 1.2)

def test_labuda(self):
with self.assertRaises(SystemExit) as ce:
pd = Parsed(windows_too('shell --labuda'.split()))
pd = ParsedArgs(windows_too('shell --labuda'.split()))
self.assertEqual(ce.exception.code, 2)


class TestParseRun(unittest.TestCase):
def test(self):
pd = Parsed(windows_too(['run', 'python3', '-OO', 'file.py']))
pd = ParsedArgs(windows_too(['run', 'python3', '-OO', 'file.py']))
self.assertEqual(pd.command, Commands.run)
self.assertEqual(pd.run_args, ['python3', '-OO', 'file.py'])


class TestParseCreate(unittest.TestCase):
def test_with_arg(self):
pd = Parsed(['create', 'python3'])
pd = ParsedArgs(['create', 'python3'])
self.assertEqual(pd.command, Commands.create)
self.assertEqual(pd.python_executable, "python3")

def test_without_arg(self):
pd = Parsed(['create'])
pd = ParsedArgs(['create'])
self.assertEqual(pd.command, Commands.create)
self.assertEqual(pd.python_executable, None)


class TestParseRecreate(unittest.TestCase):
def test_with_arg(self):
pd = Parsed(['recreate', 'python3'])
pd = ParsedArgs(['recreate', 'python3'])
self.assertEqual(pd.command, Commands.recreate)
self.assertEqual(pd.python_executable, "python3")

def test_without_arg(self):
pd = Parsed(['recreate'])
pd = ParsedArgs(['recreate'])
self.assertEqual(pd.command, Commands.recreate)
self.assertEqual(pd.python_executable, None)

Expand Down
4 changes: 2 additions & 2 deletions tests/test_call_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ class TestNew(unittest.TestCase):

def test_file_after_call(self):
psr = ParsedCall("vien -p zzz call -d FiLe.Py arg1".split())
self.assertEqual(psr.file, "FiLe.Py")
self.assertEqual(psr.filename, "FiLe.Py")

def test_file_before_and_after_call(self):
psr = ParsedCall("vien -p wrong.py call -d right.py arg1".split())
self.assertEqual(psr.file, "right.py")
self.assertEqual(psr.filename, "right.py")

def test_file_not_found(self):
with self.assertRaises(PyFileArgNotFoundExit):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_get_project_dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from tests.test_arg_parser import windows_too
from vien._main import get_project_dir
from vien._exceptions import PyFileArgNotFoundExit
from vien._parsed_args import Parsed
from vien._parsed_args import ParsedArgs


def fix_paths(s: str):
Expand All @@ -25,7 +25,7 @@ def setUp(self) -> None:

def _gpd(self, cmd: str) -> Path:
cmd = fix_paths(cmd)
result = get_project_dir(Parsed(windows_too(cmd.split())))
result = get_project_dir(ParsedArgs(windows_too(cmd.split())))
self.assertTrue(result.is_absolute())
return result

Expand Down
2 changes: 1 addition & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from typing import List, Optional

from tests.test_arg_parser import windows_too
from vien._parsed_args import Parsed
from vien._parsed_args import ParsedArgs

from vien._common import is_windows

Expand Down
2 changes: 1 addition & 1 deletion vien/_cmdexe_escape_args.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Code taken from https://stackoverflow.com/a/29215357
# taken from https://stackoverflow.com/a/29215357
# SPDX-FileCopyrightText: Holger Just
# SPDX-License-Identifier: CC BY-SA 3.0

Expand Down
3 changes: 2 additions & 1 deletion vien/_constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
__version__ = "8.0.2"
__version__ = "8.0.3"
__copyright__ = "(c) 2020-2021 Artëm IG <github.com/rtmigo>"
__license__ = "BSD-3-Clause"

32 changes: 17 additions & 15 deletions vien/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from vien import is_posix
from vien._common import need_posix, is_windows, need_windows
from vien._parsed_args import Commands, Parsed
from vien._parsed_args import Commands, ParsedArgs
from vien._bash_runner import run_as_bash_script
from vien._call_funcs import relative_fn_to_module_name, relative_inner_path
from vien._parsed_call import ParsedCall, list_left_partition
Expand Down Expand Up @@ -388,25 +388,27 @@ def replace_arg(args: List[str], old: str, new: List[str]) -> List[str]:
return result


def main_call(parsed: Parsed, dirs: Dirs):
def main_call(parsed: ParsedArgs, dirs: Dirs):
dirs.venv_must_exist()

parsed_call = ParsedCall(parsed.args)
assert parsed_call.file is not None
#parsed_call = ParsedCall(parsed.args)
#assert parsed_call.file is not None

if not os.path.exists(parsed_call.file):
raise PyFileNotFoundExit(Path(parsed_call.file))
assert parsed.call is not None

if parsed_call.before_filename == "-m":
if not os.path.exists(parsed.call.filename):
raise PyFileNotFoundExit(Path(parsed.call.filename))

if parsed.call.before_filename == "-m":
# todo unit test
# /abc/project/package/module.py -> package/module.py
relative = relative_inner_path(parsed_call.file, dirs.project_dir)
relative = relative_inner_path(parsed.call.filename, dirs.project_dir)
# package/module.py -> package.module
module_name = relative_fn_to_module_name(relative)
# replacing the filename in args with the module name.
# It is already prefixed with -m
args = parsed_call.args.copy()
args[parsed_call.file_idx] = module_name
args = parsed.call.args.copy()
args[parsed.call.filename_idx] = module_name
# args to python are those after 'call' word
_, args_to_python = list_left_partition(args, 'call')
assert '-m' in args_to_python
Expand All @@ -431,14 +433,14 @@ def normalize_path(reference: Path, path: Path) -> Path:
return Path(os.path.normpath(reference / path))


def get_project_dir(parsed: Parsed) -> Path:
def get_project_dir(parsed: ParsedArgs) -> Path:
if parsed.project_dir_arg is not None:
if parsed.command == Commands.call:
# for the 'call' the reference dir is the parent or .py file
pyfile = ParsedCall(parsed.args).file
if pyfile is None:
assert parsed.call is not None
if parsed.call.filename is None:
raise PyFileArgNotFoundExit
reference_dir = Path(pyfile).parent.absolute()
reference_dir = Path(parsed.call.filename).parent.absolute()
else:
# for other commands the reference dir is cwd
reference_dir = Path(".").absolute()
Expand All @@ -453,7 +455,7 @@ def get_project_dir(parsed: Parsed) -> Path:


def main_entry_point(args: Optional[List[str]] = None):
parsed = Parsed(args)
parsed = ParsedArgs(args)

dirs = Dirs(project_dir=get_project_dir(parsed))

Expand Down
Loading

0 comments on commit 9cd1007

Please sign in to comment.