Python bindings for the swisseph-rs crate — a pure-Rust Swiss Ephemeris
Project description
pyswisseph-rs
Python bindings for the swisseph-rs
crate — a pure-Rust reimplementation of the
Swiss Ephemeris.
Why this package? Every method on Ephemeris releases the GIL, so a single
shared instance can drive a ThreadPoolExecutor at full core utilization with
zero coordination. No global state, no swe_close(), no mutex — just pass
the same object to every thread.
pip install pyswisseph-rs # or: uv add pyswisseph-rs
Quickstart (zero files, Moshier ephemeris)
The built-in Moshier analytical ephemeris covers all planets for any date without ephemeris files. Accuracy is ~1 arc-second for modern dates.
from swisseph_rs import Body, CalcFlags, Ephemeris, EphemerisConfig
eph = Ephemeris(EphemerisConfig()) # Moshier by default, no files needed
result = eph.calc_ut(2451545.0, Body.SUN, CalcFlags.SPEED)
lon, lat, dist, lon_speed, lat_speed, dist_speed = result.data
print(f"Sun longitude at J2000: {lon:.6f}°")
Using Swiss Ephemeris data files
For sub-arc-second precision, point EphemerisConfig at a directory
containing the Swiss Ephemeris data files:
from swisseph_rs import Ephemeris, EphemerisConfig, EphemerisSource
eph = Ephemeris(EphemerisConfig(
ephemeris_source=EphemerisSource.SWISS,
ephe_path="/path/to/ephe",
))
Data files (sepl*.se1, semo*.se1, seas*.se1, etc.) are available from
Astrodienst. Download the files
covering your date range and place them in the directory you point ephe_path
to.
For JPL ephemerides, set ephemeris_source=EphemerisSource.JPL and optionally
jpl_filename="de441.eph".
Threading
pyswisseph-rs is designed for concurrent workloads. Every Ephemeris
method releases the GIL around the Rust computation, so multiple Python
threads run in true parallel on separate cores.
import concurrent.futures
from swisseph_rs import Body, CalcFlags, Ephemeris, EphemerisConfig
eph = Ephemeris(EphemerisConfig())
bodies = [Body.SUN, Body.MOON, Body.MERCURY, Body.VENUS, Body.MARS,
Body.JUPITER, Body.SATURN]
def calc_year(year_offset):
"""Calculate daily positions for one year."""
jd_start = 2451545.0 + year_offset * 365.25
results = []
for day in range(365):
for body in bodies:
r = eph.calc_ut(jd_start + day, body, CalcFlags.SPEED)
results.append(r.data[0]) # longitude
return results
# Same Ephemeris instance shared across all threads — no copies, no locks
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
futures = [pool.submit(calc_year, y) for y in range(20)]
all_results = [f.result() for f in futures]
This consistently achieves near-linear speedup (e.g. ~3.5x on 4 cores). Results are bit-identical to serial execution.
What about multiprocessing?
Ephemeris is not picklable. For process-based parallelism, construct a
separate Ephemeris in each worker. But threading is usually the better
choice here — there is no GIL contention, so threads give you the speedup
without the IPC overhead.
Sidereal mode and topographic position
Configuration that the C library sets via global state (swe_set_sid_mode,
swe_set_topo) is passed through EphemerisConfig instead:
from swisseph_rs import (
Ephemeris, EphemerisConfig, SiderealMode, TopoPosition,
)
eph = Ephemeris(EphemerisConfig(
sidereal_mode=SiderealMode.LAHIRI,
topographic=TopoPosition(longitude=-74.006, latitude=40.7128, altitude=10.0),
))
The config is frozen after construction — no mutable global state, no
ordering bugs between set_* calls.
Migrating from C pyswisseph
pyswisseph-rs wraps the same Swiss Ephemeris engine but replaces the C
library's global-state API with an object-oriented, stateless design. The
key differences:
- No global state.
swe_set_ephe_path,swe_set_sid_mode,swe_set_topobecome fields onEphemerisConfig.swe_closeis unnecessary. - Methods on
Ephemeris. Functions likeswe_calcbecomeeph.calc(). - Named types. Flags are
CalcFlags.SPEED, bodies areBody.SUN, not raw integers.
Every method's docstring includes the corresponding swe_* name. Use
help(eph.calc) or check the type stubs for the mapping.
Function mapping (top 25)
| C pyswisseph | pyswisseph-rs |
|---|---|
swe_calc(jd, ipl, iflag) |
eph.calc(jd, body, flags) |
swe_calc_ut(jd, ipl, iflag) |
eph.calc_ut(jd, body, flags) |
swe_calc_pctr(jd, ipl, ictr, iflag) |
eph.calc_pctr(jd, body, center, flags) |
swe_fixstar2(star, jd, iflag) |
eph.fixstar2(star, jd, flags) |
swe_fixstar2_ut(star, jd, iflag) |
eph.fixstar2_ut(star, jd, flags) |
swe_fixstar2_mag(star) |
eph.fixstar2_mag(star) |
swe_houses(jd, lat, lon, hsys) |
eph.houses(jd, lat, lon, hsys) |
swe_houses_ex(jd, iflag, lat, lon, hsys) |
eph.houses_ex(jd, flags, lat, lon, hsys) |
swe_house_pos(armc, lat, eps, hsys, ...) |
houses.house_pos(armc, lat, eps, hsys, xpin) |
swe_get_ayanamsa_ex(jd, iflag) |
eph.get_ayanamsa_ex(jd, flags) |
swe_julday(y, m, d, h, cal) |
date.julday(y, m, d, h, cal) |
swe_revjul(jd, cal) |
date.revjul(jd, cal) |
swe_utc_to_jd(y,m,d,h,mi,s, cal) |
date.utc_to_jd(utc, cal, eph) |
swe_day_of_week(jd) |
date.day_of_week(jd) |
swe_rise_trans(...) |
eph.rise_trans(...) |
swe_pheno_ut(jd, ipl, iflag) |
eph.pheno_ut(jd, body, flags) |
swe_nod_aps_ut(jd, ipl, iflag, method) |
eph.nod_aps_ut(jd, body, flags, method) |
swe_sol_eclipse_when_glob(jd, iflag, ifltype, bwd) |
eph.sol_eclipse_when_glob(jd, flags, ifltype, backward) |
swe_lun_eclipse_when(jd, iflag, ifltype, bwd) |
eph.lun_eclipse_when(jd, flags, ifltype, backward) |
swe_get_orbital_elements(jd, ipl, iflag) |
eph.get_orbital_elements(jd, body, flags) |
swe_split_deg(ddeg, roundflag) |
math.split_degrees(ddeg, flags) |
swe_sidtime(jd) |
sidereal_time.sidereal_time(jd, config) |
swe_refrac(inalt, atpress, attemp, dir) |
azalt.refrac(inalt, atpress, attemp, dir) |
swe_set_ephe_path(path) |
EphemerisConfig(ephe_path=path) |
swe_set_sid_mode(sid_mode, t0, ayan_t0) |
EphemerisConfig(sidereal_mode=..., sidereal_t0=..., sidereal_ayan_t0=...) |
swe_set_topo(lon, lat, alt) |
EphemerisConfig(topographic=TopoPosition(...)) |
swe_close() |
(not needed — no global state) |
Module structure
Free functions live in submodules mirroring the Rust crate's module tree:
from swisseph_rs import date, math, houses, azalt, sidereal_time
Types and flags are re-exported at the top level:
from swisseph_rs import Body, CalcFlags, EphemerisConfig, Ephemeris
Design and transliteration discipline
pyswisseph-rs maintains a strict 1:1 correspondence with the underlying
Rust crate's public API. Every Python class, enum, and function mirrors
exactly one Rust symbol. This is a deliberate design choice documented in
CONTEXT.md and the architecture decision records in
docs/adr/:
- ADR 0001 — PyO3 on the lib crate, not the C FFI layer
- ADR 0002 — strict 1:1 transliteration from Rust to Python
License
This project is licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See LICENSE for the full text.
The wheel statically links the swisseph-rs crate, which is a derivative
work of the Swiss Ephemeris by Astrodienst
AG, also licensed under AGPL-3.0-or-later.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyswisseph_rs-0.1.0.tar.gz.
File metadata
- Download URL: pyswisseph_rs-0.1.0.tar.gz
- Upload date:
- Size: 147.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57c065aa4cc9da031bbee46f8819d6fb8185408d395d568ad9fbcfb84b807693
|
|
| MD5 |
a361df6a1333de3cf9aa6eb216c0c55d
|
|
| BLAKE2b-256 |
f25d2853bf794c0c83937407b6061acaebbdb4bace11490c13c00ab77a31905f
|
File details
Details for the file pyswisseph_rs-0.1.0-cp311-abi3-win_amd64.whl.
File metadata
- Download URL: pyswisseph_rs-0.1.0-cp311-abi3-win_amd64.whl
- Upload date:
- Size: 916.6 kB
- Tags: CPython 3.11+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ee2a1c12fbae1f75675a367457a939f58e6cdf9fe8f6b4f365e4b7249770c2bc
|
|
| MD5 |
edaadc8adba441ab624a6626a8e646bc
|
|
| BLAKE2b-256 |
40bf2e2acc0dbc9fb9c096e007190df21aff606f4df89521e88dc0c4d67ed680
|
File details
Details for the file pyswisseph_rs-0.1.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: pyswisseph_rs-0.1.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 1.1 MB
- Tags: CPython 3.11+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e33179f7dc1d9cffeada35a903ae818b6f62f61b2e6d6e4d5759c1285a2369a7
|
|
| MD5 |
91075adfe92a695eb1f9753c52b0a063
|
|
| BLAKE2b-256 |
16ccfc1640ae2b239a89b41eccac6b99481e19af95cd93e8da416920c876eefc
|
File details
Details for the file pyswisseph_rs-0.1.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: pyswisseph_rs-0.1.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 1.1 MB
- Tags: CPython 3.11+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e751672cdbd1e5d47a66ded0d5eecd5f3f6c09df95b4f4218ba1421a8665544f
|
|
| MD5 |
b3ef348fb0b98fb54126a807573fbb32
|
|
| BLAKE2b-256 |
03d6bd95f1d0c5ea46eca661fe1d265b9ce313250cbd6a9ffec8afdc76a7198a
|
File details
Details for the file pyswisseph_rs-0.1.0-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.
File metadata
- Download URL: pyswisseph_rs-0.1.0-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
- Upload date:
- Size: 2.1 MB
- Tags: CPython 3.11+, macOS 10.12+ universal2 (ARM64, x86-64), macOS 10.12+ x86-64, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2ef76bc899484998e0765eebd0e52bfc88d50fde5fe80c4ee12a760198b728db
|
|
| MD5 |
f4d80cc3ea50d6b27ac11dda7387780e
|
|
| BLAKE2b-256 |
8e777cec7d535b16f78dd7f5d31f26845a7c563c372d3ac92cf2b82016ceb077
|