python-package-folder 5.2.3__tar.gz → 5.2.4__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.
Files changed (60) hide show
  1. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/PKG-INFO +1 -1
  2. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/pyproject.toml +1 -1
  3. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/version_calculator.py +215 -0
  4. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/.copier-answers.yml +0 -0
  5. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
  6. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
  7. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/.cursor/rules/general.mdc +0 -0
  8. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/.cursor/rules/python.mdc +0 -0
  9. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/.github/workflows/ci.yml +0 -0
  10. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/.github/workflows/publish.yml +0 -0
  11. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/.gitignore +0 -0
  12. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/.vscode/settings.json +0 -0
  13. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/LICENSE +0 -0
  14. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/MANIFEST.in +0 -0
  15. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/Makefile +0 -0
  16. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/README.md +0 -0
  17. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/coverage.svg +0 -0
  18. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/development.md +0 -0
  19. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/docs/DEVELOPMENT.md +0 -0
  20. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/docs/INSTALLATION.md +0 -0
  21. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/docs/PUBLISHING.md +0 -0
  22. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/docs/REFERENCE.md +0 -0
  23. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/docs/USAGE.md +0 -0
  24. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/docs/VERSION_RESOLUTION.md +0 -0
  25. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/installation.md +0 -0
  26. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/publishing.md +0 -0
  27. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/__init__.py +0 -0
  28. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/__main__.py +0 -0
  29. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/analyzer.py +0 -0
  30. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/finder.py +0 -0
  31. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/manager.py +0 -0
  32. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/publisher.py +0 -0
  33. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/py.typed +0 -0
  34. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/python_package_folder.py +0 -0
  35. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/subfolder_build.py +0 -0
  36. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/types.py +0 -0
  37. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/utils.py +0 -0
  38. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/src/python_package_folder/version.py +0 -0
  39. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/conftest.py +0 -0
  40. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/folder_structure/some_globals.py +0 -0
  41. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  42. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
  43. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  44. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
  45. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  46. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  47. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_build_with_external_deps.py +0 -0
  48. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_exclude_patterns.py +0 -0
  49. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_linting.py +0 -0
  50. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_preserve_directory_structure.py +0 -0
  51. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_publisher.py +0 -0
  52. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_shared_subdirectory_imports.py +0 -0
  53. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_spreadsheet_creation_imports.py +0 -0
  54. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_subfolder_build.py +0 -0
  55. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_third_party_dependencies.py +0 -0
  56. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_utils.py +0 -0
  57. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_version_calculator.py +0 -0
  58. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/test_version_manager.py +0 -0
  59. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/tests/tests.py +0 -0
  60. {python_package_folder-5.2.3 → python_package_folder-5.2.4}/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
3
+ Version: 5.2.4
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>
@@ -43,7 +43,7 @@ dependencies = [
43
43
 
44
44
  # ---- Dev dependencies ----
45
45
 
46
- version = "5.2.3"
46
+ version = "5.2.4"
47
47
  [dependency-groups]
48
48
  dev = [
49
49
  "pytest>=8.3.5",
@@ -13,6 +13,8 @@ from __future__ import annotations
13
13
  import logging
14
14
  import re
15
15
  import subprocess
16
+ import sys
17
+ import tempfile
16
18
  from html.parser import HTMLParser
17
19
  from pathlib import Path
18
20
 
@@ -185,6 +187,175 @@ class SimpleIndexParser(HTMLParser):
185
187
  return None
186
188
 
187
189
 
190
+ def _query_azure_artifacts_version_via_pip_index(
191
+ package_name: str,
192
+ repository_url: str,
193
+ username: str | None = None,
194
+ password: str | None = None,
195
+ ) -> str | None:
196
+ """
197
+ Query Azure Artifacts for latest version using 'pip index versions'.
198
+
199
+ This method uses pip's built-in index querying, which uses the same
200
+ authentication mechanism as pip install/publish.
201
+
202
+ Args:
203
+ package_name: Package name to query
204
+ repository_url: Azure Artifacts repository URL
205
+ username: Optional username for authentication
206
+ password: Optional password/token for authentication
207
+
208
+ Returns:
209
+ Latest version string or None if not found/unsupported
210
+ """
211
+ # Build pip index URL (remove /upload suffix if present)
212
+ index_url = repository_url.replace("/upload", "/simple")
213
+
214
+ logger.info(f"Querying Azure Artifacts via 'pip index versions' for '{package_name}'...")
215
+
216
+ # Build pip command
217
+ cmd = ["pip", "index", "versions", package_name, "--index-url", index_url]
218
+
219
+ # Add authentication if provided
220
+ # pip supports credentials in URL format: https://user:pass@host/path
221
+ if username and password:
222
+ auth_url = index_url.replace("https://", f"https://{username}:{password}@")
223
+ cmd[cmd.index("--index-url") + 1] = auth_url
224
+
225
+ try:
226
+ result = subprocess.run(
227
+ cmd,
228
+ capture_output=True,
229
+ text=True,
230
+ timeout=30,
231
+ )
232
+
233
+ if result.returncode == 0:
234
+ # Parse output: "Package 'data' versions: 0.1.0, 0.2.0, 0.3.0"
235
+ # Or: "data (0.1.0, 0.2.0, 0.3.0)"
236
+ match = re.search(r"versions?:\s*(.+)", result.stdout, re.IGNORECASE)
237
+ if not match:
238
+ # Try alternative format: "package-name (version1, version2, ...)"
239
+ match = re.search(rf"{re.escape(package_name)}\s*\(([^)]+)\)", result.stdout)
240
+
241
+ if match:
242
+ versions_str = match.group(1).strip()
243
+ # Split by comma and clean up
244
+ versions = [v.strip().strip("'\"") for v in versions_str.split(",")]
245
+ if versions:
246
+ # Sort versions to get the latest
247
+ try:
248
+ sorted_versions = sorted(versions, key=_parse_version_for_sort, reverse=True)
249
+ latest_version = sorted_versions[0]
250
+ logger.info(f"Found latest version via pip index: {latest_version}")
251
+ return latest_version
252
+ except Exception as e:
253
+ logger.warning(f"Error sorting versions from pip index: {e}. Using first version.")
254
+ return versions[-1] # Return last one as fallback
255
+
256
+ # Check if error indicates package doesn't exist
257
+ if "not found" in result.stderr.lower() or "no such package" in result.stderr.lower():
258
+ logger.info(f"Package '{package_name}' not found via pip index (first release)")
259
+ else:
260
+ logger.debug(f"pip index versions output: stdout={result.stdout}, stderr={result.stderr}")
261
+
262
+ return None
263
+ except subprocess.TimeoutExpired:
264
+ logger.warning(f"Timeout querying Azure Artifacts via pip index for '{package_name}'")
265
+ return None
266
+ except FileNotFoundError:
267
+ # pip command not found or pip index not available (pip < 21.2)
268
+ logger.debug("'pip index versions' not available, will try alternative method")
269
+ return None
270
+ except Exception as e:
271
+ logger.warning(f"Error querying Azure Artifacts via pip index for '{package_name}': {e}")
272
+ return None
273
+
274
+
275
+ def _query_azure_artifacts_version_via_pip_install(
276
+ package_name: str,
277
+ repository_url: str,
278
+ username: str | None = None,
279
+ password: str | None = None,
280
+ ) -> str | None:
281
+ """
282
+ Query Azure Artifacts by attempting to install the latest version
283
+ using 'pip install --dry-run', then extracting the version.
284
+
285
+ This is a fallback method when 'pip index versions' is not available.
286
+
287
+ Args:
288
+ package_name: Package name to query
289
+ repository_url: Azure Artifacts repository URL
290
+ username: Optional username for authentication
291
+ password: Optional password/token for authentication
292
+
293
+ Returns:
294
+ Latest version string or None if not found/unsupported
295
+ """
296
+ # Build index URL
297
+ index_url = repository_url.replace("/upload", "/simple")
298
+
299
+ logger.info(f"Querying Azure Artifacts via 'pip install --dry-run' for '{package_name}'...")
300
+
301
+ # Build pip command
302
+ cmd = [
303
+ sys.executable,
304
+ "-m",
305
+ "pip",
306
+ "install",
307
+ "--index-url",
308
+ index_url,
309
+ "--no-deps", # Don't install dependencies
310
+ "--dry-run", # Don't actually install
311
+ package_name,
312
+ ]
313
+
314
+ # Add authentication if provided
315
+ if username and password:
316
+ auth_url = index_url.replace("https://", f"https://{username}:{password}@")
317
+ cmd[cmd.index("--index-url") + 1] = auth_url
318
+
319
+ try:
320
+ result = subprocess.run(
321
+ cmd,
322
+ capture_output=True,
323
+ text=True,
324
+ timeout=60,
325
+ )
326
+
327
+ if result.returncode == 0:
328
+ # Parse output to find version
329
+ # pip shows: "Would install data-0.3.0" or "Collecting data==0.3.0"
330
+ # Try multiple patterns
331
+ patterns = [
332
+ rf"Would install\s+{re.escape(package_name)}-([\d.]+(?:[a-zA-Z0-9]+)?)",
333
+ rf"Collecting\s+{re.escape(package_name)}==([\d.]+(?:[a-zA-Z0-9]+)?)",
334
+ rf"Downloading\s+{re.escape(package_name)}-([\d.]+(?:[a-zA-Z0-9]+)?)",
335
+ ]
336
+
337
+ for pattern in patterns:
338
+ match = re.search(pattern, result.stdout, re.IGNORECASE)
339
+ if match:
340
+ version = match.group(1)
341
+ logger.info(f"Found version via pip install --dry-run: {version}")
342
+ return version
343
+
344
+ # Check if error indicates package doesn't exist
345
+ if "not found" in result.stderr.lower() or "no matching distribution" in result.stderr.lower():
346
+ logger.info(f"Package '{package_name}' not found via pip install (first release)")
347
+ else:
348
+ logger.debug(f"pip install --dry-run output: stdout={result.stdout[:500]}, stderr={result.stderr[:500]}")
349
+
350
+ return None
351
+ except subprocess.TimeoutExpired:
352
+ logger.warning(f"Timeout querying Azure Artifacts via pip install for '{package_name}'")
353
+ return None
354
+ except Exception as e:
355
+ logger.warning(f"Error querying Azure Artifacts via pip install for '{package_name}': {e}")
356
+ return None
357
+
358
+
188
359
  def _query_azure_artifacts_version(
189
360
  package_name: str,
190
361
  repository_url: str,
@@ -194,6 +365,50 @@ def _query_azure_artifacts_version(
194
365
  """
195
366
  Query Azure Artifacts for the latest version.
196
367
 
368
+ Tries multiple methods in order:
369
+ 1. pip index versions (fastest, uses same auth as pip install)
370
+ 2. pip install --dry-run (fallback if pip index not available)
371
+ 3. HTML parsing of simple index (last resort)
372
+
373
+ Args:
374
+ package_name: Package name to query
375
+ repository_url: Azure Artifacts repository URL
376
+ username: Optional username for authentication
377
+ password: Optional password/token for authentication
378
+
379
+ Returns:
380
+ Latest version string or None if not found/unsupported
381
+ """
382
+ # Method 1: Try pip index versions first (fastest, uses same auth as publishing)
383
+ version = _query_azure_artifacts_version_via_pip_index(
384
+ package_name, repository_url, username, password
385
+ )
386
+ if version:
387
+ return version
388
+
389
+ # Method 2: Fallback to pip install --dry-run
390
+ version = _query_azure_artifacts_version_via_pip_install(
391
+ package_name, repository_url, username, password
392
+ )
393
+ if version:
394
+ return version
395
+
396
+ # Method 3: Last resort - HTML parsing (original method)
397
+ logger.info(f"Falling back to HTML parsing for '{package_name}'...")
398
+ return _query_azure_artifacts_version_via_html(
399
+ package_name, repository_url, username, password
400
+ )
401
+
402
+
403
+ def _query_azure_artifacts_version_via_html(
404
+ package_name: str,
405
+ repository_url: str,
406
+ username: str | None = None,
407
+ password: str | None = None,
408
+ ) -> str | None:
409
+ """
410
+ Query Azure Artifacts for the latest version via HTML parsing.
411
+
197
412
  Azure Artifacts uses a simple index format (HTML) following PEP 503.
198
413
  Parses the HTML to extract version numbers from package filenames.
199
414