python-package-folder 5.2.0__tar.gz → 5.2.1__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.0 → python_package_folder-5.2.1}/PKG-INFO +1 -1
  2. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/pyproject.toml +1 -1
  3. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/publisher.py +50 -2
  4. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/version_calculator.py +71 -16
  5. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/.copier-answers.yml +0 -0
  6. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
  7. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
  8. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/.cursor/rules/general.mdc +0 -0
  9. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/.cursor/rules/python.mdc +0 -0
  10. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/.github/workflows/ci.yml +0 -0
  11. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/.github/workflows/publish.yml +0 -0
  12. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/.gitignore +0 -0
  13. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/.vscode/settings.json +0 -0
  14. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/LICENSE +0 -0
  15. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/MANIFEST.in +0 -0
  16. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/Makefile +0 -0
  17. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/README.md +0 -0
  18. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/coverage.svg +0 -0
  19. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/development.md +0 -0
  20. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/docs/DEVELOPMENT.md +0 -0
  21. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/docs/INSTALLATION.md +0 -0
  22. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/docs/PUBLISHING.md +0 -0
  23. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/docs/REFERENCE.md +0 -0
  24. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/docs/USAGE.md +0 -0
  25. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/docs/VERSION_RESOLUTION.md +0 -0
  26. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/installation.md +0 -0
  27. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/publishing.md +0 -0
  28. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/__init__.py +0 -0
  29. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/__main__.py +0 -0
  30. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/analyzer.py +0 -0
  31. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/finder.py +0 -0
  32. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/manager.py +0 -0
  33. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/py.typed +0 -0
  34. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/python_package_folder.py +0 -0
  35. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/subfolder_build.py +0 -0
  36. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/types.py +0 -0
  37. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/utils.py +0 -0
  38. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/src/python_package_folder/version.py +0 -0
  39. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/conftest.py +0 -0
  40. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/folder_structure/some_globals.py +0 -0
  41. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  42. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
  43. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  44. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
  45. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  46. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  47. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_build_with_external_deps.py +0 -0
  48. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_exclude_patterns.py +0 -0
  49. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_linting.py +0 -0
  50. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_preserve_directory_structure.py +0 -0
  51. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_publisher.py +0 -0
  52. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_shared_subdirectory_imports.py +0 -0
  53. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_spreadsheet_creation_imports.py +0 -0
  54. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_subfolder_build.py +0 -0
  55. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_third_party_dependencies.py +0 -0
  56. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_utils.py +0 -0
  57. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_version_calculator.py +0 -0
  58. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/test_version_manager.py +0 -0
  59. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/tests/tests.py +0 -0
  60. {python_package_folder-5.2.0 → python_package_folder-5.2.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 5.2.0
3
+ Version: 5.2.1
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.0"
46
+ version = "5.2.1"
47
47
  [dependency-groups]
48
48
  dev = [
49
49
  "pytest>=8.3.5",
@@ -310,11 +310,59 @@ class Publisher:
310
310
  print(f"Files to upload: {len(dist_files)}")
311
311
 
312
312
  try:
313
- subprocess.run(cmd, check=True, text=True)
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
- print(f"Error: {e}", file=sys.stderr)
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:
@@ -120,6 +120,7 @@ class SimpleIndexParser(HTMLParser):
120
120
  self.versions: set[str] = set()
121
121
  self.in_anchor = False
122
122
  self.current_href = ""
123
+ self.links_processed = 0
123
124
 
124
125
  def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
125
126
  if tag == "a":
@@ -134,17 +135,24 @@ class SimpleIndexParser(HTMLParser):
134
135
  if self.in_anchor:
135
136
  # Extract version from link text or href
136
137
  # Format: package-name-version-... or package-name-version.tar.gz
137
- version = self._extract_version_from_filename(data.strip())
138
- if version:
139
- self.versions.add(version)
138
+ link_text = data.strip()
139
+ if link_text:
140
+ logger.debug(f"Processing link text: '{link_text}'")
141
+ version = self._extract_version_from_filename(link_text)
142
+ if version:
143
+ logger.debug(f"Extracted version '{version}' from link text: '{link_text}'")
144
+ self.versions.add(version)
140
145
  # Also check href if it contains version info
141
146
  if self.current_href:
147
+ logger.debug(f"Processing href: '{self.current_href}'")
142
148
  version = self._extract_version_from_filename(self.current_href)
143
149
  if version:
150
+ logger.debug(f"Extracted version '{version}' from href: '{self.current_href}'")
144
151
  self.versions.add(version)
145
152
 
146
153
  def handle_endtag(self, tag: str) -> None:
147
154
  if tag == "a":
155
+ self.links_processed += 1
148
156
  self.in_anchor = False
149
157
  self.current_href = ""
150
158
 
@@ -198,60 +206,107 @@ def _query_azure_artifacts_version(
198
206
  simple_index_url = repository_url.replace("/upload", f"/simple/{package_name}/")
199
207
  else:
200
208
  simple_index_url = repository_url.rstrip("/") + f"/simple/{package_name}/"
201
- logger.debug(f"Constructed Azure Artifacts simple index URL: {simple_index_url}")
209
+ logger.info(f"Constructed Azure Artifacts simple index URL: {simple_index_url}")
202
210
  except Exception as e:
203
211
  logger.warning(f"Error constructing Azure Artifacts URL for '{package_name}': {e}")
204
212
  return None
205
213
 
206
214
  try:
215
+ logger.info(f"Fetching Azure Artifacts simple index for '{package_name}'...")
207
216
  response = requests.get(simple_index_url, timeout=10)
208
- logger.debug(f"Azure Artifacts response status: {response.status_code}")
217
+ logger.info(f"Azure Artifacts response: status={response.status_code}, content_length={len(response.text)} bytes")
209
218
 
210
219
  if response.status_code == 401:
211
- logger.warning(f"Authentication required for Azure Artifacts (401). Package '{package_name}' may require authentication to query.")
220
+ logger.warning(
221
+ f"Authentication required for Azure Artifacts (401). "
222
+ f"Package '{package_name}' may require authentication to query. "
223
+ f"URL: {simple_index_url}"
224
+ )
212
225
  return None
213
226
  elif response.status_code == 403:
214
- logger.warning(f"Access forbidden for Azure Artifacts (403). Package '{package_name}' may not be accessible or requires different permissions.")
227
+ logger.warning(
228
+ f"Access forbidden for Azure Artifacts (403). "
229
+ f"Package '{package_name}' may not be accessible or requires different permissions. "
230
+ f"URL: {simple_index_url}"
231
+ )
215
232
  return None
216
233
  elif response.status_code == 404:
217
- logger.debug(f"Package '{package_name}' not found on Azure Artifacts (404) - first release")
234
+ logger.info(
235
+ f"Package '{package_name}' not found on Azure Artifacts (404) - this appears to be the first release. "
236
+ f"URL: {simple_index_url}"
237
+ )
218
238
  return None
219
239
  elif response.status_code != 200:
220
- logger.warning(f"Unexpected status code {response.status_code} from Azure Artifacts for '{package_name}'")
240
+ logger.warning(
241
+ f"Unexpected status code {response.status_code} from Azure Artifacts for '{package_name}'. "
242
+ f"URL: {simple_index_url}, Response preview: {response.text[:200]}"
243
+ )
221
244
  return None
222
245
 
223
246
  # Parse HTML to extract versions
247
+ logger.info(f"Parsing HTML response to extract versions for '{package_name}'...")
224
248
  parser = SimpleIndexParser(package_name)
225
249
  try:
226
250
  parser.feed(response.text)
251
+ logger.info(
252
+ f"HTML parsing completed: processed {parser.links_processed} link(s), "
253
+ f"found {len(parser.versions)} unique version(s)"
254
+ )
227
255
  except Exception as e:
228
- logger.warning(f"Error parsing Azure Artifacts HTML for '{package_name}': {e}")
256
+ logger.warning(
257
+ f"Error parsing Azure Artifacts HTML for '{package_name}': {e}. "
258
+ f"Response length: {len(response.text)} bytes, "
259
+ f"Response preview: {response.text[:500]}"
260
+ )
229
261
  return None
230
262
 
231
263
  if not parser.versions:
232
- logger.debug(f"No versions found in Azure Artifacts HTML for '{package_name}'")
264
+ if parser.links_processed == 0:
265
+ logger.info(
266
+ f"No links found in Azure Artifacts HTML for '{package_name}'. "
267
+ f"This may indicate: (1) HTML structure differs from PEP 503 format, "
268
+ f"(2) package doesn't exist, or (3) authentication required. "
269
+ f"Response preview: {response.text[:500]}"
270
+ )
271
+ else:
272
+ logger.info(
273
+ f"Found {parser.links_processed} link(s) but no versions extracted for '{package_name}'. "
274
+ f"This may indicate: (1) package name mismatch (expected '{package_name}'), "
275
+ f"(2) filename format differs from expected pattern, or (3) first release. "
276
+ f"Response preview: {response.text[:500]}"
277
+ )
233
278
  return None
234
279
 
235
280
  # Find the latest version
236
281
  versions = list(parser.versions)
237
- logger.debug(f"Found {len(versions)} versions in Azure Artifacts: {versions}")
282
+ logger.info(f"Found {len(versions)} version(s) in Azure Artifacts HTML: {versions}")
238
283
 
239
284
  # Sort versions to find the latest
240
285
  try:
241
286
  sorted_versions = sorted(versions, key=_parse_version_for_sort, reverse=True)
242
287
  latest_version = sorted_versions[0]
243
- logger.info(f"Found latest version {latest_version} on Azure Artifacts for '{package_name}'")
288
+ logger.info(f"Latest version on Azure Artifacts for '{package_name}': {latest_version}")
244
289
  return latest_version
245
290
  except Exception as e:
246
- logger.warning(f"Error sorting versions for '{package_name}': {e}")
291
+ logger.warning(
292
+ f"Error sorting versions for '{package_name}': {e}. "
293
+ f"Versions found: {versions}. Using first version as fallback."
294
+ )
247
295
  # Fallback: return the first version found
248
296
  return versions[0]
249
297
 
250
298
  except requests.RequestException as e:
251
- logger.warning(f"Network error querying Azure Artifacts for '{package_name}': {e}")
299
+ logger.warning(
300
+ f"Network error querying Azure Artifacts for '{package_name}': {e}. "
301
+ f"URL: {simple_index_url}"
302
+ )
252
303
  return None
253
304
  except Exception as e:
254
- logger.warning(f"Unexpected error querying Azure Artifacts for '{package_name}': {e}", exc_info=True)
305
+ logger.warning(
306
+ f"Unexpected error querying Azure Artifacts for '{package_name}': {e}. "
307
+ f"URL: {simple_index_url}",
308
+ exc_info=True
309
+ )
255
310
  return None
256
311
 
257
312