python-package-folder 5.1.4__tar.gz → 5.1.6__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.
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/PKG-INFO +1 -1
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/coverage.svg +2 -2
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/pyproject.toml +1 -1
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/publisher.py +60 -14
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/subfolder_build.py +38 -27
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_exclude_patterns.py +56 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/.copier-answers.yml +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/.github/workflows/ci.yml +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/.github/workflows/publish.yml +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/.gitignore +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/.vscode/settings.json +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/LICENSE +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/MANIFEST.in +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/Makefile +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/README.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/development.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/docs/DEVELOPMENT.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/docs/INSTALLATION.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/docs/PUBLISHING.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/docs/REFERENCE.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/docs/USAGE.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/docs/VERSION_RESOLUTION.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/installation.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/publishing.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/analyzer.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/manager.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/python_package_folder.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/types.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/version.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/version_calculator.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/conftest.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_build_with_external_deps.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_linting.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_preserve_directory_structure.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_publisher.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_shared_subdirectory_imports.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_spreadsheet_creation_imports.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_subfolder_build.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_third_party_dependencies.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_utils.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_version_calculator.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_version_manager.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/tests.py +0 -0
- {python_package_folder-5.1.4 → python_package_folder-5.1.6}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version: 5.1.
|
|
3
|
+
Version: 5.1.6
|
|
4
4
|
Summary: Python package to automatically package and build a folder, fetching all relevant dependencies.
|
|
5
5
|
Project-URL: Repository, https://github.com/alelom/python-package-folder
|
|
6
6
|
Author-email: Alessio Lombardi <work@alelom.com>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
|
15
15
|
<text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
|
16
16
|
<text x="31.5" y="14">coverage</text>
|
|
17
|
-
<text x="81" y="15" fill="#010101" fill-opacity=".3">
|
|
18
|
-
<text x="81" y="14">
|
|
17
|
+
<text x="81" y="15" fill="#010101" fill-opacity=".3">68%</text>
|
|
18
|
+
<text x="81" y="14">68%</text>
|
|
19
19
|
</g>
|
|
20
20
|
</svg>
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/publisher.py
RENAMED
|
@@ -8,6 +8,7 @@ repositories including PyPI, PyPI Test, and Azure Artifacts.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import getpass
|
|
11
|
+
import os
|
|
11
12
|
import subprocess
|
|
12
13
|
import sys
|
|
13
14
|
from enum import Enum
|
|
@@ -19,6 +20,18 @@ except ImportError:
|
|
|
19
20
|
keyring = None
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
def _is_non_interactive() -> bool:
|
|
24
|
+
"""Check if running in a non-interactive environment (CI/CD)."""
|
|
25
|
+
# Check for common CI environment variables
|
|
26
|
+
ci_vars = ["GITHUB_ACTIONS", "CI", "CONTINUOUS_INTEGRATION", "TF_BUILD"]
|
|
27
|
+
if any(os.getenv(var) for var in ci_vars):
|
|
28
|
+
return True
|
|
29
|
+
# Check if stdin is not a TTY (non-interactive)
|
|
30
|
+
if not sys.stdin.isatty():
|
|
31
|
+
return True
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
22
35
|
class Repository(Enum):
|
|
23
36
|
"""
|
|
24
37
|
Supported package repositories.
|
|
@@ -112,9 +125,9 @@ class Publisher:
|
|
|
112
125
|
"""
|
|
113
126
|
Get credentials for publishing.
|
|
114
127
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
128
|
+
Prompts for username and password/token if not already provided.
|
|
129
|
+
In non-interactive environments (CI/CD), checks environment variables
|
|
130
|
+
or raises an error if credentials are missing.
|
|
118
131
|
|
|
119
132
|
Returns:
|
|
120
133
|
Tuple of (username, password/token)
|
|
@@ -122,21 +135,54 @@ class Publisher:
|
|
|
122
135
|
username = self.username
|
|
123
136
|
password = self.password
|
|
124
137
|
|
|
125
|
-
|
|
126
|
-
|
|
138
|
+
is_non_interactive_env = _is_non_interactive()
|
|
139
|
+
|
|
140
|
+
# Get username
|
|
127
141
|
if not username:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
if is_non_interactive_env:
|
|
143
|
+
# Check environment variables
|
|
144
|
+
username = os.getenv("TWINE_USERNAME") or os.getenv("PYPI_USERNAME")
|
|
145
|
+
if not username:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
f"Username is required for publishing to {self.repository.value} in CI/CD. "
|
|
148
|
+
"Please provide --username argument or set TWINE_USERNAME/PYPI_USERNAME environment variable."
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
username = input(f"Enter username for {self.repository.value}: ").strip()
|
|
152
|
+
if not username:
|
|
153
|
+
raise ValueError("Username is required")
|
|
131
154
|
|
|
155
|
+
# Get password
|
|
132
156
|
if not password:
|
|
133
|
-
if
|
|
134
|
-
|
|
157
|
+
if is_non_interactive_env:
|
|
158
|
+
# Check environment variables (common names used by twine and CI/CD)
|
|
159
|
+
password = (
|
|
160
|
+
os.getenv("TWINE_PASSWORD")
|
|
161
|
+
or os.getenv("PYPI_PASSWORD")
|
|
162
|
+
or os.getenv("AZURE_ARTIFACTS_TOKEN") # For Azure
|
|
163
|
+
)
|
|
164
|
+
if not password:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
f"Password/token is required for publishing to {self.repository.value} in CI/CD. "
|
|
167
|
+
"Please provide --password argument or set one of: "
|
|
168
|
+
"TWINE_PASSWORD, PYPI_PASSWORD, or AZURE_ARTIFACTS_TOKEN environment variable."
|
|
169
|
+
)
|
|
135
170
|
else:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
171
|
+
if self.repository == Repository.AZURE:
|
|
172
|
+
prompt = f"Enter Azure Artifacts token for {username}: "
|
|
173
|
+
else:
|
|
174
|
+
prompt = f"Enter PyPI token for {username} (or __token__ for API token): "
|
|
175
|
+
try:
|
|
176
|
+
password = getpass.getpass(prompt)
|
|
177
|
+
except (EOFError, OSError):
|
|
178
|
+
# Handle non-interactive environments gracefully
|
|
179
|
+
raise ValueError(
|
|
180
|
+
f"Password/token is required for publishing to {self.repository.value}. "
|
|
181
|
+
"Cannot prompt for password in non-interactive environment. "
|
|
182
|
+
"Please provide --password argument or set TWINE_PASSWORD/PYPI_PASSWORD environment variable."
|
|
183
|
+
)
|
|
184
|
+
if not password:
|
|
185
|
+
raise ValueError("Password/token is required")
|
|
140
186
|
|
|
141
187
|
# Auto-detect if password is an API token and adjust username
|
|
142
188
|
if password.startswith("pypi-") or password.startswith("pypi_Ag"):
|
|
@@ -694,44 +694,55 @@ class SubfolderBuildConfig:
|
|
|
694
694
|
# Usually after [dependency-groups] or at the end
|
|
695
695
|
insert_index = len(result)
|
|
696
696
|
tool_section_exists = False
|
|
697
|
+
tool_section_start = -1
|
|
698
|
+
tool_section_end = -1
|
|
699
|
+
|
|
700
|
+
# First, search specifically for [tool.python-package-folder]
|
|
697
701
|
for i, line in enumerate(result):
|
|
698
702
|
if line.strip() == "[tool.python-package-folder]":
|
|
699
703
|
tool_section_exists = True
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
704
|
+
tool_section_start = i
|
|
705
|
+
# Find end of section (next [section] or end of file)
|
|
706
|
+
for j in range(i + 1, len(result)):
|
|
707
|
+
if result[j].strip().startswith("["):
|
|
708
|
+
tool_section_end = j
|
|
709
|
+
break
|
|
710
|
+
if tool_section_end == -1:
|
|
711
|
+
tool_section_end = len(result)
|
|
705
712
|
break
|
|
713
|
+
|
|
714
|
+
# If not found, find a good insertion point before other [tool.*] sections
|
|
715
|
+
if not tool_section_exists:
|
|
716
|
+
for i, line in enumerate(result):
|
|
717
|
+
if line.strip().startswith("[tool.") and i > 0:
|
|
718
|
+
# Insert before other tool sections
|
|
719
|
+
insert_index = i
|
|
720
|
+
break
|
|
706
721
|
|
|
707
722
|
# Format exclude patterns
|
|
708
723
|
patterns_str = ", ".join(f'"{p}"' for p in exclude_patterns)
|
|
709
|
-
exclude_lines = [
|
|
710
|
-
"",
|
|
711
|
-
"[tool.python-package-folder]",
|
|
712
|
-
f'exclude-patterns = [{patterns_str}]',
|
|
713
|
-
]
|
|
714
724
|
|
|
715
725
|
if tool_section_exists:
|
|
716
|
-
#
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
break
|
|
730
|
-
else:
|
|
731
|
-
# Add exclude-patterns to existing section
|
|
732
|
-
result.insert(end_index - 1, f'exclude-patterns = [{patterns_str}]')
|
|
726
|
+
# Update existing section
|
|
727
|
+
# Check if exclude-patterns already exists in the section
|
|
728
|
+
has_exclude_patterns = False
|
|
729
|
+
for i in range(tool_section_start + 1, tool_section_end):
|
|
730
|
+
if "exclude-patterns" in result[i]:
|
|
731
|
+
has_exclude_patterns = True
|
|
732
|
+
# Update the existing line
|
|
733
|
+
result[i] = f'exclude-patterns = [{patterns_str}]'
|
|
734
|
+
break
|
|
735
|
+
|
|
736
|
+
if not has_exclude_patterns:
|
|
737
|
+
# Add exclude-patterns to existing section (before the next section)
|
|
738
|
+
result.insert(tool_section_end, f'exclude-patterns = [{patterns_str}]')
|
|
733
739
|
else:
|
|
734
740
|
# Insert new section
|
|
741
|
+
exclude_lines = [
|
|
742
|
+
"",
|
|
743
|
+
"[tool.python-package-folder]",
|
|
744
|
+
f'exclude-patterns = [{patterns_str}]',
|
|
745
|
+
]
|
|
735
746
|
result[insert_index:insert_index] = exclude_lines
|
|
736
747
|
|
|
737
748
|
return "\n".join(result)
|
|
@@ -211,4 +211,60 @@ class TestExcludePatternsInBuild:
|
|
|
211
211
|
assert ".*_test.*" in content
|
|
212
212
|
assert "sandbox" in content
|
|
213
213
|
|
|
214
|
+
# Verify there's only ONE [tool.python-package-folder] section (no duplicates)
|
|
215
|
+
sections = [line for line in content.split("\n") if line.strip() == "[tool.python-package-folder]"]
|
|
216
|
+
assert len(sections) == 1, f"Found {len(sections)} duplicate [tool.python-package-folder] sections"
|
|
217
|
+
|
|
218
|
+
config.restore()
|
|
219
|
+
|
|
220
|
+
def test_exclude_patterns_no_duplicate_section(self, tmp_path: Path) -> None:
|
|
221
|
+
"""Test that exclude patterns don't create duplicate sections when original already has it."""
|
|
222
|
+
project_root = tmp_path / "test_project"
|
|
223
|
+
project_root.mkdir()
|
|
224
|
+
|
|
225
|
+
# Create pyproject.toml with existing [tool.python-package-folder] section
|
|
226
|
+
pyproject_content = """[project]
|
|
227
|
+
name = "test-package"
|
|
228
|
+
version = "0.1.0"
|
|
229
|
+
|
|
230
|
+
[build-system]
|
|
231
|
+
requires = ["hatchling"]
|
|
232
|
+
build-backend = "hatchling.build"
|
|
233
|
+
|
|
234
|
+
[tool.hatch.build.targets.wheel]
|
|
235
|
+
packages = ["src/test_package"]
|
|
236
|
+
|
|
237
|
+
[tool.python-package-folder]
|
|
238
|
+
exclude-patterns = ["_SS", ".*_test.*"]
|
|
239
|
+
|
|
240
|
+
[tool.pylint.'TYPECHECK']
|
|
241
|
+
generated-members = ["networkx.*"]
|
|
242
|
+
"""
|
|
243
|
+
(project_root / "pyproject.toml").write_text(pyproject_content)
|
|
244
|
+
|
|
245
|
+
# Create source directory
|
|
246
|
+
src_dir = project_root / "src" / "test_package"
|
|
247
|
+
src_dir.mkdir(parents=True)
|
|
248
|
+
(src_dir / "__init__.py").write_text("")
|
|
249
|
+
|
|
250
|
+
config = SubfolderBuildConfig(
|
|
251
|
+
project_root=project_root,
|
|
252
|
+
src_dir=src_dir,
|
|
253
|
+
package_name="test-package",
|
|
254
|
+
version="1.0.0",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Create temporary pyproject.toml
|
|
258
|
+
temp_pyproject = config.create_temp_pyproject()
|
|
259
|
+
assert temp_pyproject is not None
|
|
260
|
+
|
|
261
|
+
# Check that there's only ONE [tool.python-package-folder] section
|
|
262
|
+
content = temp_pyproject.read_text()
|
|
263
|
+
sections = [line for line in content.split("\n") if line.strip() == "[tool.python-package-folder]"]
|
|
264
|
+
assert len(sections) == 1, f"Found {len(sections)} duplicate [tool.python-package-folder] sections: {sections}"
|
|
265
|
+
|
|
266
|
+
# Verify exclude-patterns is present
|
|
267
|
+
assert "exclude-patterns" in content
|
|
268
|
+
assert "_SS" in content
|
|
269
|
+
|
|
214
270
|
config.restore()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/__init__.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/__main__.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/analyzer.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/finder.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/manager.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/types.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/utils.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/src/python_package_folder/version.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/folder_structure/some_globals.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_build_with_external_deps.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_third_party_dependencies.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_package_folder-5.1.4 → python_package_folder-5.1.6}/tests/test_version_calculator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|