python-package-folder 5.2.0__tar.gz → 5.2.2__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.2.0 → python_package_folder-5.2.2}/PKG-INFO +1 -1
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/coverage.svg +2 -2
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/pyproject.toml +1 -1
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/publisher.py +50 -2
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/python_package_folder.py +19 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/version_calculator.py +93 -19
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_version_calculator.py +33 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/.copier-answers.yml +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/.github/workflows/ci.yml +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/.github/workflows/publish.yml +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/.gitignore +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/.vscode/settings.json +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/LICENSE +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/MANIFEST.in +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/Makefile +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/README.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/development.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/docs/DEVELOPMENT.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/docs/INSTALLATION.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/docs/PUBLISHING.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/docs/REFERENCE.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/docs/USAGE.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/docs/VERSION_RESOLUTION.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/installation.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/publishing.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/analyzer.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/manager.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/subfolder_build.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/types.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/version.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/conftest.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_build_with_external_deps.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_exclude_patterns.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_linting.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_preserve_directory_structure.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_publisher.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_shared_subdirectory_imports.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_spreadsheet_creation_imports.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_subfolder_build.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_third_party_dependencies.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_utils.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_version_manager.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/tests.py +0 -0
- {python_package_folder-5.2.0 → python_package_folder-5.2.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version: 5.2.
|
|
3
|
+
Version: 5.2.2
|
|
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">67%</text>
|
|
18
|
+
<text x="81" y="14">67%</text>
|
|
19
19
|
</g>
|
|
20
20
|
</svg>
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/publisher.py
RENAMED
|
@@ -310,11 +310,59 @@ class Publisher:
|
|
|
310
310
|
print(f"Files to upload: {len(dist_files)}")
|
|
311
311
|
|
|
312
312
|
try:
|
|
313
|
-
subprocess.run(
|
|
313
|
+
result = subprocess.run(
|
|
314
|
+
cmd,
|
|
315
|
+
check=True,
|
|
316
|
+
text=True,
|
|
317
|
+
capture_output=True,
|
|
318
|
+
)
|
|
319
|
+
# Print twine output if available
|
|
320
|
+
if result.stdout:
|
|
321
|
+
print(result.stdout)
|
|
322
|
+
if result.stderr:
|
|
323
|
+
print(result.stderr, file=sys.stderr)
|
|
314
324
|
print(f"\n✓ Successfully published to {self.repository.value}")
|
|
315
325
|
except subprocess.CalledProcessError as e:
|
|
316
326
|
print(f"\n✗ Failed to publish to {self.repository.value}", file=sys.stderr)
|
|
317
|
-
|
|
327
|
+
|
|
328
|
+
# Extract and display twine's actual error message
|
|
329
|
+
error_details = []
|
|
330
|
+
if e.stdout:
|
|
331
|
+
error_details.append(f"stdout: {e.stdout}")
|
|
332
|
+
if e.stderr:
|
|
333
|
+
error_details.append(f"stderr: {e.stderr}")
|
|
334
|
+
if e.returncode is not None:
|
|
335
|
+
error_details.append(f"exit code: {e.returncode}")
|
|
336
|
+
|
|
337
|
+
if error_details:
|
|
338
|
+
print("Twine error details:", file=sys.stderr)
|
|
339
|
+
for detail in error_details:
|
|
340
|
+
print(f" {detail}", file=sys.stderr)
|
|
341
|
+
else:
|
|
342
|
+
# Fallback to generic error if no output captured
|
|
343
|
+
print(f"Command failed: {' '.join(cmd)}", file=sys.stderr)
|
|
344
|
+
print(f"Return code: {e.returncode}", file=sys.stderr)
|
|
345
|
+
|
|
346
|
+
# Provide helpful hints based on common errors
|
|
347
|
+
if e.returncode == 1:
|
|
348
|
+
if e.stderr and ("already exists" in e.stderr.lower() or "409" in e.stderr or "conflict" in e.stderr.lower()):
|
|
349
|
+
print(
|
|
350
|
+
"\nHint: This version may already exist on the repository. "
|
|
351
|
+
"Use --skip-existing to skip files that already exist, "
|
|
352
|
+
"or publish a new version.",
|
|
353
|
+
file=sys.stderr,
|
|
354
|
+
)
|
|
355
|
+
elif e.stderr and ("401" in e.stderr or "unauthorized" in e.stderr.lower()):
|
|
356
|
+
print(
|
|
357
|
+
"\nHint: Authentication failed. Check your credentials.",
|
|
358
|
+
file=sys.stderr,
|
|
359
|
+
)
|
|
360
|
+
elif e.stderr and ("403" in e.stderr or "forbidden" in e.stderr.lower()):
|
|
361
|
+
print(
|
|
362
|
+
"\nHint: Access forbidden. Check your permissions for this repository.",
|
|
363
|
+
file=sys.stderr,
|
|
364
|
+
)
|
|
365
|
+
|
|
318
366
|
raise
|
|
319
367
|
|
|
320
368
|
def publish_interactive(self, skip_existing: bool = False) -> None:
|
|
@@ -186,6 +186,21 @@ def main() -> int:
|
|
|
186
186
|
repository = args.publish if args.publish else None
|
|
187
187
|
repository_url = args.repository_url if args.publish else None
|
|
188
188
|
|
|
189
|
+
# Get credentials for authenticated registry queries (especially Azure Artifacts)
|
|
190
|
+
# Try to get them from args or environment variables
|
|
191
|
+
query_username = args.username
|
|
192
|
+
query_password = args.password
|
|
193
|
+
|
|
194
|
+
# If not provided via args, check environment variables (for Azure Artifacts)
|
|
195
|
+
if repository == "azure" and not query_username:
|
|
196
|
+
query_username = os.getenv("TWINE_USERNAME") or os.getenv("PYPI_USERNAME")
|
|
197
|
+
if repository == "azure" and not query_password:
|
|
198
|
+
query_password = (
|
|
199
|
+
os.getenv("TWINE_PASSWORD")
|
|
200
|
+
or os.getenv("PYPI_PASSWORD")
|
|
201
|
+
or os.getenv("AZURE_ARTIFACTS_TOKEN")
|
|
202
|
+
)
|
|
203
|
+
|
|
189
204
|
if is_subfolder:
|
|
190
205
|
# Workflow 1: subfolder build
|
|
191
206
|
# src_dir is guaranteed to be relative to project_root due to is_subfolder check
|
|
@@ -199,6 +214,8 @@ def main() -> int:
|
|
|
199
214
|
subfolder_path=subfolder_rel_path,
|
|
200
215
|
repository=repository,
|
|
201
216
|
repository_url=repository_url,
|
|
217
|
+
username=query_username,
|
|
218
|
+
password=query_password,
|
|
202
219
|
)
|
|
203
220
|
else:
|
|
204
221
|
# Workflow 2: main package
|
|
@@ -221,6 +238,8 @@ def main() -> int:
|
|
|
221
238
|
subfolder_path=None,
|
|
222
239
|
repository=repository,
|
|
223
240
|
repository_url=repository_url,
|
|
241
|
+
username=query_username,
|
|
242
|
+
password=query_password,
|
|
224
243
|
)
|
|
225
244
|
|
|
226
245
|
if resolved_version:
|
|
@@ -25,6 +25,8 @@ def query_registry_version(
|
|
|
25
25
|
package_name: str,
|
|
26
26
|
repository: str,
|
|
27
27
|
repository_url: str | None = None,
|
|
28
|
+
username: str | None = None,
|
|
29
|
+
password: str | None = None,
|
|
28
30
|
) -> str | None:
|
|
29
31
|
"""
|
|
30
32
|
Query package registry for the latest published version.
|
|
@@ -33,6 +35,8 @@ def query_registry_version(
|
|
|
33
35
|
package_name: Package name to query
|
|
34
36
|
repository: Repository type ('pypi', 'testpypi', or 'azure')
|
|
35
37
|
repository_url: Repository URL (required for Azure Artifacts)
|
|
38
|
+
username: Optional username for authenticated queries (Azure Artifacts)
|
|
39
|
+
password: Optional password/token for authenticated queries (Azure Artifacts)
|
|
36
40
|
|
|
37
41
|
Returns:
|
|
38
42
|
Latest version string or None if not found/unsupported
|
|
@@ -54,7 +58,7 @@ def query_registry_version(
|
|
|
54
58
|
logger.warning("Azure Artifacts repository URL not provided")
|
|
55
59
|
return None
|
|
56
60
|
logger.info(f"Querying Azure Artifacts for package '{package_name}' at {repository_url}")
|
|
57
|
-
version = _query_azure_artifacts_version(package_name, repository_url)
|
|
61
|
+
version = _query_azure_artifacts_version(package_name, repository_url, username, password)
|
|
58
62
|
if version:
|
|
59
63
|
logger.info(f"Found version {version} on Azure Artifacts")
|
|
60
64
|
else:
|
|
@@ -120,6 +124,7 @@ class SimpleIndexParser(HTMLParser):
|
|
|
120
124
|
self.versions: set[str] = set()
|
|
121
125
|
self.in_anchor = False
|
|
122
126
|
self.current_href = ""
|
|
127
|
+
self.links_processed = 0
|
|
123
128
|
|
|
124
129
|
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
|
125
130
|
if tag == "a":
|
|
@@ -134,17 +139,24 @@ class SimpleIndexParser(HTMLParser):
|
|
|
134
139
|
if self.in_anchor:
|
|
135
140
|
# Extract version from link text or href
|
|
136
141
|
# Format: package-name-version-... or package-name-version.tar.gz
|
|
137
|
-
|
|
138
|
-
if
|
|
139
|
-
|
|
142
|
+
link_text = data.strip()
|
|
143
|
+
if link_text:
|
|
144
|
+
logger.debug(f"Processing link text: '{link_text}'")
|
|
145
|
+
version = self._extract_version_from_filename(link_text)
|
|
146
|
+
if version:
|
|
147
|
+
logger.debug(f"Extracted version '{version}' from link text: '{link_text}'")
|
|
148
|
+
self.versions.add(version)
|
|
140
149
|
# Also check href if it contains version info
|
|
141
150
|
if self.current_href:
|
|
151
|
+
logger.debug(f"Processing href: '{self.current_href}'")
|
|
142
152
|
version = self._extract_version_from_filename(self.current_href)
|
|
143
153
|
if version:
|
|
154
|
+
logger.debug(f"Extracted version '{version}' from href: '{self.current_href}'")
|
|
144
155
|
self.versions.add(version)
|
|
145
156
|
|
|
146
157
|
def handle_endtag(self, tag: str) -> None:
|
|
147
158
|
if tag == "a":
|
|
159
|
+
self.links_processed += 1
|
|
148
160
|
self.in_anchor = False
|
|
149
161
|
self.current_href = ""
|
|
150
162
|
|
|
@@ -176,6 +188,8 @@ class SimpleIndexParser(HTMLParser):
|
|
|
176
188
|
def _query_azure_artifacts_version(
|
|
177
189
|
package_name: str,
|
|
178
190
|
repository_url: str,
|
|
191
|
+
username: str | None = None,
|
|
192
|
+
password: str | None = None,
|
|
179
193
|
) -> str | None:
|
|
180
194
|
"""
|
|
181
195
|
Query Azure Artifacts for the latest version.
|
|
@@ -186,6 +200,8 @@ def _query_azure_artifacts_version(
|
|
|
186
200
|
Args:
|
|
187
201
|
package_name: Package name to query
|
|
188
202
|
repository_url: Azure Artifacts repository URL
|
|
203
|
+
username: Optional username for authentication
|
|
204
|
+
password: Optional password/token for authentication
|
|
189
205
|
|
|
190
206
|
Returns:
|
|
191
207
|
Latest version string or None if not found/unsupported
|
|
@@ -198,60 +214,114 @@ def _query_azure_artifacts_version(
|
|
|
198
214
|
simple_index_url = repository_url.replace("/upload", f"/simple/{package_name}/")
|
|
199
215
|
else:
|
|
200
216
|
simple_index_url = repository_url.rstrip("/") + f"/simple/{package_name}/"
|
|
201
|
-
logger.
|
|
217
|
+
logger.info(f"Constructed Azure Artifacts simple index URL: {simple_index_url}")
|
|
202
218
|
except Exception as e:
|
|
203
219
|
logger.warning(f"Error constructing Azure Artifacts URL for '{package_name}': {e}")
|
|
204
220
|
return None
|
|
205
221
|
|
|
206
222
|
try:
|
|
207
|
-
|
|
208
|
-
|
|
223
|
+
# Prepare authentication if credentials are provided
|
|
224
|
+
auth = None
|
|
225
|
+
if username and password:
|
|
226
|
+
auth = (username, password)
|
|
227
|
+
logger.info(f"Fetching Azure Artifacts simple index for '{package_name}' with authentication...")
|
|
228
|
+
else:
|
|
229
|
+
logger.info(f"Fetching Azure Artifacts simple index for '{package_name}' (no authentication)...")
|
|
230
|
+
|
|
231
|
+
response = requests.get(simple_index_url, auth=auth, timeout=10)
|
|
232
|
+
logger.info(f"Azure Artifacts response: status={response.status_code}, content_length={len(response.text)} bytes")
|
|
209
233
|
|
|
210
234
|
if response.status_code == 401:
|
|
211
|
-
logger.warning(
|
|
235
|
+
logger.warning(
|
|
236
|
+
f"Authentication required for Azure Artifacts (401). "
|
|
237
|
+
f"Package '{package_name}' may require authentication to query. "
|
|
238
|
+
f"URL: {simple_index_url}"
|
|
239
|
+
)
|
|
212
240
|
return None
|
|
213
241
|
elif response.status_code == 403:
|
|
214
|
-
logger.warning(
|
|
242
|
+
logger.warning(
|
|
243
|
+
f"Access forbidden for Azure Artifacts (403). "
|
|
244
|
+
f"Package '{package_name}' may not be accessible or requires different permissions. "
|
|
245
|
+
f"URL: {simple_index_url}"
|
|
246
|
+
)
|
|
215
247
|
return None
|
|
216
248
|
elif response.status_code == 404:
|
|
217
|
-
logger.
|
|
249
|
+
logger.info(
|
|
250
|
+
f"Package '{package_name}' not found on Azure Artifacts (404) - this appears to be the first release. "
|
|
251
|
+
f"URL: {simple_index_url}"
|
|
252
|
+
)
|
|
218
253
|
return None
|
|
219
254
|
elif response.status_code != 200:
|
|
220
|
-
logger.warning(
|
|
255
|
+
logger.warning(
|
|
256
|
+
f"Unexpected status code {response.status_code} from Azure Artifacts for '{package_name}'. "
|
|
257
|
+
f"URL: {simple_index_url}, Response preview: {response.text[:200]}"
|
|
258
|
+
)
|
|
221
259
|
return None
|
|
222
260
|
|
|
223
261
|
# Parse HTML to extract versions
|
|
262
|
+
logger.info(f"Parsing HTML response to extract versions for '{package_name}'...")
|
|
224
263
|
parser = SimpleIndexParser(package_name)
|
|
225
264
|
try:
|
|
226
265
|
parser.feed(response.text)
|
|
266
|
+
logger.info(
|
|
267
|
+
f"HTML parsing completed: processed {parser.links_processed} link(s), "
|
|
268
|
+
f"found {len(parser.versions)} unique version(s)"
|
|
269
|
+
)
|
|
227
270
|
except Exception as e:
|
|
228
|
-
logger.warning(
|
|
271
|
+
logger.warning(
|
|
272
|
+
f"Error parsing Azure Artifacts HTML for '{package_name}': {e}. "
|
|
273
|
+
f"Response length: {len(response.text)} bytes, "
|
|
274
|
+
f"Response preview: {response.text[:500]}"
|
|
275
|
+
)
|
|
229
276
|
return None
|
|
230
277
|
|
|
231
278
|
if not parser.versions:
|
|
232
|
-
|
|
279
|
+
if parser.links_processed == 0:
|
|
280
|
+
logger.info(
|
|
281
|
+
f"No links found in Azure Artifacts HTML for '{package_name}'. "
|
|
282
|
+
f"This may indicate: (1) HTML structure differs from PEP 503 format, "
|
|
283
|
+
f"(2) package doesn't exist, or (3) authentication required. "
|
|
284
|
+
f"Response preview: {response.text[:500]}"
|
|
285
|
+
)
|
|
286
|
+
else:
|
|
287
|
+
logger.info(
|
|
288
|
+
f"Found {parser.links_processed} link(s) but no versions extracted for '{package_name}'. "
|
|
289
|
+
f"This may indicate: (1) package name mismatch (expected '{package_name}'), "
|
|
290
|
+
f"(2) filename format differs from expected pattern, or (3) first release. "
|
|
291
|
+
f"Response preview: {response.text[:500]}"
|
|
292
|
+
)
|
|
233
293
|
return None
|
|
234
294
|
|
|
235
295
|
# Find the latest version
|
|
236
296
|
versions = list(parser.versions)
|
|
237
|
-
logger.
|
|
297
|
+
logger.info(f"Found {len(versions)} version(s) in Azure Artifacts HTML: {versions}")
|
|
238
298
|
|
|
239
299
|
# Sort versions to find the latest
|
|
240
300
|
try:
|
|
241
301
|
sorted_versions = sorted(versions, key=_parse_version_for_sort, reverse=True)
|
|
242
302
|
latest_version = sorted_versions[0]
|
|
243
|
-
logger.info(f"
|
|
303
|
+
logger.info(f"Latest version on Azure Artifacts for '{package_name}': {latest_version}")
|
|
244
304
|
return latest_version
|
|
245
305
|
except Exception as e:
|
|
246
|
-
logger.warning(
|
|
306
|
+
logger.warning(
|
|
307
|
+
f"Error sorting versions for '{package_name}': {e}. "
|
|
308
|
+
f"Versions found: {versions}. Using first version as fallback."
|
|
309
|
+
)
|
|
247
310
|
# Fallback: return the first version found
|
|
248
311
|
return versions[0]
|
|
249
312
|
|
|
250
313
|
except requests.RequestException as e:
|
|
251
|
-
logger.warning(
|
|
314
|
+
logger.warning(
|
|
315
|
+
f"Network error querying Azure Artifacts for '{package_name}': {e}. "
|
|
316
|
+
f"URL: {simple_index_url}"
|
|
317
|
+
)
|
|
252
318
|
return None
|
|
253
319
|
except Exception as e:
|
|
254
|
-
logger.warning(
|
|
320
|
+
logger.warning(
|
|
321
|
+
f"Unexpected error querying Azure Artifacts for '{package_name}': {e}. "
|
|
322
|
+
f"URL: {simple_index_url}",
|
|
323
|
+
exc_info=True
|
|
324
|
+
)
|
|
255
325
|
return None
|
|
256
326
|
|
|
257
327
|
|
|
@@ -573,6 +643,8 @@ def resolve_version(
|
|
|
573
643
|
subfolder_path: Path | None = None,
|
|
574
644
|
repository: str | None = None,
|
|
575
645
|
repository_url: str | None = None,
|
|
646
|
+
username: str | None = None,
|
|
647
|
+
password: str | None = None,
|
|
576
648
|
) -> tuple[str | None, str | None]:
|
|
577
649
|
"""
|
|
578
650
|
Resolve the next version using conventional commits.
|
|
@@ -585,6 +657,8 @@ def resolve_version(
|
|
|
585
657
|
subfolder_path: Optional path to subfolder (relative to project_root)
|
|
586
658
|
repository: Optional target repository ('pypi', 'testpypi', or 'azure')
|
|
587
659
|
repository_url: Optional repository URL (required for Azure Artifacts)
|
|
660
|
+
username: Optional username for authenticated registry queries (Azure Artifacts)
|
|
661
|
+
password: Optional password/token for authenticated registry queries (Azure Artifacts)
|
|
588
662
|
|
|
589
663
|
Returns:
|
|
590
664
|
Tuple of (version string if a release is determined, error message if any)
|
|
@@ -596,7 +670,7 @@ def resolve_version(
|
|
|
596
670
|
baseline_version = None
|
|
597
671
|
if repository and package_name:
|
|
598
672
|
logger.info(f"Attempting to query {repository} for baseline version of '{package_name}'")
|
|
599
|
-
baseline_version = query_registry_version(package_name, repository, repository_url)
|
|
673
|
+
baseline_version = query_registry_version(package_name, repository, repository_url, username, password)
|
|
600
674
|
|
|
601
675
|
# Step 2: Fallback to git tags if registry query failed
|
|
602
676
|
if not baseline_version:
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_version_calculator.py
RENAMED
|
@@ -151,6 +151,39 @@ class TestQueryRegistryVersion:
|
|
|
151
151
|
# Should return None when no versions found in HTML
|
|
152
152
|
assert version is None
|
|
153
153
|
|
|
154
|
+
@patch("python_package_folder.version_calculator.requests.get")
|
|
155
|
+
def test_query_azure_artifacts_version_with_auth(self, mock_get: MagicMock) -> None:
|
|
156
|
+
"""Test querying Azure Artifacts with authentication."""
|
|
157
|
+
mock_response = Mock()
|
|
158
|
+
mock_response.status_code = 200
|
|
159
|
+
mock_response.text = """<!DOCTYPE html>
|
|
160
|
+
<html>
|
|
161
|
+
<head>
|
|
162
|
+
<title>Links for test-package</title>
|
|
163
|
+
</head>
|
|
164
|
+
<body>
|
|
165
|
+
<h1>Links for test-package</h1>
|
|
166
|
+
<a href="test-package-1.0.0-py3-none-any.whl">test-package-1.0.0-py3-none-any.whl</a>
|
|
167
|
+
<a href="test-package-1.1.0-py3-none-any.whl">test-package-1.1.0-py3-none-any.whl</a>
|
|
168
|
+
</body>
|
|
169
|
+
</html>"""
|
|
170
|
+
mock_get.return_value = mock_response
|
|
171
|
+
|
|
172
|
+
version = query_registry_version(
|
|
173
|
+
"test-package",
|
|
174
|
+
"azure",
|
|
175
|
+
repository_url="https://pkgs.dev.azure.com/ORG/PROJECT/_packaging/FEED/pypi/upload",
|
|
176
|
+
username="testuser",
|
|
177
|
+
password="testtoken",
|
|
178
|
+
)
|
|
179
|
+
# Should parse HTML and return the latest version
|
|
180
|
+
assert version == "1.1.0"
|
|
181
|
+
|
|
182
|
+
# Verify authentication was used
|
|
183
|
+
mock_get.assert_called_once()
|
|
184
|
+
call_args = mock_get.call_args
|
|
185
|
+
assert call_args[1]["auth"] == ("testuser", "testtoken")
|
|
186
|
+
|
|
154
187
|
@patch("python_package_folder.version_calculator.requests.get")
|
|
155
188
|
def test_query_registry_version_error_handling(self, mock_get: MagicMock) -> None:
|
|
156
189
|
"""Test that registry query errors are handled gracefully."""
|
|
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.2.0 → python_package_folder-5.2.2}/src/python_package_folder/__init__.py
RENAMED
|
File without changes
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/__main__.py
RENAMED
|
File without changes
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/analyzer.py
RENAMED
|
File without changes
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/finder.py
RENAMED
|
File without changes
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/manager.py
RENAMED
|
File without changes
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/types.py
RENAMED
|
File without changes
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/utils.py
RENAMED
|
File without changes
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/src/python_package_folder/version.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/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.2.0 → python_package_folder-5.2.2}/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
|
|
File without changes
|
{python_package_folder-5.2.0 → python_package_folder-5.2.2}/tests/test_third_party_dependencies.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|