venvflon 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
venvflon-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Michal Plichta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.1
2
+ Name: venvflon
3
+ Version: 0.1.0
4
+ Summary: simple virtual environment switcher
5
+ Author: Michal Plichta
6
+ Maintainer: Michal Plichta
7
+ License: MIT License
8
+ Project-URL: Homepage, https://github.com/emcek/venvflon
9
+ Project-URL: Source, https://github.com/emcek/venvflon.git
10
+ Project-URL: Changelog, https://github.com/emcek/venvflon/blob/master/CHANGELOG.md
11
+ Keywords: venv,uv,virtualenv
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Environment :: Win32 (MS Windows)
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Operating System :: Microsoft :: Windows
22
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 10
23
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 11
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Provides-Extra: test
29
+ Requires-Dist: GitPython==3.1.43; extra == "test"
30
+ Requires-Dist: interrogate==1.7.0; extra == "test"
31
+ Requires-Dist: isort==5.13.2; extra == "test"
32
+ Requires-Dist: lxml==5.3.0; extra == "test"
33
+ Requires-Dist: mypy==1.13.0; extra == "test"
34
+ Requires-Dist: packaging==24.2; extra == "test"
35
+ Requires-Dist: pip-audit==2.7.3; extra == "test"
36
+ Requires-Dist: pytest==8.3.3; extra == "test"
37
+ Requires-Dist: pytest-randomly==3.16.0; extra == "test"
38
+ Requires-Dist: ruff==0.7.4; extra == "test"
39
+ Requires-Dist: types-pyinstaller==6.10.0.20240812; extra == "test"
40
+
41
+ # venvflon
42
+ simple virtual environment switcher
43
+
44
+ ## name
45
+ Comes from polish `wenflon` or `kaniula` (eng. `cannula`)
@@ -0,0 +1,5 @@
1
+ # venvflon
2
+ simple virtual environment switcher
3
+
4
+ ## name
5
+ Comes from polish `wenflon` or `kaniula` (eng. `cannula`)
@@ -0,0 +1,127 @@
1
+ [build-system]
2
+ requires = ['setuptools']
3
+ build-backend = 'setuptools.build_meta'
4
+
5
+ [project]
6
+ name = 'venvflon'
7
+ authors = [{name = 'Michal Plichta'}]
8
+ maintainers = [{name = 'Michal Plichta'}]
9
+ description = 'simple virtual environment switcher'
10
+ license = {text = 'MIT License'}
11
+ requires-python = '>=3.9'
12
+ readme = 'README.md'
13
+ classifiers = [
14
+ 'Development Status :: 4 - Beta',
15
+ 'Environment :: Console',
16
+ 'Environment :: Win32 (MS Windows)',
17
+ 'License :: OSI Approved :: MIT License',
18
+ 'Programming Language :: Python :: 3.9',
19
+ 'Programming Language :: Python :: 3.10',
20
+ 'Programming Language :: Python :: 3.11',
21
+ 'Programming Language :: Python :: 3.12',
22
+ 'Programming Language :: Python :: 3.13',
23
+ 'Operating System :: Microsoft :: Windows',
24
+ 'Operating System :: Microsoft :: Windows :: Windows 10',
25
+ 'Operating System :: Microsoft :: Windows :: Windows 11',
26
+ 'Topic :: Utilities',
27
+ ]
28
+ keywords = [
29
+ 'venv', 'uv', 'virtualenv',
30
+ ]
31
+ dynamic = ['version']
32
+
33
+ [project.urls]
34
+ Homepage = 'https://github.com/emcek/venvflon'
35
+ Source = 'https://github.com/emcek/venvflon.git'
36
+ Changelog = 'https://github.com/emcek/venvflon/blob/master/CHANGELOG.md'
37
+
38
+ [project.scripts]
39
+ flon_cli = 'venvflon.run:run'
40
+
41
+ [project.gui-scripts]
42
+ flon = 'venvflon.run:run'
43
+
44
+ [project.optional-dependencies]
45
+ test = [
46
+ 'GitPython==3.1.43',
47
+ 'interrogate==1.7.0',
48
+ 'isort==5.13.2',
49
+ 'lxml==5.3.0',
50
+ 'mypy==1.13.0',
51
+ 'packaging==24.2',
52
+ 'pip-audit==2.7.3',
53
+ 'pytest==8.3.3',
54
+ 'pytest-randomly==3.16.0',
55
+ 'ruff==0.7.4',
56
+ 'types-pyinstaller==6.10.0.20240812',
57
+ ]
58
+
59
+ [tool.setuptools.dynamic]
60
+ version = {attr = 'venvflon.run.__version__'}
61
+
62
+ [tool.setuptools.packages.find]
63
+ where = ['src/']
64
+ include = ['venvflon*']
65
+ exclude = ['venvflon.tests*']
66
+
67
+ [tool.setuptools.package-data]
68
+ venvflon = [
69
+ 'img/*',
70
+ 'py.typed',
71
+ ]
72
+
73
+ [tool.pytest.ini_options]
74
+ addopts = ['-q']
75
+ pythonpath = ['src']
76
+ testpaths = ['tests']
77
+ markers = [
78
+ 'slow: marks tests as slow',
79
+ ]
80
+
81
+ [tool.interrogate]
82
+ ignore-init-method = false
83
+ ignore-init-module = true
84
+ ignore-magic = true
85
+ ignore-semiprivate = false
86
+ ignore-private = false
87
+ ignore-property-decorators = false
88
+ ignore-module = true
89
+ ignore-nested-functions = false
90
+ ignore-nested-classes = true
91
+ ignore-setters = false
92
+ fail-under = 90
93
+ verbose = 1
94
+ quiet = false
95
+ exclude = ['tests/test_*']
96
+
97
+ [tool.ruff]
98
+ line-length = 160
99
+ lint.select = ['E', 'F', 'C90', 'D', 'RUF']
100
+ lint.ignore = ['D100', 'D101','D104', 'D105', 'D203', 'D204', 'D212', 'RUF001', 'RUF010']
101
+ extend-exclude = [
102
+ 'tests/',
103
+ ]
104
+
105
+ [tool.ruff.lint.mccabe]
106
+ max-complexity = 5
107
+
108
+ [tool.mypy]
109
+ #ignore_missing_imports = true
110
+ #no_warn_no_return = true
111
+ #check_untyped_defs = true
112
+ html_report = 'mypyhtml'
113
+ exclude = [
114
+ 'tests/',
115
+ 'generate_ver_file\.py$',
116
+ ]
117
+
118
+ [tool.isort]
119
+ line_length = 160
120
+
121
+ [tool.uv]
122
+ dev-dependencies = [
123
+ 'venvflon[test]',
124
+ 'build',
125
+ 'detect-test-pollution',
126
+ 'pre-commit',
127
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ from os import environ, getcwd
4
+ from pathlib import Path
5
+ from sys import base_prefix, executable
6
+ from time import sleep
7
+
8
+ from venvflon.utils import get_command_output, make_sym_link, rm_sym_link, venv_list_in
9
+
10
+ environ['TCL_LIBRARY'] = str(Path(base_prefix) / 'tcl' / 'tcl8.6')
11
+ environ['TK_LIBRARY'] = str(Path(base_prefix) / 'tcl' / 'tk8.6')
12
+ import tkinter as tk
13
+
14
+ print(executable)
15
+
16
+ class Gui(tk.Frame):
17
+ """Tkinter GUI for venvflon."""
18
+
19
+ def __init__(self, master: tk.Tk) -> None:
20
+ """
21
+ Tkinter GUI for venvflon.
22
+
23
+ :param master: Tkinter root
24
+ """
25
+ super().__init__(master)
26
+ self.master = master
27
+ self.venv = tk.StringVar(value=' ')
28
+ self.status_txt = tk.StringVar()
29
+ self.cwd_entry = tk.StringVar()
30
+ self.cwd = Path(getcwd())
31
+ self.cwd_entry.set(str(self.cwd))
32
+ self.venv_list = venv_list_in(current_path=self.cwd)
33
+ new_width, new_height = len(str(self.venv_list[0])) + 300, len(self.venv_list) * 55
34
+ self.master.geometry(f'{new_width}x{new_height}')
35
+ self.master.minsize(width=new_width, height=new_height)
36
+ self.init_widgets()
37
+ self.update_status()
38
+
39
+ def init_widgets(self) -> None:
40
+ """Initialize widgets."""
41
+ self.master.columnconfigure(index=0, weight=1)
42
+ cwd_label = tk.Label(self.master, text='cwd:')
43
+ cwd_label.grid(row=0, column=0, sticky=tk.W)
44
+ cwd = tk.Entry(master=self.master, textvariable=self.cwd_entry, width=len(str(self.venv_list[0])) + 2)
45
+ cwd.grid(row=0, column=1, sticky=tk.W)
46
+ cwd.bind('<Return>', self.refresh_cwd)
47
+ self.add_venvs()
48
+
49
+ def add_venvs(self):
50
+ """Add venvs as radio buttons to the GUI."""
51
+ venv_label = tk.Label(self.master, text='venv:')
52
+ venv_label.grid(row=1, column=0, sticky=tk.W)
53
+ frame = tk.Frame(master=self.master, relief=tk.GROOVE, borderwidth=2)
54
+ frame.grid(row=1, column=1, columnspan=2, padx=2, pady=2, rowspan=len(self.venv_list))
55
+ for i, text in enumerate(self.venv_list, 1):
56
+ rb_venvs = tk.Radiobutton(master=frame, text=str(text), variable=self.venv, value=text, command=self.venv_selected)
57
+ rb_venvs.grid(row=i, column=1, pady=0, padx=2, sticky=tk.W)
58
+ status = tk.Label(master=self.master, textvariable=self.status_txt)
59
+ status.grid(row=len(self.venv_list) + 1, column=0, columnspan=3, sticky=tk.E)
60
+
61
+ def refresh_cwd(self, *args):
62
+ """
63
+ Refresh the current working directory.
64
+
65
+ :param args: internal tkinter arguments
66
+ """
67
+ self.venv_list = venv_list_in(current_path=self.cwd)
68
+ self.add_venvs()
69
+
70
+ def venv_selected(self):
71
+ """Set the selected venv as the active one."""
72
+ new_venv = self.venv.get()
73
+ rm_sym_link(sym_link=Path(getcwd()) / '.venv')
74
+ make_sym_link(to_path=Path(getcwd()) / '.venv', target=Path(new_venv))
75
+ sleep(0.8)
76
+ self.update_status()
77
+
78
+ def update_status(self):
79
+ """Update the status text."""
80
+ out = get_command_output(cmd=['python', '-V'])
81
+ self.status_txt.set(f'Current: {out[2].strip()}')
File without changes
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from os import environ
4
+ from pathlib import Path
5
+ from sys import base_prefix
6
+
7
+ from venvflon.flon import Gui
8
+
9
+ environ['TCL_LIBRARY'] = str(Path(base_prefix) / 'tcl' / 'tcl8.6')
10
+ environ['TK_LIBRARY'] = str(Path(base_prefix) / 'tcl' / 'tk8.6')
11
+ import tkinter as tk
12
+
13
+ __version__ = '0.1.0'
14
+
15
+ def run():
16
+ """Run the main GUI."""
17
+ root_tk = tk.Tk()
18
+ width, height = 300, 150
19
+ root_tk.title(f'venvflon - v{__version__}')
20
+ root_tk.geometry(f'{width}x{height}')
21
+ root_tk.iconphoto(False, tk.PhotoImage(file=Path(__file__).parent / 'img' / 'cannula_64.png'))
22
+ gui = Gui(master=root_tk)
23
+ gui.mainloop()
24
+
25
+
26
+ if __name__ == '__main__':
27
+ run()
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from os import sep, walk
5
+ from pathlib import Path
6
+ from subprocess import CalledProcessError, run
7
+
8
+
9
+ def run_command(cmd: Sequence[str], cwd: Path | None = None) -> int:
10
+ """
11
+ Run command in shell as a subprocess.
12
+
13
+ :param cmd: The command to be executed as a sequence of strings
14
+ :param cwd: current working directory
15
+ :return: The return code of command
16
+ """
17
+ try:
18
+ proc = run(cmd, check=True, shell=False, cwd=cwd)
19
+ return proc.returncode
20
+ except CalledProcessError as e:
21
+ print(f'Result: {e}')
22
+ return -1
23
+
24
+ def get_command_output(cmd: Sequence[str], cwd: Path | None = None) -> tuple[int, str, str]:
25
+ """
26
+ Execute command and return its output.
27
+
28
+ :param cmd: The command to be executed as a sequence of strings
29
+ :param cwd: current working directory
30
+ :return: Tuple with return code, stderr and stdout
31
+ """
32
+ try:
33
+ result = run(cmd, capture_output=True, check=True, cwd=cwd)
34
+ return result.returncode, result.stderr.decode('utf-8'), result.stdout.decode('utf-8')
35
+ except CalledProcessError as e:
36
+ print(f'Result: {e}')
37
+ return e.returncode, e.stderr.decode('utf-8'), e.stdout.decode('utf-8')
38
+
39
+
40
+ def make_sym_link(to_path: Path, target: Path) -> None:
41
+ """
42
+ Make symbolic link.
43
+
44
+ :param to_path: path to symbolic link
45
+ :param target: target path
46
+ """
47
+ cmd_symlink = f'"New-Item -ItemType SymbolicLink -Path \\"{to_path}\\" -Target \\"{target}\\"'
48
+ ps_command = f"Start-Process powershell.exe -ArgumentList '-Command {cmd_symlink}' -Verb RunAs"
49
+ print(f'Make symbolic link: {ps_command}')
50
+ run_command(cmd=['powershell.exe', '-Command', ps_command])
51
+
52
+
53
+ def rm_sym_link(sym_link: Path) -> None:
54
+ """
55
+ Remove symbolic link.
56
+
57
+ :param sym_link: path to symbolic link
58
+ """
59
+ rm_symlink = f"(Get-Item '{sym_link}').Delete()"
60
+ ps_command = f'Start-Process powershell.exe -ArgumentList "-Command {rm_symlink}" -Verb RunAs'
61
+ print(f'Execute: {ps_command}')
62
+ run_command(cmd=['powershell.exe', '-Command', ps_command])
63
+
64
+
65
+ def venv_list_in(current_path: Path, max_depth: int = 1) -> Sequence[Path]:
66
+ """
67
+ Find all virtual environments in given path.
68
+
69
+ :param current_path: path to search in
70
+ :param max_depth: maximum depth of search
71
+ :return: list of paths to virtual environments
72
+ """
73
+ result = []
74
+ for dirpath, dirnames, _ in walk(current_path, topdown=True):
75
+ for dirname in dirnames:
76
+ if '.venv_' in dirname:
77
+ result.append(Path(dirpath) / dirname)
78
+ if dirpath.count(sep) - str(current_path).count(sep) == max_depth - 1:
79
+ del dirnames[:]
80
+ return result
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.1
2
+ Name: venvflon
3
+ Version: 0.1.0
4
+ Summary: simple virtual environment switcher
5
+ Author: Michal Plichta
6
+ Maintainer: Michal Plichta
7
+ License: MIT License
8
+ Project-URL: Homepage, https://github.com/emcek/venvflon
9
+ Project-URL: Source, https://github.com/emcek/venvflon.git
10
+ Project-URL: Changelog, https://github.com/emcek/venvflon/blob/master/CHANGELOG.md
11
+ Keywords: venv,uv,virtualenv
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Environment :: Win32 (MS Windows)
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Operating System :: Microsoft :: Windows
22
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 10
23
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 11
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Provides-Extra: test
29
+ Requires-Dist: GitPython==3.1.43; extra == "test"
30
+ Requires-Dist: interrogate==1.7.0; extra == "test"
31
+ Requires-Dist: isort==5.13.2; extra == "test"
32
+ Requires-Dist: lxml==5.3.0; extra == "test"
33
+ Requires-Dist: mypy==1.13.0; extra == "test"
34
+ Requires-Dist: packaging==24.2; extra == "test"
35
+ Requires-Dist: pip-audit==2.7.3; extra == "test"
36
+ Requires-Dist: pytest==8.3.3; extra == "test"
37
+ Requires-Dist: pytest-randomly==3.16.0; extra == "test"
38
+ Requires-Dist: ruff==0.7.4; extra == "test"
39
+ Requires-Dist: types-pyinstaller==6.10.0.20240812; extra == "test"
40
+
41
+ # venvflon
42
+ simple virtual environment switcher
43
+
44
+ ## name
45
+ Comes from polish `wenflon` or `kaniula` (eng. `cannula`)
@@ -0,0 +1,24 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/venvflon.egg-info/PKG-INFO
5
+ src/venvflon.egg-info/SOURCES.txt
6
+ src/venvflon.egg-info/dependency_links.txt
7
+ src/venvflon.egg-info/entry_points.txt
8
+ src/venvflon.egg-info/requires.txt
9
+ src/venvflon.egg-info/top_level.txt
10
+ src/venvflon/__init__.py
11
+ src/venvflon/flon.py
12
+ src/venvflon/py.typed
13
+ src/venvflon/run.py
14
+ src/venvflon/utils.py
15
+ src/venvflon.egg-info/PKG-INFO
16
+ src/venvflon.egg-info/SOURCES.txt
17
+ src/venvflon.egg-info/dependency_links.txt
18
+ src/venvflon.egg-info/entry_points.txt
19
+ src/venvflon.egg-info/requires.txt
20
+ src/venvflon.egg-info/top_level.txt
21
+ src/venvflon/img/cannula.ico
22
+ src/venvflon/img/cannula_512.png
23
+ src/venvflon/img/cannula_64.png
24
+ tests/test_utils.py
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ flon_cli = venvflon.run:run
3
+
4
+ [gui_scripts]
5
+ flon = venvflon.run:run
@@ -0,0 +1,13 @@
1
+
2
+ [test]
3
+ GitPython==3.1.43
4
+ interrogate==1.7.0
5
+ isort==5.13.2
6
+ lxml==5.3.0
7
+ mypy==1.13.0
8
+ packaging==24.2
9
+ pip-audit==2.7.3
10
+ pytest==8.3.3
11
+ pytest-randomly==3.16.0
12
+ ruff==0.7.4
13
+ types-pyinstaller==6.10.0.20240812
@@ -0,0 +1 @@
1
+ venvflon
@@ -0,0 +1,31 @@
1
+ from pathlib import Path
2
+ from sys import platform
3
+
4
+ from pytest import mark
5
+
6
+ from venvflon import utils
7
+
8
+
9
+ @mark.slow
10
+ @mark.skipif(condition=platform != 'win32', reason='Run only on Windows')
11
+ @mark.parametrize('cmd, result', [('Clear-Host', 0), ('bullshit', -1)])
12
+ def test_run_command(cmd, result):
13
+ rc = utils.run_command(cmd=['powershell', cmd])
14
+ assert rc == result
15
+
16
+
17
+ def test_success_deep_1_venv_list_in(resources):
18
+ venvs = utils.venv_list_in(current_path=resources, max_depth=1)
19
+ assert len(venvs) == 3
20
+ assert sorted([venv.name for venv in venvs]) == ['.venv_310', '.venv_311', '.venv_312']
21
+
22
+
23
+ def test_success_deep_2_venv_list_in(resources):
24
+ venvs = utils.venv_list_in(current_path=resources, max_depth=2)
25
+ assert len(venvs) == 4
26
+ assert sorted([venv.name for venv in venvs]) == ['.venv_310', '.venv_311', '.venv_312', '.venv_39']
27
+
28
+
29
+ def test_failure_venv_list_in():
30
+ venvs = utils.venv_list_in(current_path=Path('..'), max_depth=1)
31
+ assert len(venvs) == 0