Skip to main content

Astrology calculations library — traditional Hellenistic focus, Swiss Ephemeris backed

Project description

astrologica

PyPI version Python versions License: MIT

A Python astrology library. Traditional Hellenistic focus, Swiss Ephemeris backed, domain-pure core.

Install

pip install astrologica              # core
pip install 'astrologica[geo]'       # + city lookup, timezone, elevation helpers

Quickstart

from datetime import datetime
from zoneinfo import ZoneInfo
from astrologica import ChartData, Place, Planet, compute_natal_chart

data = ChartData(
    datetime=datetime(1990, 5, 15, 14, 30, tzinfo=ZoneInfo("America/New_York")),
    place=Place(latitude=40.7128, longitude=-74.0060),
)

chart = compute_natal_chart(data)

print(chart.planets[Planet.SUN].sign)           # Sign.TAURUS
print(chart.planets[Planet.SUN].degree_in_sign) # 24.72
print(chart.planets[Planet.SUN].is_retrograde)  # False
print(chart.is_diurnal)                         # True

Don't know the timezone? Use the [geo] extra.

from astrologica import ChartTradition, Planet
from astrologica.geo import build_horary_chart, build_natal_chart

natal = build_natal_chart("1990-05-15T14:30", "New York")
modern = build_natal_chart("1990-05-15T14:30", "New York",
                           tradition=ChartTradition.MODERN)

horary = build_horary_chart("2025-06-01T12:00", "London", question_house=7)
print(horary.significator_of_querent, horary.significator_of_quesited)
print("Moon VOC:", horary.moon_is_void_of_course)

What's in the box

Every feature is a top-level import from astrologica. The natal Chart already contains planet positions, houses, aspects, lots, and the prenatal syzygy — the cookbook below shows everything else you can layer on.

Aspects

Computed automatically inside every Chart. To recompute standalone:

from astrologica import compute_aspects
aspects = compute_aspects(chart.planets)
for a in aspects:
    print(a.first, a.kind, a.second, f"orb={a.orb:.2f}°",
          "applying" if a.applying else "separating")

Transits — snapshot and range search

from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from astrologica import AspectKind, Planet, compute_transits, find_transits

now = datetime.now(ZoneInfo("UTC"))
snapshot = compute_transits(chart, now)
for ta in snapshot:
    print(f"t.{ta.transiting.name} {ta.kind.name} n.{ta.natal.name} orb={ta.orb:.2f}")

events = find_transits(
    chart,
    start=now,
    end=now + timedelta(days=365),
    transiting_bodies=[Planet.SATURN, Planet.JUPITER],
    natal_bodies=[Planet.SUN, Planet.MOON],
    aspects=[AspectKind.CONJUNCTION, AspectKind.OPPOSITION],
)

Returns (solar / lunar)

from datetime import datetime
from zoneinfo import ZoneInfo
from astrologica import compute_lunar_return, compute_solar_return

sr = compute_solar_return(chart, year=2026)              # birthday chart
lr = compute_lunar_return(chart, after=datetime.now(ZoneInfo("UTC")))

Pass place=Place(...) to either for a relocated return.

Secondary progressions

from astrologica import compute_secondary_progressions
progressed = compute_secondary_progressions(chart, age_years=35.5)

Primary directions (Placidian / Ptolemaic)

from astrologica import (
    ArcKey, DirectionApproach, DirectionType,
    compute_primary_directions,
)

directions = compute_primary_directions(
    chart,
    key=ArcKey.NAIBOD,                     # arc-to-years conversion
    direction=DirectionType.DIRECT,        # or CONVERSE
    approach=DirectionApproach.ZODIACAL,   # or MUNDANE
)
for d in directions:
    print(f"{d.promissor.name}{d.significator.name} {d.kind.name} "
          f"in {d.years:+.2f} years")

Zodiacal releasing

from astrologica import Lot, compute_zodiacal_releasing

periods = compute_zodiacal_releasing(
    chart,
    lot=Lot.SPIRIT,         # or Lot.FORTUNE
    max_level=4,            # 1 = major, 4 = sub-sub-sub
    year_length_days=360.0, # Valens' Egyptian year
)
for p in periods:
    marker = " LB" if p.is_lb else (" *peak*" if p.is_peak else "")
    print(f"L{p.level} {p.sign.name} {p.start.date()}{p.end.date()}{marker}")

Essential dignities

from astrologica import TermsSystem, compute_dignities

sun = chart.planets[Planet.SUN]
print(sun.dignities)   # set on every PlanetPosition

# Or compute standalone at any longitude:
d = compute_dignities(
    Planet.JUPITER, longitude=5.0,
    is_diurnal=True,
    terms_system=TermsSystem.EGYPTIAN,  # or PTOLEMAIC / CHALDEAN / …
)

Lots (Hellenistic) — classical seven + custom DSL

from astrologica import Lot
print(chart.lots[Lot.FORTUNE].sign, chart.lots[Lot.FORTUNE].degree_in_sign)

Build your own parts:

from astrologica import (
    CustomLot, LotFormula, Planet,
    CardinalAngle, CardinalAngleName, HouseCuspRef,
    LordOf, LordKind, RulerOf, RulerOfKind, PriorLot, SyzygyPoint,
    compute_custom_lot,
)

# Lot of Fortune, rebuilt:
fortune = CustomLot(
    name="Fortune",
    day=LotFormula(plus=(Planet.MOON,), minus=(Planet.SUN,)),
    night=LotFormula(plus=(Planet.SUN,), minus=(Planet.MOON,)),
)
# ASC is implicit in every formula; sect inversion is automatic.
longitude = compute_custom_lot(chart, fortune)

Formulas accept any LotComponent: bare Planet, CardinalAngle (e.g. MC), HouseCuspRef, LordOf (house lord), RulerOf (sign ruler of a body), PriorLot, or SyzygyPoint.

Fixed stars (30 classical)

from astrologica import FixedStar, compute_fixed_star_conjunctions

for conj in compute_fixed_star_conjunctions(chart, orb=1.0):
    print(f"{conj.body.name} conj {conj.star.name} (orb {conj.orb:.2f}°)")

Midpoints

from astrologica import compute_midpoints
mps = compute_midpoints(chart)  # 21 classical planet-pair midpoints

Antiscia / contraantiscia

from astrologica import compute_antiscion, compute_contraantiscion
compute_antiscion(15.0)         # solstitial-axis reflection
compute_contraantiscion(15.0)   # equinoctial-axis reflection

Dodecatemorion (twelfth-part)

from astrologica import DodecatemorionVariant, compute_dodecatemorion
compute_dodecatemorion(24.72)                                # Valens (default)
compute_dodecatemorion(24.72, DodecatemorionVariant.FIRMICUS)

Planetary hours (Chaldean order)

from datetime import date
from astrologica import Place, compute_planetary_hours

hours = compute_planetary_hours(
    on=date(2025, 6, 1),
    place=Place(latitude=51.5074, longitude=-0.1278),
)
for h in hours:
    print(h.ruler.name, h.start, "→", h.end, "day" if h.is_daytime else "night")

Rise / set / MC / IC

from datetime import date
from astrologica import Planet, Place, compute_rise_set

times = compute_rise_set(Planet.SUN, date(2025, 6, 1),
                         Place(latitude=51.5074, longitude=-0.1278))
print(times.rise, times.mc, times.set, times.ic)  # any may be None

Sign rising times at a latitude

from astrologica import Sign, compute_rising_times

rt = compute_rising_times(latitude_deg=40.7128)
for sign in Sign:
    print(sign.name, f"{rt[sign]:6.2f}°")  # sums to 360°

Prenatal syzygy

Already attached to every chart — no extra call needed:

print(chart.syzygy.kind.name, chart.syzygy.when, chart.syzygy.sign.name)
# → "NEW_MOON" or "FULL_MOON", the datetime, and the sign

Also callable directly against any moment if you need it standalone:

from astrologica import SwissEphemerisAdapter, compute_prenatal_syzygy
s = compute_prenatal_syzygy(chart.data.datetime, SwissEphemerisAdapter())

find_time — solve for a longitude crossing

from datetime import datetime
from zoneinfo import ZoneInfo
from astrologica import Planet, find_time

t = find_time(
    body=Planet.MARS,
    target_longitude=0.0,            # ingress into Aries
    start=datetime(2026, 1, 1, tzinfo=ZoneInfo("UTC")),
    end=datetime(2027, 1, 1, tzinfo=ZoneInfo("UTC")),
)

Horary

from astrologica import ChartData, Place, compute_horary_chart
data = ChartData.from_iso("2025-06-01T14:30:00+01:00",
                          Place(latitude=51.5074, longitude=-0.1278))
hc = compute_horary_chart(data, question_house=7)
print(hc.significator_of_querent, hc.significator_of_quesited,
      "VOC" if hc.moon_is_void_of_course else "")

Configuration

Every computation respects the ChartData and optional house system.

House systems

from astrologica import HouseSystem, compute_natal_chart
chart = compute_natal_chart(data, house_system=HouseSystem.PLACIDUS)
# Available: WHOLE_SIGN (default), PORPHYRY, ALCABITUS, REGIOMONTANUS, PLACIDUS

Sidereal zodiac (21 ayanamsas)

from astrologica import Ayanamsa, ChartData
data_sidereal = ChartData(datetime=..., place=..., ayanamsa=Ayanamsa.LAHIRI)

Available: FAGAN_BRADLEY, LAHIRI, DELUCE, RAMAN, USHASHASHI, KRISHNAMURTI, DJWHAL_KHUL, YUKTESHWAR, JN_BHASIN, BABYL_KUGLER1/2/3, BABYL_HUBER, BABYL_ETPSC, ALDEBARAN_15TAU, HIPPARCHOS, SASSANIAN, GALCENT_0SAG, J2000, J1900, B1950.

Reference frame

from astrologica import ChartData, ReferenceFrame
data_topo = ChartData(datetime=..., place=..., frame=ReferenceFrame.TOPOCENTRIC)
# Frames: GEOCENTRIC (default), TOPOCENTRIC, HELIOCENTRIC

Tradition (body set)

from astrologica import ChartTradition
compute_natal_chart(data, tradition=ChartTradition.MODERN)
# TRADITIONAL: 7 classical. MODERN: + Uranus, Neptune, Pluto + lunar nodes.

Terms systems

Supplied to compute_dignities(...): EGYPTIAN (default), PTOLEMAIC, CHALDEAN, DOROTHEAN, ASTROLOGICAL_ASSOCIATION.

Ephemeris data

No download required. The Swiss adapter uses the built-in Moshier analytical ephemeris for planet positions (accurate to a few arcseconds from 3000 BC to 3000 AD), and ships a minimal sefstars.txt inside the wheel so fixed-star lookups work out of the box.

If you want the higher-precision .se1 planet data, download the files from https://www.astro.com/ftp/swisseph/ephe/ (typically sepl_18.se1 + semo_18.se1 covers 1800–2399 AD) and point the adapter at them:

from astrologica import SwissEphemerisAdapter, compute_natal_chart
adapter = SwissEphemerisAdapter(ephe_path="/path/to/sweph/ephe")
chart = compute_natal_chart(data, ephemeris=adapter)

The same adapter can be reused across every compute_* call.

Serialization

import json
json.dumps(chart.to_dict(), indent=2)
# {
#   "datetime": "1990-05-15T14:30:00-04:00",
#   "utc": "1990-05-15T18:30:00+00:00",
#   "jd": 2448027.270833,
#   "place": {"latitude": 40.7128, "longitude": -74.006},
#   "house_system": "WHOLE_SIGN",
#   "tradition": "TRADITIONAL",
#   "ascendant": 171.12,
#   "midheaven": 81.04,
#   "is_diurnal": true,
#   "syzygy": {"kind": "FULL_MOON", ...},
#   "planets": {"SUN": {"sign": "TAURUS", "degree_in_sign": 24.72, ...}, ...},
#   "houses": [...], "aspects": [...], "lots": {...}
# }

The Chart aggregate

Field Type What it is
data ChartData the originating input (datetime, place, ayanamsa, frame)
house_system HouseSystem the system used for cusps
tradition ChartTradition TRADITIONAL or MODERN body set
ascendant, midheaven Longitude angles
is_diurnal bool sect — Sun above horizon?
syzygy Syzygy prenatal lunation (kind / when / longitude / sign)
planets Mapping[Planet, PlanetPosition] body positions + speed + dignities + retrograde
houses tuple[HouseCusp, ...] cusp longitudes
aspects tuple[Aspect, ...] Ptolemaic + semisextile + quincunx
lots Mapping[Lot, LotPosition] classical seven lots

Architecture

Port-adapter / hexagonal. The domain layer is pyswisseph-free; Swiss Ephemeris lives behind an EphemerisPort. Public API at astrologica.*; internals under astrologica._internal.* (enforced by import-linter contracts). Frozen, slots-using dataclasses throughout; every chart is JSON-serialisable via Chart.to_dict(). Type-hint friendly — the package ships a py.typed marker.

Custom backends (e.g. for tests) are a drop-in:

from astrologica import EphemerisPort, compute_natal_chart
class MyEphemeris(EphemerisPort): ...
compute_natal_chart(data, ephemeris=MyEphemeris())

License

MIT — see LICENSE.

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

astrologica-0.1.2.tar.gz (136.8 kB view details)

Uploaded Source

Built Distribution

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

astrologica-0.1.2-py3-none-any.whl (111.5 kB view details)

Uploaded Python 3

File details

Details for the file astrologica-0.1.2.tar.gz.

File metadata

  • Download URL: astrologica-0.1.2.tar.gz
  • Upload date:
  • Size: 136.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for astrologica-0.1.2.tar.gz
Algorithm Hash digest
SHA256 58a12432447b6ee6e705232693a4220b113c9c2fbad5ad3044b8f12d95dfb2e3
MD5 85262b1a9cdcad83069b98fd09c02973
BLAKE2b-256 8f03de4056f66c193371fdb1b88544a1095bf0044ef10f67bf763a5c9a3602f5

See more details on using hashes here.

Provenance

The following attestation bundles were made for astrologica-0.1.2.tar.gz:

Publisher: publish.yml on milanpredic/astrologica

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file astrologica-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: astrologica-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 111.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for astrologica-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 05684fb515f7ed3beccf9b1a1fd1e5c605dd183ba657a5746735144dd5d3260f
MD5 9a26d7372b8d339e138f31510574a8c5
BLAKE2b-256 41094ebb9e2628d9f0574f57ed54e54447b4a97364d668f811506900370e6ca3

See more details on using hashes here.

Provenance

The following attestation bundles were made for astrologica-0.1.2-py3-none-any.whl:

Publisher: publish.yml on milanpredic/astrologica

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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