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.
Release files for pyswisseph-rs 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyswisseph_rs-0.1.2.tar.gz | 149.2 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| pyswisseph_rs-0.1.2-cp311-abi3-win_amd64.whl | CPython 3.11 | abi3 | Windows x86-64 | Details |
| pyswisseph_rs-0.1.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| pyswisseph_rs-0.1.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| pyswisseph_rs-0.1.2-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl | CPython 3.11 | abi3 | macOS 10.12+ x86-64, macOS 11.0+ ARM64, macOS 10.12+ universal2 (ARM64, x86-64) | Details |
Total release size: 5.3 MB
Release files / pyswisseph_rs-0.1.2.tar.gz
| Download URL | pyswisseph_rs-0.1.2.tar.gz |
|---|---|
| Size | 149.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
21ffb32ebf71ce10ffbf5012898e6ff33b7b4cdd3902afd0686c54e75aaf3241
|
|
BLAKE2b-256 checksum How to use checksums |
f33ad78cece6682167bbf21cf5897f521e1ec7d79d49b4176a3092165adf3cc3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / pyswisseph_rs-0.1.2-cp311-abi3-win_amd64.whl
| Download URL | pyswisseph_rs-0.1.2-cp311-abi3-win_amd64.whl |
|---|---|
| Size | 924.3 kB |
| Tags | CPython 3.11 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
e3930e549abf4af701ffb518a70812290a4e32ef4343f7ee8bdff64efb530220
|
|
BLAKE2b-256 checksum How to use checksums |
a219d8fdbb4b1fd48a64d52660dbaed740d99f109ff426ea045a4c471ab856b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / pyswisseph_rs-0.1.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | pyswisseph_rs-0.1.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
38626e3fba04e3dc087d84df174a6b9fdc9728639663ef3cfd7eb1daaaed060e
|
|
BLAKE2b-256 checksum How to use checksums |
e4ab3554281283ca2d82845d46340033ae753b073e65e0064fd2b0464926c86f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / pyswisseph_rs-0.1.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | pyswisseph_rs-0.1.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
dc1cbc640c036b9ad2ce39972bb09421313de24aed43438220b81ff86ed4644a
|
|
BLAKE2b-256 checksum How to use checksums |
920823a0cf54d42a985609f20e099320d157ac5cf6198b7425209d8fc58ea671
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / pyswisseph_rs-0.1.2-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
| Download URL | pyswisseph_rs-0.1.2-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl |
|---|---|
| Size | 2.0 MB |
| Tags | CPython 3.11 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
5fa8bf346ad8574de9cd5c8570f295c5880568b6340aad011c9e3f7df0ccc669
|
|
BLAKE2b-256 checksum How to use checksums |
5fdf027c77462d3aec80d4cfaf0f31946bfab100cb580fcaab9e933c368f807e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|