Skip to content

Commit 9cd1007

Browse files
Merge a4b558a into master
2 parents 7c089a7 + a4b558a commit 9cd1007

10 files changed

+128
-87
lines changed

README.md

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,20 +117,36 @@ $ vien call main.py
117117

118118
# "create" command
119119

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

125124
``` bash
126-
$ cd /path/to/myProject
125+
$ cd /abc/myProject
127126
$ vien create
128127
```
129128

130-
By default `vien` will use the Python interpreter that running `vien` itself as
131-
the interpreter for the virtual environment.
129+
By default, the current **working directory** is assumed to be the
130+
**project directory**.
131+
132+
Alternatively you can use `-p` parameter.
133+
134+
``` bash
135+
$ vien -p /abc/myProject create
136+
```
137+
132138

133-
If you have more than one Python version, you can provide an argument to point
139+
The `-p` parameter works with all commands, not just `create`.
140+
141+
``` bash
142+
$ cd /other/working/dir
143+
$ vien -p /abc/myProject create
144+
$ vien -p /abc/myProject shell
145+
```
146+
147+
### "create": choose the Python version
148+
149+
If you have more than one Python installed, you can provide an argument to point
134150
to the proper interpreter.
135151

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

163+
When `create` is called with no argument, `vien` will use the Python
164+
interpreter that is running `vien` itself. For example, if you used Python 3.9
165+
to `pip install vien`, then it is the Python 3.9 runs `vien`, and this
166+
Python 3.9 will be used in the virtual environment.
167+
168+
147169
# "shell" command
148170

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

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

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

362384

363385
If `--project-dir` is specified as a **relative path**, its interpretation depends
364-
on the command. For the `call` command, this is considered a path relative to
365-
the parent directory of the `.py` file being run. For other commands, this is
366-
a path relative to the current working directory.
386+
on the command.
387+
- For the `call` command, this is a path relative to
388+
the parent directory of the `.py` file being run
389+
- For other commands, this is
390+
a path relative to the current working directory
367391

368392
# Virtual environments location
369393

tests/test_arg_parser.py

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from tests.common import is_posix
1111
from vien._main import get_project_dir
12-
from vien._parsed_args import Parsed, Commands, items_after
12+
from vien._parsed_args import ParsedArgs, Commands, _iter_after
1313

1414

1515
def windows_too(args: List[str]) -> List[str]:
@@ -20,21 +20,21 @@ def windows_too(args: List[str]) -> List[str]:
2020
implemented for Windows.
2121
"""
2222
if is_windows:
23-
return [Parsed.PARAM_WINDOWS_ALL_ARGS] + args
23+
return [ParsedArgs.PARAM_WINDOWS_ALL_ARGS] + args
2424
else:
2525
return args
2626

2727

2828
class TestItemsAfter(unittest.TestCase):
2929
def test_items_after(self):
30-
self.assertEqual(list(items_after(['A', 'B', 'C'], 'A')),
30+
self.assertEqual(list(_iter_after(['A', 'B', 'C'], 'A')),
3131
['B', 'C'])
32-
self.assertEqual(list(items_after(['A', 'B', 'C'], 'B')),
32+
self.assertEqual(list(_iter_after(['A', 'B', 'C'], 'B')),
3333
['C'])
34-
self.assertEqual(list(items_after(['A', 'B', 'C'], 'C')),
34+
self.assertEqual(list(_iter_after(['A', 'B', 'C'], 'C')),
3535
[])
3636
with self.assertRaises(LookupError):
37-
list(items_after(['A', 'B', 'C'], 'X'))
37+
list(_iter_after(['A', 'B', 'C'], 'X'))
3838

3939

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

4848

4949
class TestProjectDir(unittest.TestCase):
5050

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

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

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

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

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

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

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

8080

8181
class TestParseCall(unittest.TestCase):
8282

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

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

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

97+
def test_call_field(self):
98+
pd = ParsedArgs('-p a/b/c call -m myfile.py arg1 arg2'.split())
99+
self.assertIsNotNone(pd.call)
100+
self.assertEqual(pd.call.filename, "myfile.py")
101+
self.assertEqual(pd.call.before_filename, "-m")
102+
97103

98104
class TestParseShell(unittest.TestCase):
99105
def test_no_args(self):
100-
pd = Parsed(windows_too('shell'.split()))
106+
pd = ParsedArgs(windows_too('shell'.split()))
101107
self.assertEqual(pd.command, Commands.shell)
102108
self.assertEqual(pd.shell_delay, None)
103109
self.assertEqual(pd.shell_input, None)
104110

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

109115
def test_delay(self):
110-
pd = Parsed(windows_too('shell --delay 1.2'.split()))
116+
pd = ParsedArgs(windows_too('shell --delay 1.2'.split()))
111117
self.assertEqual(pd.shell_delay, 1.2)
112118

113119
def test_labuda(self):
114120
with self.assertRaises(SystemExit) as ce:
115-
pd = Parsed(windows_too('shell --labuda'.split()))
121+
pd = ParsedArgs(windows_too('shell --labuda'.split()))
116122
self.assertEqual(ce.exception.code, 2)
117123

118124

119125
class TestParseRun(unittest.TestCase):
120126
def test(self):
121-
pd = Parsed(windows_too(['run', 'python3', '-OO', 'file.py']))
127+
pd = ParsedArgs(windows_too(['run', 'python3', '-OO', 'file.py']))
122128
self.assertEqual(pd.command, Commands.run)
123129
self.assertEqual(pd.run_args, ['python3', '-OO', 'file.py'])
124130

125131

126132
class TestParseCreate(unittest.TestCase):
127133
def test_with_arg(self):
128-
pd = Parsed(['create', 'python3'])
134+
pd = ParsedArgs(['create', 'python3'])
129135
self.assertEqual(pd.command, Commands.create)
130136
self.assertEqual(pd.python_executable, "python3")
131137

132138
def test_without_arg(self):
133-
pd = Parsed(['create'])
139+
pd = ParsedArgs(['create'])
134140
self.assertEqual(pd.command, Commands.create)
135141
self.assertEqual(pd.python_executable, None)
136142

137143

138144
class TestParseRecreate(unittest.TestCase):
139145
def test_with_arg(self):
140-
pd = Parsed(['recreate', 'python3'])
146+
pd = ParsedArgs(['recreate', 'python3'])
141147
self.assertEqual(pd.command, Commands.recreate)
142148
self.assertEqual(pd.python_executable, "python3")
143149

144150
def test_without_arg(self):
145-
pd = Parsed(['recreate'])
151+
pd = ParsedArgs(['recreate'])
146152
self.assertEqual(pd.command, Commands.recreate)
147153
self.assertEqual(pd.python_executable, None)
148154

tests/test_call_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ class TestNew(unittest.TestCase):
1212

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

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

2121
def test_file_not_found(self):
2222
with self.assertRaises(PyFileArgNotFoundExit):

tests/test_get_project_dir.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from tests.test_arg_parser import windows_too
1010
from vien._main import get_project_dir
1111
from vien._exceptions import PyFileArgNotFoundExit
12-
from vien._parsed_args import Parsed
12+
from vien._parsed_args import ParsedArgs
1313

1414

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

2626
def _gpd(self, cmd: str) -> Path:
2727
cmd = fix_paths(cmd)
28-
result = get_project_dir(Parsed(windows_too(cmd.split())))
28+
result = get_project_dir(ParsedArgs(windows_too(cmd.split())))
2929
self.assertTrue(result.is_absolute())
3030
return result
3131

tests/test_main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from typing import List, Optional
1717

1818
from tests.test_arg_parser import windows_too
19-
from vien._parsed_args import Parsed
19+
from vien._parsed_args import ParsedArgs
2020

2121
from vien._common import is_windows
2222

vien/_cmdexe_escape_args.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Code taken from https://stackoverflow.com/a/29215357
1+
# taken from https://stackoverflow.com/a/29215357
22
# SPDX-FileCopyrightText: Holger Just
33
# SPDX-License-Identifier: CC BY-SA 3.0
44

vien/_constants.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
__version__ = "8.0.2"
1+
__version__ = "8.0.3"
22
__copyright__ = "(c) 2020-2021 Artëm IG <github.com/rtmigo>"
33
__license__ = "BSD-3-Clause"
4+

vien/_main.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from vien import is_posix
1616
from vien._common import need_posix, is_windows, need_windows
17-
from vien._parsed_args import Commands, Parsed
17+
from vien._parsed_args import Commands, ParsedArgs
1818
from vien._bash_runner import run_as_bash_script
1919
from vien._call_funcs import relative_fn_to_module_name, relative_inner_path
2020
from vien._parsed_call import ParsedCall, list_left_partition
@@ -388,25 +388,27 @@ def replace_arg(args: List[str], old: str, new: List[str]) -> List[str]:
388388
return result
389389

390390

391-
def main_call(parsed: Parsed, dirs: Dirs):
391+
def main_call(parsed: ParsedArgs, dirs: Dirs):
392392
dirs.venv_must_exist()
393393

394-
parsed_call = ParsedCall(parsed.args)
395-
assert parsed_call.file is not None
394+
#parsed_call = ParsedCall(parsed.args)
395+
#assert parsed_call.file is not None
396396

397-
if not os.path.exists(parsed_call.file):
398-
raise PyFileNotFoundExit(Path(parsed_call.file))
397+
assert parsed.call is not None
399398

400-
if parsed_call.before_filename == "-m":
399+
if not os.path.exists(parsed.call.filename):
400+
raise PyFileNotFoundExit(Path(parsed.call.filename))
401+
402+
if parsed.call.before_filename == "-m":
401403
# todo unit test
402404
# /abc/project/package/module.py -> package/module.py
403-
relative = relative_inner_path(parsed_call.file, dirs.project_dir)
405+
relative = relative_inner_path(parsed.call.filename, dirs.project_dir)
404406
# package/module.py -> package.module
405407
module_name = relative_fn_to_module_name(relative)
406408
# replacing the filename in args with the module name.
407409
# It is already prefixed with -m
408-
args = parsed_call.args.copy()
409-
args[parsed_call.file_idx] = module_name
410+
args = parsed.call.args.copy()
411+
args[parsed.call.filename_idx] = module_name
410412
# args to python are those after 'call' word
411413
_, args_to_python = list_left_partition(args, 'call')
412414
assert '-m' in args_to_python
@@ -431,14 +433,14 @@ def normalize_path(reference: Path, path: Path) -> Path:
431433
return Path(os.path.normpath(reference / path))
432434

433435

434-
def get_project_dir(parsed: Parsed) -> Path:
436+
def get_project_dir(parsed: ParsedArgs) -> Path:
435437
if parsed.project_dir_arg is not None:
436438
if parsed.command == Commands.call:
437439
# for the 'call' the reference dir is the parent or .py file
438-
pyfile = ParsedCall(parsed.args).file
439-
if pyfile is None:
440+
assert parsed.call is not None
441+
if parsed.call.filename is None:
440442
raise PyFileArgNotFoundExit
441-
reference_dir = Path(pyfile).parent.absolute()
443+
reference_dir = Path(parsed.call.filename).parent.absolute()
442444
else:
443445
# for other commands the reference dir is cwd
444446
reference_dir = Path(".").absolute()
@@ -453,7 +455,7 @@ def get_project_dir(parsed: Parsed) -> Path:
453455

454456

455457
def main_entry_point(args: Optional[List[str]] = None):
456-
parsed = Parsed(args)
458+
parsed = ParsedArgs(args)
457459

458460
dirs = Dirs(project_dir=get_project_dir(parsed))
459461

0 commit comments

Comments
 (0)