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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frigobar
3
- Version: 12
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 = "12"
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
@@ -13,7 +13,7 @@ wheels = [
13
13
 
14
14
  [[package]]
15
15
  name = "frigobar"
16
- version = "11"
16
+ version = "14"
17
17
  source = { editable = "." }
18
18
  dependencies = [
19
19
  { name = "pathspec" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes