frigobar 12__tar.gz → 14__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.
- {frigobar-12 → frigobar-14}/PKG-INFO +1 -1
- {frigobar-12 → frigobar-14}/frigobar/frigobar.py +83 -0
- {frigobar-12 → frigobar-14}/pyproject.toml +1 -1
- {frigobar-12 → frigobar-14}/tests/test_frigobar.py +186 -0
- {frigobar-12 → frigobar-14}/uv.lock +1 -1
- {frigobar-12 → frigobar-14}/.github/workflows/publish-pypi.yml +0 -0
- {frigobar-12 → frigobar-14}/.gitignore +0 -0
- {frigobar-12 → frigobar-14}/frigobar/__init__.py +0 -0
- {frigobar-12 → frigobar-14}/frigobar/__main__.py +0 -0
- {frigobar-12 → frigobar-14}/frigobar/cli.py +0 -0
- {frigobar-12 → frigobar-14}/license +0 -0
- {frigobar-12 → frigobar-14}/readme.md +0 -0
- {frigobar-12 → frigobar-14}/tests/__init__.py +0 -0
- {frigobar-12 → frigobar-14}/tests/conftest.py +0 -0
- {frigobar-12 → frigobar-14}/tests/script_folder/.gitignore +0 -0
- {frigobar-12 → frigobar-14}/tests/script_folder/another_script.py +0 -0
- {frigobar-12 → frigobar-14}/tests/script_folder/pyproject.toml +0 -0
- {frigobar-12 → frigobar-14}/tests/script_folder/requirements.txt +0 -0
- {frigobar-12 → frigobar-14}/tests/script_folder/script.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: frigobar
|
|
3
|
-
Version:
|
|
3
|
+
Version: 14
|
|
4
4
|
Summary: Distribute Python scripts to Windows machines without freezing them.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ubalklen/Frigobar
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/ubalklen/Frigobar/issues
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import glob
|
|
2
2
|
import os
|
|
3
3
|
import shutil
|
|
4
|
+
import tomllib
|
|
4
5
|
from subprocess import CalledProcessError, Popen, run
|
|
5
6
|
|
|
6
7
|
import pathspec
|
|
@@ -59,6 +60,7 @@ def _get_git_non_ignored_files(directory):
|
|
|
59
60
|
cwd=directory,
|
|
60
61
|
capture_output=True,
|
|
61
62
|
text=True,
|
|
63
|
+
encoding="utf-8",
|
|
62
64
|
check=True,
|
|
63
65
|
)
|
|
64
66
|
return {os.path.normpath(f) for f in result.stdout.strip("\0").split("\0") if f}
|
|
@@ -125,6 +127,72 @@ def _create_ignore_fn(base_dir, target_directory, git_files=None, gitignore_spec
|
|
|
125
127
|
return ignore_fn
|
|
126
128
|
|
|
127
129
|
|
|
130
|
+
PYPROJECT_PATH_FIELDS = [
|
|
131
|
+
["tool", "setuptools", "packages", "find", "where"],
|
|
132
|
+
["tool", "pytest", "ini_options", "pythonpath"],
|
|
133
|
+
["tool", "pytest", "ini_options", "testpaths"],
|
|
134
|
+
["tool", "hatch", "build", "targets", "wheel", "packages"],
|
|
135
|
+
["tool", "hatch", "build", "targets", "sdist", "include"],
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _get_nested(data, keys):
|
|
140
|
+
for key in keys:
|
|
141
|
+
if not isinstance(data, dict) or key not in data:
|
|
142
|
+
return None
|
|
143
|
+
data = data[key]
|
|
144
|
+
return data
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _compute_path_replacement(value, include_dir_name, pyproject_inside_include):
|
|
148
|
+
if pyproject_inside_include:
|
|
149
|
+
if value == ".":
|
|
150
|
+
return f"script/{include_dir_name}"
|
|
151
|
+
return f"script/{include_dir_name}/{value}"
|
|
152
|
+
else:
|
|
153
|
+
if value == include_dir_name:
|
|
154
|
+
return f"script/{include_dir_name}"
|
|
155
|
+
if value.startswith(include_dir_name + "/"):
|
|
156
|
+
return f"script/{value}"
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _patch_pyproject_paths(pyproject_path, include_dir_name, pyproject_inside_include):
|
|
161
|
+
with open(pyproject_path, "rb") as f:
|
|
162
|
+
data = tomllib.load(f)
|
|
163
|
+
|
|
164
|
+
replacements = []
|
|
165
|
+
for field_path in PYPROJECT_PATH_FIELDS:
|
|
166
|
+
value = _get_nested(data, field_path)
|
|
167
|
+
if value is None:
|
|
168
|
+
continue
|
|
169
|
+
if isinstance(value, list):
|
|
170
|
+
for item in value:
|
|
171
|
+
if isinstance(item, str):
|
|
172
|
+
new_val = _compute_path_replacement(
|
|
173
|
+
item, include_dir_name, pyproject_inside_include
|
|
174
|
+
)
|
|
175
|
+
if new_val:
|
|
176
|
+
replacements.append((item, new_val))
|
|
177
|
+
elif isinstance(value, str):
|
|
178
|
+
new_val = _compute_path_replacement(value, include_dir_name, pyproject_inside_include)
|
|
179
|
+
if new_val:
|
|
180
|
+
replacements.append((value, new_val))
|
|
181
|
+
|
|
182
|
+
if not replacements:
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
with open(pyproject_path, "r", encoding="utf-8") as f:
|
|
186
|
+
content = f.read()
|
|
187
|
+
|
|
188
|
+
for old_val, new_val in replacements:
|
|
189
|
+
content = content.replace(f'"{old_val}"', f'"{new_val}"')
|
|
190
|
+
content = content.replace(f"'{old_val}'", f"'{new_val}'")
|
|
191
|
+
|
|
192
|
+
with open(pyproject_path, "w", encoding="utf-8") as f:
|
|
193
|
+
f.write(content)
|
|
194
|
+
|
|
195
|
+
|
|
128
196
|
def create_frigobar(
|
|
129
197
|
script_path: str,
|
|
130
198
|
target_directory: str = "frigobar",
|
|
@@ -218,6 +286,21 @@ def create_frigobar(
|
|
|
218
286
|
if os.path.exists(pyproject_path):
|
|
219
287
|
shutil.copy(pyproject_path, target_directory)
|
|
220
288
|
|
|
289
|
+
# Patch pyproject.toml paths when using include_directory
|
|
290
|
+
if include_directory:
|
|
291
|
+
target_pyproject = os.path.join(target_directory, "pyproject.toml")
|
|
292
|
+
if os.path.exists(target_pyproject):
|
|
293
|
+
include_dir_name = os.path.basename(include_directory)
|
|
294
|
+
if pyproject_file:
|
|
295
|
+
pyproject_source_dir = os.path.dirname(os.path.abspath(pyproject_file))
|
|
296
|
+
else:
|
|
297
|
+
pyproject_source_dir = os.path.dirname(script_path)
|
|
298
|
+
include_abs = os.path.abspath(include_directory)
|
|
299
|
+
pyproject_inside_include = os.path.commonpath(
|
|
300
|
+
[pyproject_source_dir, include_abs]
|
|
301
|
+
) == os.path.normpath(include_abs)
|
|
302
|
+
_patch_pyproject_paths(target_pyproject, include_dir_name, pyproject_inside_include)
|
|
303
|
+
|
|
221
304
|
# Create bat file
|
|
222
305
|
if include_directory:
|
|
223
306
|
include_dir_name = os.path.basename(include_directory)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "frigobar"
|
|
7
|
-
version = "
|
|
7
|
+
version = "14"
|
|
8
8
|
description = "Distribute Python scripts to Windows machines without freezing them."
|
|
9
9
|
authors = [
|
|
10
10
|
{name="ubalklen", email="42127323+ubalklen@users.noreply.github.com"},
|
|
@@ -590,6 +590,7 @@ def test_get_git_non_ignored_files_handles_non_ascii_names():
|
|
|
590
590
|
cmd = mock_run.call_args[0][0]
|
|
591
591
|
assert "-z" in cmd
|
|
592
592
|
assert "core.quotepath=false" in cmd
|
|
593
|
+
assert mock_run.call_args[1]["encoding"] == "utf-8"
|
|
593
594
|
|
|
594
595
|
|
|
595
596
|
def test_create_frigobar_copy_directory_with_non_ascii_filenames(tmp_path):
|
|
@@ -612,3 +613,188 @@ def test_create_frigobar_copy_directory_with_non_ascii_filenames(tmp_path):
|
|
|
612
613
|
assert path.exists(path.join(str(target), "script", "script.py"))
|
|
613
614
|
assert path.exists(path.join(str(target), "script", "Notificação.docx"))
|
|
614
615
|
assert path.exists(path.join(str(target), "script", "résumé.txt"))
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def test_patch_pyproject_paths_setuptools_where_outside(tmp_path):
|
|
619
|
+
"""When pyproject.toml is outside include_directory, paths matching include_dir_name are prefixed"""
|
|
620
|
+
source_dir = tmp_path / "project"
|
|
621
|
+
source_dir.mkdir()
|
|
622
|
+
src_dir = source_dir / "src"
|
|
623
|
+
src_dir.mkdir()
|
|
624
|
+
(src_dir / "main.py").write_text("print('hello')")
|
|
625
|
+
(src_dir / "__init__.py").write_text("")
|
|
626
|
+
|
|
627
|
+
pyproject_content = '[project]\nname = "my-pkg"\nversion = "1.0"\n\n[tool.setuptools.packages.find]\nwhere = ["src"]\n'
|
|
628
|
+
pyproject_file = source_dir / "pyproject.toml"
|
|
629
|
+
pyproject_file.write_text(pyproject_content)
|
|
630
|
+
|
|
631
|
+
target = tmp_path / "dist"
|
|
632
|
+
|
|
633
|
+
with patch("frigobar.frigobar._get_git_non_ignored_files", return_value=None):
|
|
634
|
+
frigobar.create_frigobar(
|
|
635
|
+
script_path=str(src_dir / "main.py"),
|
|
636
|
+
target_directory=str(target),
|
|
637
|
+
include_directory=str(src_dir),
|
|
638
|
+
pyproject_file=str(pyproject_file),
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
with open(path.join(str(target), "pyproject.toml"), "r") as f:
|
|
642
|
+
content = f.read()
|
|
643
|
+
assert 'where = ["script/src"]' in content
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def test_patch_pyproject_paths_setuptools_where_inside(tmp_path):
|
|
647
|
+
"""When pyproject.toml is inside include_directory, all paths get script/<include_dir_name> prefix"""
|
|
648
|
+
source_dir = tmp_path / "project"
|
|
649
|
+
source_dir.mkdir()
|
|
650
|
+
lib_dir = source_dir / "lib"
|
|
651
|
+
lib_dir.mkdir()
|
|
652
|
+
(lib_dir / "__init__.py").write_text("")
|
|
653
|
+
(source_dir / "main.py").write_text("print('hello')")
|
|
654
|
+
|
|
655
|
+
pyproject_content = '[project]\nname = "my-pkg"\nversion = "1.0"\n\n[tool.setuptools.packages.find]\nwhere = ["lib"]\n'
|
|
656
|
+
pyproject_file = source_dir / "pyproject.toml"
|
|
657
|
+
pyproject_file.write_text(pyproject_content)
|
|
658
|
+
|
|
659
|
+
target = tmp_path / "dist"
|
|
660
|
+
|
|
661
|
+
with patch("frigobar.frigobar._get_git_non_ignored_files", return_value=None):
|
|
662
|
+
frigobar.create_frigobar(
|
|
663
|
+
script_path=str(source_dir / "main.py"),
|
|
664
|
+
target_directory=str(target),
|
|
665
|
+
include_directory=str(source_dir),
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
with open(path.join(str(target), "pyproject.toml"), "r") as f:
|
|
669
|
+
content = f.read()
|
|
670
|
+
assert 'where = ["script/project/lib"]' in content
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def test_patch_pyproject_paths_dot_where_inside(tmp_path):
|
|
674
|
+
"""When pyproject.toml is inside include_directory and where=['.'], it becomes script/<dir>"""
|
|
675
|
+
source_dir = tmp_path / "myproject"
|
|
676
|
+
source_dir.mkdir()
|
|
677
|
+
(source_dir / "main.py").write_text("print('hello')")
|
|
678
|
+
(source_dir / "__init__.py").write_text("")
|
|
679
|
+
|
|
680
|
+
pyproject_content = '[project]\nname = "my-pkg"\nversion = "1.0"\n\n[tool.setuptools.packages.find]\nwhere = ["."]\n'
|
|
681
|
+
pyproject_file = source_dir / "pyproject.toml"
|
|
682
|
+
pyproject_file.write_text(pyproject_content)
|
|
683
|
+
|
|
684
|
+
target = tmp_path / "dist"
|
|
685
|
+
|
|
686
|
+
with patch("frigobar.frigobar._get_git_non_ignored_files", return_value=None):
|
|
687
|
+
frigobar.create_frigobar(
|
|
688
|
+
script_path=str(source_dir / "main.py"),
|
|
689
|
+
target_directory=str(target),
|
|
690
|
+
include_directory=str(source_dir),
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
with open(path.join(str(target), "pyproject.toml"), "r") as f:
|
|
694
|
+
content = f.read()
|
|
695
|
+
assert 'where = ["script/myproject"]' in content
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def test_patch_pyproject_paths_hatch_packages(tmp_path):
|
|
699
|
+
"""Hatch build targets packages field is also patched"""
|
|
700
|
+
source_dir = tmp_path / "project"
|
|
701
|
+
source_dir.mkdir()
|
|
702
|
+
src_dir = source_dir / "src"
|
|
703
|
+
src_dir.mkdir()
|
|
704
|
+
pkg_dir = src_dir / "mypkg"
|
|
705
|
+
pkg_dir.mkdir()
|
|
706
|
+
(pkg_dir / "__init__.py").write_text("")
|
|
707
|
+
(src_dir / "main.py").write_text("print('hello')")
|
|
708
|
+
|
|
709
|
+
pyproject_content = '[project]\nname = "my-pkg"\nversion = "1.0"\n\n[tool.hatch.build.targets.wheel]\npackages = ["src/mypkg"]\n'
|
|
710
|
+
pyproject_file = source_dir / "pyproject.toml"
|
|
711
|
+
pyproject_file.write_text(pyproject_content)
|
|
712
|
+
|
|
713
|
+
target = tmp_path / "dist"
|
|
714
|
+
|
|
715
|
+
with patch("frigobar.frigobar._get_git_non_ignored_files", return_value=None):
|
|
716
|
+
frigobar.create_frigobar(
|
|
717
|
+
script_path=str(src_dir / "main.py"),
|
|
718
|
+
target_directory=str(target),
|
|
719
|
+
include_directory=str(src_dir),
|
|
720
|
+
pyproject_file=str(pyproject_file),
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
with open(path.join(str(target), "pyproject.toml"), "r") as f:
|
|
724
|
+
content = f.read()
|
|
725
|
+
assert 'packages = ["script/src/mypkg"]' in content
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def test_patch_pyproject_paths_no_patch_without_include_directory(tmp_path):
|
|
729
|
+
"""pyproject.toml is not patched when include_directory is not used"""
|
|
730
|
+
source_dir = tmp_path / "project"
|
|
731
|
+
source_dir.mkdir()
|
|
732
|
+
(source_dir / "main.py").write_text("print('hello')")
|
|
733
|
+
|
|
734
|
+
pyproject_content = '[project]\nname = "my-pkg"\nversion = "1.0"\n\n[tool.setuptools.packages.find]\nwhere = ["src"]\n'
|
|
735
|
+
pyproject_file = source_dir / "pyproject.toml"
|
|
736
|
+
pyproject_file.write_text(pyproject_content)
|
|
737
|
+
|
|
738
|
+
target = tmp_path / "dist"
|
|
739
|
+
|
|
740
|
+
frigobar.create_frigobar(
|
|
741
|
+
script_path=str(source_dir / "main.py"),
|
|
742
|
+
target_directory=str(target),
|
|
743
|
+
pyproject_file=str(pyproject_file),
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
with open(path.join(str(target), "pyproject.toml"), "r") as f:
|
|
747
|
+
content = f.read()
|
|
748
|
+
assert 'where = ["src"]' in content
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def test_patch_pyproject_paths_no_patch_with_requirements(tmp_path):
|
|
752
|
+
"""pyproject.toml is not present when requirements_file is used"""
|
|
753
|
+
source_dir = tmp_path / "project"
|
|
754
|
+
source_dir.mkdir()
|
|
755
|
+
src_dir = source_dir / "src"
|
|
756
|
+
src_dir.mkdir()
|
|
757
|
+
(src_dir / "main.py").write_text("print('hello')")
|
|
758
|
+
|
|
759
|
+
req_file = source_dir / "requirements.txt"
|
|
760
|
+
req_file.write_text("requests\n")
|
|
761
|
+
|
|
762
|
+
target = tmp_path / "dist"
|
|
763
|
+
|
|
764
|
+
with patch("frigobar.frigobar._get_git_non_ignored_files", return_value=None):
|
|
765
|
+
frigobar.create_frigobar(
|
|
766
|
+
script_path=str(src_dir / "main.py"),
|
|
767
|
+
target_directory=str(target),
|
|
768
|
+
include_directory=str(src_dir),
|
|
769
|
+
requirements_file=str(req_file),
|
|
770
|
+
python_version="3.12",
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
assert not path.exists(path.join(str(target), "pyproject.toml"))
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
def test_patch_pyproject_paths_pytest_pythonpath(tmp_path):
|
|
777
|
+
"""pytest pythonpath field is also patched"""
|
|
778
|
+
source_dir = tmp_path / "project"
|
|
779
|
+
source_dir.mkdir()
|
|
780
|
+
src_dir = source_dir / "src"
|
|
781
|
+
src_dir.mkdir()
|
|
782
|
+
(src_dir / "main.py").write_text("print('hello')")
|
|
783
|
+
|
|
784
|
+
pyproject_content = '[project]\nname = "my-pkg"\nversion = "1.0"\n\n[tool.pytest.ini_options]\npythonpath = ["src"]\ntestpaths = ["tests"]\n'
|
|
785
|
+
pyproject_file = source_dir / "pyproject.toml"
|
|
786
|
+
pyproject_file.write_text(pyproject_content)
|
|
787
|
+
|
|
788
|
+
target = tmp_path / "dist"
|
|
789
|
+
|
|
790
|
+
with patch("frigobar.frigobar._get_git_non_ignored_files", return_value=None):
|
|
791
|
+
frigobar.create_frigobar(
|
|
792
|
+
script_path=str(src_dir / "main.py"),
|
|
793
|
+
target_directory=str(target),
|
|
794
|
+
include_directory=str(src_dir),
|
|
795
|
+
pyproject_file=str(pyproject_file),
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
with open(path.join(str(target), "pyproject.toml"), "r") as f:
|
|
799
|
+
content = f.read()
|
|
800
|
+
assert 'pythonpath = ["script/src"]' in content
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|