planetarypy 0.53.2__tar.gz → 0.53.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 (53) hide show
  1. {planetarypy-0.53.2 → planetarypy-0.53.4}/CHANGELOG.md +13 -0
  2. {planetarypy-0.53.2 → planetarypy-0.53.4}/PKG-INFO +1 -1
  3. {planetarypy-0.53.2 → planetarypy-0.53.4}/pyproject.toml +2 -2
  4. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/cli.py +12 -4
  5. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/utils.py +18 -2
  6. {planetarypy-0.53.2 → planetarypy-0.53.4}/.gitignore +0 -0
  7. {planetarypy-0.53.2 → planetarypy-0.53.4}/AUTHORS.md +0 -0
  8. {planetarypy-0.53.2 → planetarypy-0.53.4}/LICENSE +0 -0
  9. {planetarypy-0.53.2 → planetarypy-0.53.4}/README.md +0 -0
  10. {planetarypy-0.53.2 → planetarypy-0.53.4}/docs/README.md +0 -0
  11. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/__init__.py +0 -0
  12. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/catalog/__init__.py +0 -0
  13. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/catalog/_index_resolver.py +0 -0
  14. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/catalog/_mission_map.py +0 -0
  15. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/catalog/_objects.py +0 -0
  16. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/catalog/_parser.py +0 -0
  17. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/catalog/_pattern_resolver.py +0 -0
  18. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/catalog/_repo.py +0 -0
  19. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/catalog/_resolver.py +0 -0
  20. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/catalog/_schema.py +0 -0
  21. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/catalog/_url_rewrite.py +0 -0
  22. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/catalog/_validation.py +0 -0
  23. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/config.py +0 -0
  24. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/data/__init__.py +0 -0
  25. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/datetime_format_converters.py +0 -0
  26. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/geo.py +0 -0
  27. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/instruments/__init__.py +0 -0
  28. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/instruments/go/__init__.py +0 -0
  29. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/instruments/go/ssi.py +0 -0
  30. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/instruments/mro/__init__.py +0 -0
  31. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/instruments/mro/ctx/__init__.py +0 -0
  32. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/instruments/mro/ctx/ctx_calib.py +0 -0
  33. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/instruments/mro/ctx/ctx_edr.py +0 -0
  34. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/instruments/mro/hirise.py +0 -0
  35. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/instruments/utils.py +0 -0
  36. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/isis/autoseed.py +0 -0
  37. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/isis/projected.py +0 -0
  38. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/pds/__init__.py +0 -0
  39. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/pds/dynamic_index.py +0 -0
  40. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/pds/dynamic_url_handlers.py +0 -0
  41. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/pds/index_fixes.py +0 -0
  42. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/pds/index_labels.py +0 -0
  43. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/pds/index_logging.py +0 -0
  44. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/pds/index_main.py +0 -0
  45. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/pds/static_index.py +0 -0
  46. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/pds/utils.py +0 -0
  47. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/plotting.py +0 -0
  48. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/spice/__init__.py +0 -0
  49. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/spice/archived_kernels.py +0 -0
  50. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/spice/config.py +0 -0
  51. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/spice/generic_kernels.py +0 -0
  52. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/spice/pckernels.py +0 -0
  53. {planetarypy-0.53.2 → planetarypy-0.53.4}/src/planetarypy/spice/spicer.py +0 -0
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.53.4] - 2026-04-24
9
+
10
+ ### Fixed
11
+ - **`url_retrieve` is now concurrency-safe.** Previously two processes (e.g. parallel pytest-xdist workers hitting `load_generic_kernels()` via `Spicer("MARS")` / `Spicer("MOON")`) could clobber each other's `.part` scratch file and race on the final `rename()`, producing `FileNotFoundError`. The scratch file now includes the writer's PID (`{name}.{pid}.part`), and when a concurrent winner has already moved the final file into place the loser silently drops its scratch copy instead of raising. This was the root cause of intermittent CI failures in `test_spicer`.
12
+
13
+ ### Changed
14
+ - CI workflow `test.yaml` now prefetches the SPICE generic kernels in a single-writer step before invoking `pytest`, so parallel test workers see cached files and never trigger the download path simultaneously. Complements the `url_retrieve` fix; either alone would green CI, both together harden the library for any concurrent caller.
15
+
16
+ ## [0.53.3] - 2026-04-24
17
+
18
+ ### Fixed
19
+ - **`plp fetch` and `plp hibrowse` now emit only the resolved file path on stdout.** Diagnostic lines ("Resolving…", "URL:…", "Fetching…") and the "Browse:" prefix previously mixed with the final path on stdout, which made shell command substitution clumsy (e.g. `qgis (plp fetch mro.ctx.edr <pid>)` captured all of it as arguments). Diagnostics now go to stderr; only the payload path hits stdout.
20
+
8
21
  ## [0.53.2] - 2026-04-24
9
22
 
10
23
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: planetarypy
3
- Version: 0.53.2
3
+ Version: 0.53.4
4
4
  Summary: Core package for planetary data tools. Includes PDS index utilities, SPICE integrations, and more.
5
5
  Project-URL: Homepage, https://github.com/planetarypy/planetarypy
6
6
  Project-URL: Repository, https://github.com/planetarypy/planetarypy
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "planetarypy"
7
- version = "0.53.2"
7
+ version = "0.53.4"
8
8
  description = "Core package for planetary data tools. Includes PDS index utilities, SPICE integrations, and more."
9
9
  readme = "README.md"
10
10
  requires-python = ">= 3.11, <4"
@@ -127,6 +127,6 @@ max-line-length = 88
127
127
  extend-ignore = ["E203", "E701"]
128
128
 
129
129
  [tool.bumpversion]
130
- current_version = "0.53.2"
130
+ current_version = "0.53.4"
131
131
  commit = true
132
132
  tag = true
@@ -64,11 +64,11 @@ def fetch(
64
64
 
65
65
  mission, instrument, product_key = key.split(".")
66
66
 
67
- typer.echo(f"Resolving {key} / {product_id}...")
67
+ typer.echo(f"Resolving {key} / {product_id}...", err=True)
68
68
  try:
69
69
  resolved = resolve_product(mission, instrument, product_key, product_id)
70
70
  for f in resolved.files:
71
- typer.echo(f"URL: {resolved.url_stem}/{f}")
71
+ typer.echo(f"URL: {resolved.url_stem}/{f}", err=True)
72
72
  if here:
73
73
  local_dir = Path.cwd()
74
74
  else:
@@ -76,6 +76,9 @@ def fetch(
76
76
  mission, instrument, product_key, resolved.product_id,
77
77
  )
78
78
  download_product(resolved, local_dir, label_only=label_only, force=force)
79
+ # Final resolved paths go to stdout (and only stdout) so that
80
+ # shell command substitution — e.g. `qgis (plp fetch …)` —
81
+ # captures just the file paths, not the diagnostic chatter.
79
82
  for f in resolved.files:
80
83
  typer.echo(local_dir / f)
81
84
  except Exception as e:
@@ -127,12 +130,17 @@ def hibrowse(
127
130
  obs_id = f"{parts[0]}_{parts[1]}_{parts[2]}"
128
131
  orbit = int(parts[1])
129
132
  suffix = "abrowse.jpg" if annotated else "browse.jpg"
130
- typer.echo(f"Fetching {HIRISE_BASE}/EXTRAS/{data_level}/{parts[0]}/{_orbit_range(orbit)}/{obs_id}/{pid}.{suffix}")
133
+ typer.echo(
134
+ f"Fetching {HIRISE_BASE}/EXTRAS/{data_level}/{parts[0]}/{_orbit_range(orbit)}/{obs_id}/{pid}.{suffix}",
135
+ err=True,
136
+ )
131
137
 
132
138
  try:
133
139
  dest = Path.cwd() if here else None
134
140
  outpath = get_browse(product_id, annotated=annotated, dest=dest, force=force)
135
- typer.echo(f"Browse: {outpath}")
141
+ # Raw path on stdout so `qgis (plp hibrowse …)` and similar
142
+ # shell substitutions capture just the path.
143
+ typer.echo(outpath)
136
144
  except Exception as e:
137
145
  typer.echo(f"Error: {e}", err=True)
138
146
  raise typer.Exit(1)
@@ -2,6 +2,7 @@
2
2
  import datetime as dt
3
3
  import email.utils as eut
4
4
  import http.client as httplib
5
+ import os
5
6
  from pathlib import Path
6
7
  from typing import Any
7
8
  from urllib.request import Request, urlopen
@@ -276,7 +277,10 @@ def url_retrieve(
276
277
  """
277
278
  url = str(url)
278
279
  outfile = Path(outfile)
279
- part_file = outfile.with_suffix(outfile.suffix + ".part")
280
+ # Per-PID scratch file so concurrent callers (e.g. parallel pytest
281
+ # workers, multiprocessing) don't clobber each other's partial
282
+ # writes and don't race on the final rename.
283
+ part_file = outfile.with_suffix(f"{outfile.suffix}.{os.getpid()}.part")
280
284
 
281
285
  if user:
282
286
  auth = HTTPBasicAuth(user, passwd)
@@ -301,7 +305,19 @@ def url_retrieve(
301
305
  ) as fd:
302
306
  for chunk in R.iter_content(chunk_size=chunk_size):
303
307
  fd.write(chunk)
304
- part_file.rename(outfile)
308
+ # If another concurrent writer already finished first, drop our
309
+ # scratch file rather than overwriting the winner.
310
+ if outfile.exists():
311
+ part_file.unlink(missing_ok=True)
312
+ else:
313
+ try:
314
+ part_file.replace(outfile)
315
+ except FileNotFoundError:
316
+ # Lost a very tight race: another writer moved our part
317
+ # file out from under us (unlikely with per-PID suffix).
318
+ # If outfile now exists we're still fine.
319
+ if not outfile.exists():
320
+ raise
305
321
 
306
322
 
307
323
  def have_internet() -> bool:
File without changes
File without changes
File without changes
File without changes