Skip to main content

No project description provided

Project description

Quick Spice Manager

PyPI PyPI - Python Version PyPI - License Coookiecutter - Wolt


Documentation: https://luca-penasa.github.io/quick-spice-manager

Source Code: https://github.com/luca-penasa/quick-spice-manager

PyPI: https://pypi.org/project/quick-spice-manager/


quick-spice-manager provides a straightforward way to download, cache, and load ESA SPICE kernels. The core of the library is an FTP-based download engine that fetches kernels directly from the ESA public FTP server (spiftp.esac.esa.int) with parallel transfers and progress reporting. QuickSpiceManager handles metakernel resolution, local caching, pool management, and environment-based overrides — so you can get a loaded kernel set with minimal boilerplate. Optional integration with planetary-coverage is available as an extras install.

Supported missions include JUICE, SOLAR-ORBITER, BEPICOLOMBO, MARS-EXPRESS, ROSETTA, and many more.

Installation

pip install quick-spice-manager

Note: The SpiceManager name is a deprecated alias for QuickSpiceManager and will be removed in a future release. Use QuickSpiceManager in new code.

Usage

Basic usage

from quick_spice_manager import QuickSpiceManager

# Downloads kernels automatically from the ESA FTP server and loads them.
# Kernels are cached in the platform user-cache directory and reused on
# subsequent calls — no re-download unless the cache is cleared.
sm = QuickSpiceManager(spacecraft="JUICE", mk="plan")
sm.load_kernels()

# ... use spiceypy directly ...

sm.unload_kernels()

Getting a furnshable metakernel path

resolved_mk triggers FTP download and PATH_VALUES rewriting without loading anything into the SPICE pool, giving you a ready-to-use metakernel path:

sm = QuickSpiceManager(spacecraft="JUICE", mk="plan")

import spiceypy
spiceypy.furnsh(str(sm.resolved_mk))  # pool management is yours

The result is cached — repeated access to sm.resolved_mk does not re-download or recreate the temp file.

Context manager

The preferred approach — kernels are loaded on entry and the original kernel pool is automatically restored on exit:

with QuickSpiceManager(spacecraft="JUICE", mk="plan") as sm:
    # kernels are loaded here; original pool is restored on exit
    ...

By default exclusive=True, which clears the entire SPICE pool before loading so that only the kernels from the chosen metakernel are active. Set exclusive=False to keep any previously loaded kernels alongside the new ones:

with QuickSpiceManager(spacecraft="JUICE", mk="plan", exclusive=False) as sm:
    ...

Listing available metakernels

sm = QuickSpiceManager(spacecraft="JUICE")
print(sm.metakernels)  # e.g. ['juice_plan', 'juice_plan_v462_20260223_001', ...]

Pinning a specific SKD version

By default version="latest" fetches the current unversioned metakernel and re-downloads it on each fresh resolution so you always get the latest kernel list. To pin a reproducible snapshot, pass the exact version tag from the FTP server:

sm = QuickSpiceManager(spacecraft="JUICE", mk="plan", version="v462_20260223_001")
sm.load_kernels()

The version tag is the suffix that appears in the versioned filenames listed by sm.metakernels (e.g. juice_plan_v462_20260223_001 → tag is v462_20260223_001). Pinned versions are looked up in kernels/mk/ first, then in kernels/mk/former_versions/. A FileNotFoundError is raised if the tag is not found, rather than silently falling back to a different version.

Adding extra kernels

Kernels furnished via add_kernel() are registered as intentional — is_dirty stays False and the pool can be snapshotted including them:

sm = QuickSpiceManager(spacecraft="JUICE", mk="plan")
sm.load_kernels()

sm.add_kernel("/path/to/extra.bc")                          # single file
sm.add_kernel(["/path/to/a.tls", "/path/to/b.bsp"])        # multiple files

Saving the current pool state

Write the live SPICE pool to a portable metakernel file that can be re-furnished later to reproduce the exact same set of loaded kernels:

sm.snapshot_pool("/tmp/pool_snapshot.tm")

Pool management

print(sm.is_active)   # True if this manager currently owns the SPICE pool
print(sm.is_dirty)    # True if kernels were added/removed since load_kernels()
sm.clean_pool()       # unload extras and re-furnish missing kernels

Inspecting the current configuration

sm.config returns a pandas DataFrame — spacecraft, version, the resolved metakernel path, and kernels directory — resolved entirely from this manager's own state. Unlike tour_config below, it does not require the planetary-coverage extra and doesn't report target/instrument, since those belong to TourConfig, not this manager:

print(sm.config)

Documenting which kernels were used

kernel_provenance() reports the minimum information needed to reproduce the currently active kernel set — e.g. when generating PDS4 labels for processing performed on downlinked data. Rather than flattening every individual kernel file, it cites the metakernel itself (its real, resolved filename — with the SKD version baked in when pinned) plus only the kernels added on top of it (e.g. via add_kernel()):

info = sm.kernel_provenance()
info["metakernel"]       # '.../juice_plan_v462_20260223_001.tm'
info["spacecraft"]       # 'JUICE'
info["version"]          # 'v462_20260223_001' (or 'latest')
info["resolved_at"]      # ISO-8601 UTC timestamp of when this was resolved --
                          # the closest thing to a version string when
                          # version='latest' was used
info["extra_kernels"]    # kernels added beyond the metakernel, e.g. via add_kernel()
info["all_kernels"]      # every kernel currently loaded, flattened

Pass source="pool" to inspect the live SPICE pool directly instead of this manager's own bookkeeping — useful when kernels were furnished by another manager instance or via raw spiceypy calls (works even without ever calling load_kernels() on this instance):

info = sm.kernel_provenance(source="pool")

Cache management

print(sm.cache_size)   # human-readable size of the kernel cache
sm.clear_cache()       # delete cached kernels (will re-download on next use)

Using a local metakernel

Pass an absolute path to an existing .tm file to skip FTP resolution:

sm = QuickSpiceManager(
    spacecraft="JUICE",
    mk="/path/to/my_local.tm",
    kernels_dir="/path/to/kernels",
)
sm.load_kernels()

planetary-coverage integration

QuickSpiceManager itself only handles SPICE kernels — it carries no target/instrument/geometry state of its own. get_tour_config() is a thin, on-demand bridge to planetary_coverage.TourConfig for downstream analysis. Requires the optional planetary-coverage extra:

pip install quick-spice-manager[planetary-coverage]
sm = QuickSpiceManager(spacecraft="JUICE", mk="plan")

# Builds a TourConfig with the given target/instrument.
tc = sm.get_tour_config(target="Jupiter", instrument="JANUS")
print(tc.coverage)

Deprecated: the old tour_config property (no target/instrument control, always target='Jupiter'/instrument='JANUS') still works for backward compatibility, but emits a DeprecationWarning — use get_tour_config() instead.

Environment variable overrides

Two environment variables (readable from a .env file in the working directory) let you override the FTP-based workflow without changing code:

Variable Effect
SPICE_METAKERNEL Path to a local metakernel file. Disables automatic FTP download.
SPICE_DIRECTORY Directory containing the kernel files referenced by the metakernel.
# .env
SPICE_METAKERNEL=/data/kernels/juice_ops.tm
SPICE_DIRECTORY=/data/kernels

Supported missions

The FTP downloader supports the following ESA missions (case-insensitive, common aliases accepted):

Mission Accepted names
BepiColombo BEPICOLOMBO, MPO, MTM, MMO
Comet Interceptor COMET-INTERCEPTOR
EnVision ENVISION
ExoMars 2016 EXOMARS2016, TGO, EDM
ExoMars RSP EXOMARSRSP
Gaia GAIA
Hera HERA
Huygens HUYGENS, CASP
INTEGRAL INTEGRAL
JUICE JUICE
JWST JWST
Mars Express MARS-EXPRESS, MEX, BEAGLE2
Rosetta ROSETTA
SMART-1 SMART-1
Solar Orbiter SOLAR-ORBITER, SOLO
Venus Express VENUS-EXPRESS, VEX

Logging

Logging is disabled by default. Enable it for debugging:

from quick_spice_manager import log_enable, log_enable_debug

log_enable()        # INFO level
log_enable_debug()  # DEBUG level

Concurrency & multi-process safety

The underlying CSPICE toolkit keeps a single, process-global kernel pool that is not thread-safe. quick-spice-manager handles this with a two-tier model:

  1. In-process (threads): every pool operation (load_kernels, unload_kernels, add_kernel, clean_pool, pool queries) is serialized by a single re-entrant lock. with QuickSpiceManager(...) as sm: holds this lock for the entire block, so the whole load → use → unload lifecycle is one critical section — a second thread's with block (or a direct load_kernels() call) waits until the first one fully exits. Advanced users calling spiceypy directly can join the same critical section via QuickSpiceManager.pool_lock.
  2. Cross-process (the on-disk kernel cache): kernel downloads write to a temp file and atomically rename it into place, so a reader never sees a truncated file, and each destination file is guarded by its own cross-process lock, so two processes racing to fetch the same missing kernel only download it once. clear_cache() is serialized against other clear_cache() calls the same way.
  3. Cross-process (total FTP connection count): per-file locking alone doesn't stop many independent managers or processes from each opening their own batch of parallel FTP connections at once. A small, fixed number of connection "slots" (shared by every QuickSpiceManager instance and OS process for this user, regardless of mission or kernels_dir) bounds the total number of simultaneous connections to ESA's FTP server machine-wide, so a burst of concurrent downloads waits its turn instead of overwhelming the server.
  4. Cross-process (resolving + furnishing): load_kernels() also takes a turn in a small, bounded pool of "furnish slots" scoped per kernels_dir, held across both the resolve/download-check step and the actual furnsh() calls. This keeps a burst of managers/processes cold-starting against the same cache directory from all hammering the same kernel files at once — by the time a manager gets its turn, an earlier holder has usually already finished preparing everything, so its own prepare step is a fast, local no-op. Different missions (different kernels_dir) never wait on each other.

Separate OS processes never need to coordinate over furnsh/unload/pool queries once kernels are loaded — each process owns an independent CSPICE kernel pool, so read-only querying across processes works without any locking. The furnish-slot mechanism above only throttles the loading step itself (a resource/throughput concern, not a correctness one), not anything that happens afterwards. Only the shared cache directory, the FTP connection count, and the furnish/prepare step are genuinely cross-process resources. One accepted limitation: clear_cache() isn't coordinated against concurrently in-flight downloads from other processes, so avoid calling it while other processes may be actively loading kernels from the same cache.

Working offline / when the ESA FTP server is unreachable

A small local cache remembers what each FTP call last returned, so the library degrades gracefully instead of hard-failing when there's no network:

  • Pinned versions never need the network twice. Once a specific version tag (e.g. version="v462_20260223_001") has been fully resolved and downloaded, later calls for the same spacecraft/mk/version skip the FTP server entirely — a pinned version tag never changes once published.
  • version="latest" (the default) always tries a live check first — its whole point is to discover newly published kernels, so it never silently serves stale data while online. Only if the ESA FTP server truly can't be reached (DNS failure, timeout, connection refused, etc.) does it fall back to the last fully-verified local resolution, logging a warning that the result may not reflect the newest kernels.
  • sm.metakernels (the list of available metakernels) gets the same treatment: cached after each successful listing, reused with a warning if the server can't be reached.
  • If nothing usable is cached when the server is unreachable, a clear ConnectionError is raised (naming exactly which referenced kernel files are still missing, if a partial cache exists) instead of a raw socket error several layers down.

This is transparent — no configuration needed — and lives alongside the regular kernel cache (<kernels_dir>/.resolution_cache.json), so clear_cache() clears it too.

Development

  • Clone this repository
  • Requirements:
    • uv
    • Python 3.10+
  • Create a virtual environment and install the dependencies
uv sync

Testing

uv run pytest

Documentation

The documentation is automatically generated from the content of the docs directory and from the docstrings of the public signatures of the source code. The documentation is updated and published as a Github Pages page automatically as part each release.

Releasing

Manual release

Releases are done with the command, e.g. incrementing patch:

uv run just bump patch
# also push, of course:
git push origin main --tags

this will update the changelog, commit it, and make a corresponding tag.

as the CI is not yet configured for publish on pypi it can be done by hand:

uv build
uv publish --build path/to/wheel

Automatic release - to be fixed

Trigger the Draft release workflow (press Run workflow). This will update the changelog & version and create a GitHub release which is in Draft state.

Find the draft release from the GitHub releases and publish it. When a release is published, it'll trigger release workflow which creates PyPI release and deploys updated documentation.

Updating with copier

To update the skeleton of the project using copier:

uvx copier update --defaults

Pre-commit

Pre-commit hooks run all the auto-formatting (ruff format), linters (e.g. ruff and mypy), and other quality checks to make sure the changeset is in good shape before a commit/push happens.

You can install the hooks with (runs for each commit):

pre-commit install

Or if you want them to run only for each push:

pre-commit install -t pre-push

Or if you want e.g. want to run all checks manually for all files:

pre-commit run --all-files

This project was generated using a fork of the wolt-python-package-cookiecutter template.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

quick_spice_manager-0.2.0.tar.gz (33.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

quick_spice_manager-0.2.0-py3-none-any.whl (35.5 kB view details)

Uploaded Python 3

File details

Details for the file quick_spice_manager-0.2.0.tar.gz.

File metadata

  • Download URL: quick_spice_manager-0.2.0.tar.gz
  • Upload date:
  • Size: 33.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Manjaro Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for quick_spice_manager-0.2.0.tar.gz
Algorithm Hash digest
SHA256 eee04b6aae69ed06d941697766a006b60f25fc4f489c48568cd99dc3898f9690
MD5 e3653b84289dab5f2ea2387f233957ca
BLAKE2b-256 1cadf1d2d326d1ffb395c07a6e87ba41d26b3e5d96cfa73dcb0b5024a25281e7

See more details on using hashes here.

File details

Details for the file quick_spice_manager-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: quick_spice_manager-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 35.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Manjaro Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for quick_spice_manager-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0e68bc6f388e3c9d763dfa7908d4e659d76db4f30984a1137f0df8045c3b2da1
MD5 6485828318feed9c72e91ca5298ac4b4
BLAKE2b-256 411360408478ba3a172fb89f4a7be559b9d60f6ba692b88b7c04d10aea7539cd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page