Skip to main content
Yanked

This release has been yanked by its maintainers, and will be ignored by installers, except when explicitly specified.
Consider using release 0.2.3 instead.

astromansion

Official Python client for the AstroMansion astrology API.

Nothing is computed locally. Every call reaches https://api.astromansion.com, which owns the ephemeris, your plan, your quota and your rate limit. The package never opens a feature the server did not grant.

Install

pip install astromansion

Python 3.10 or newer. The only dependency is httpx, installed with its SOCKS support so the client still works behind a corporate proxy, a local SOCKS proxy or Tor without anything further to install.

Get an API key

Create an account at astromansion.com, open your account page and generate a key. Requests made with it count against that account, under the plan it already has.

First chart

Put the key in the environment rather than in your source:

export ASTROMANSION_API_KEY="your key"
from astromansion import AstroMansion

client = AstroMansion()

chart = client.natal(
    date="1990-07-19",
    time="14:30",
    lat=41.0082,
    lon=28.9784,
    timezone=3,
)

print(chart.summary.Sun.sign)  # Cancer
print(chart.planets[0].house)  # 9

Birth data is passed flat. The API nests it under birth; the client does that for you.

Fields: date as YYYY-MM-DD, time as HH:MM (omit if unknown), lat and lon in decimal degrees, timezone as an hour offset or an IANA zone name, houses for a house system.

You can pass a mapping instead of keywords, but not both at once:

chart = client.natal({"date": "1990-07-19", "lat": 41.0082, "lon": 28.9784})

Where the key comes from

In order: the api_key argument, then astromansion.set_api_key(...), then ASTROMANSION_API_KEY. With none of them the constructor raises AuthenticationError, before a connection is opened and before the proxy environment is read, so a forgotten key is reported as a forgotten key.

client = AstroMansion(api_key="your key")

astromansion.reset() drops the module key and the shared client, which is what a test wants between cases.

Quick use

For a notebook or a one-file script:

import astromansion as am

am.set_api_key("your key")  # or rely on the environment
chart = am.natal(date="1990-07-19", lat=41.0082, lon=28.9784)

Applications should build a client instead: it holds a connection pool, and two of them can carry two different keys.

Async

from astromansion import AsyncAstroMansion

async with AsyncAstroMansion() as client:
    chart = await client.natal(
        date="1990-07-19",
        time="14:30",
        lat=41.0082,
        lon=28.9784,
        timezone=3,
    )

Same method names, same arguments, same exceptions. Python cannot make one class serve both, so the bare name is synchronous and Async marks the other, as in httpx, openai and anthropic.

Reading a response

The response is the server's own JSON, readable either way:

chart.summary.Sun.sign
chart["summary"]["Sun"]["sign"]
chart.to_dict()

Nothing is remodelled, so a field the API adds reaches you instead of being dropped, and no field it did not send is invented.

Some endpoints answer with the data itself and some wrap it as {"technique": ..., "result": ...}. The client opens that wrapper, so every response reads the same way and you never have to remember which kind you called:

chart = client.vedic_chart(date="1990-07-19", lat=41.0082, lon=28.9784)

chart.data          # the payload, wrapper removed
chart.technique     # what the server called it, when it said
chart.raw           # the untouched body, wrapper included

A body that carries a real result field of its own is left alone; the wrapper is recognised by its exact shape rather than by the presence of a name that data is allowed to use.

Arabic lots, fixed stars and the rest of the catalog

natal answers with the chart proper. Anything beyond it is named on chart, through options.categories:

lots = client.chart(
    date="1990-07-19", time="14:30",
    lat=41.0082, lon=28.9784, timezone=3,
    options={"categories": ["arabic_lots"]},
)

for lot in lots.data["bodies"]["arabic_lots"]:
    print(lot["name"], lot["sign"], lot["dms"], lot["house"])

Bodies come back grouped under the category that produced them, so read the group you asked for. The catalog publishes 34 Arabic lots and 890 fixed stars, along with planets, dwarfs, asteroids, centaurs, comets, hypotheticals, points, advanced_points, lilith, planetary_nodes, exoplanets, moons and eclipses.

chart answers one page at a time, and a large category is more than one page. Use bodies to read the whole of it and let the client follow the paging:

found = client.bodies(
    "fixed_stars", "arabic_lots",
    date="1990-07-19", time="14:30",
    lat=41.0082, lon=28.9784, timezone=3,
)

len(found["fixed_stars"])  # 890, in six requests
len(found["arabic_lots"])  # 34

Naming several categories reads them in one walk. The return is always a mapping keyed by the categories you asked for, one or several, so the shape never depends on how many.

Each page costs a network round trip and the calculation inside it is a rounding error beside that, so bodies asks for the largest page the API serves. page_size lowers it, and lowering it buys nothing: 890 stars are six requests at 160 and a hundred and eighty at five.

Reach for chart directly when you want one page rather than the category, and read the group with .get. The chart's own bodies are calculated alongside the categories you name and the page is a window over that whole selection, so a small catalog_limit can fill the first page with planets and angles before a single star appears, leaving no fixed_stars key at all. catalog_page.total counts the same way, the whole selection rather than the category.

There is no ceiling on how many you may read this way. options.all_bodies walks every category at once and needs the full-catalog scope that comes with Pro and Enterprise; naming the categories yourself does not.

Satellites

Six planets, twenty-five moons, and three answers for each one rather than a single longitude. Seen from Earth a moon sits within arcminutes of its planet, so its observed position just repeats the planet's; planetary_system returns the three layers apart:

system = client.planetary_system(
    birth={"date": "2000-01-01", "time": "12:00",
           "lat": 51.4779, "lon": 0.0, "timezone": 0},
    parent="jupiter",
)

for moon in system.data["satellites"]:
    orbit = moon["parentcentric"]              # where it is in its own orbit
    seen = moon["geocentric"]                  # how far the light travelled
    projected = moon["natal_parent_projected"]
    print(moon["name"], orbit["sector"], projected["sign"], projected["house"])
parent Moons
mars Phobos, Deimos
jupiter Io, Europa, Ganymede, Callisto
saturn Mimas, Enceladus, Tethys, Dione, Rhea, Titan, Hyperion, Iapetus
uranus Ariel, Umbriel, Titania, Oberon, Miranda
neptune Triton
pluto Charon, Nix, Hydra, Kerberos, Styx

The three layers

parentcentric is measured on the planet's own equator, in a frame that does not turn with the planet. It carries a sector from one to twelve rather than a house: houses need a horizon, and nobody was born on Jupiter.

geocentric carries the light time and the epoch the light left. Every moon is solved on its own, because solving once for the planet and reusing it puts an outer moon several seconds into the wrong place.

natal_parent_projected takes the moon's orbital phase, counted from the direction of Earth as measured at the planet, and lays it on the natal circle starting at the planet's own degree. A phase of zero returns the planet exactly. It is labelled astromansion_derived, because it is this engine's derivation and not a rule anyone inherited.

Three circles, so there is no shared longitude field and no aspect between them. options.correction takes NONE for geometric or LT for astrometric; LT+S names stellar aberration, which is not computed, and is refused rather than quietly answered with LT.

Which ephemeris answered

Every response names the kernel it ran on:

system.data["ephemeris"]
# {'kernel': 'jupiter-galilean-1900-2100.bsp', 'source': 'jup365',
#  'matches_horizons': True, 'horizons_delta_km': None,
#  'coverage_jd': [[2415020.5, 2488069.5]]}

Mars, Jupiter, Saturn and Pluto agree with JPL Horizons to machine precision. Uranus and Neptune do not, and say so: their kernels carry an older orbit solution, and horizons_delta_km gives the measured difference. It is an along-track phase offset rather than a wrong orbit, up to 218 km on Titania, which is 0.016 arcseconds seen from Earth. The field is there so a reader comparing against Horizons learns why before wondering.

Satellites are tabulated from 1900 to 2100. A date outside that is refused rather than extrapolated, because an extrapolated moon is a different orbit and not a rougher answer.

Every endpoint

Every published operation, 67 of them, has a method on both clients and a module-level shortcut, all generated from the schema: natal, transits, synastry, composite, solar_return, progression, harmonics, astrocartography, vedic_chart, zodiacal_releasing, firdaria, horary, electional and the rest.

Anything new is reachable before this client names it:

result = client.request("POST", "/v1/harmonics", json={"birth": {...}})

Authentication, timeouts, retries and error handling behave identically there.

Errors

from astromansion import QuotaExceededError, RateLimitError

try:
    chart = client.natal(date="1990-07-19", lat=41.0, lon=29.0)
except RateLimitError as error:
    print("wait", error.retry_after, "seconds")
except QuotaExceededError:
    print("this period's allowance is spent")
Exception Meaning
AuthenticationError Key missing, malformed or unknown
PermissionDeniedError Valid key, feature not in the plan
QuotaExceededError Allowance for the period is spent
RateLimitError Too many requests just now; retry_after says how long
ValidationError Request rejected; details names the field
NotFoundError, ConflictError Missing resource, conflicting state
ServerError The API failed to answer
AstroMansionConnectionError The request never completed

All descend from AstroMansionError. Each carries status_code, error_code, details, request_id and retry_after when the API supplies them.

Rate limits and quota

A rate limit clears on its own after retry_after. A spent quota does not: it needs a new period or a larger plan. They are separate exceptions for that reason.

The client retries only failures that carry no result: connection errors, 429 and 5xx, twice by default, honouring Retry-After. A refusal you must fix is never retried.

client = AstroMansion(timeout=60.0, max_retries=0)

Documents

pdf = client.export_pdf(date="1990-07-19", lat=41.0082, lon=28.9784)

with open("chart.pdf", "wb") as file:
    file.write(pdf)

Or name a path and let the client write it:

client.export_pdf(date="1990-07-19", lat=41.0082, lon=28.9784, output="chart.pdf")

Every endpoint that answers with a document takes output the same way: export_pdf, export_csv, render_svg, render_png, render_biwheel and render_sharecard, on both clients.

client.render_svg(date="1990-07-19", lat=41.0082, lon=28.9784, output="wheel.svg")

Nothing is written to disk unless you name a path, so a call cannot overwrite a file you did not choose. Without one you get the bytes and decide yourself.

Security

The key travels in the X-API-Key header, never in a URL. It is masked in repr(client) and appears in no exception or log line the package writes. Keep it in the environment or a secret store, not in source control. Rotate it from your account page if it leaks.

Staging

client = AstroMansion(base_url="http://localhost:8000")

Also readable from ASTROMANSION_BASE_URL.

Links

Download files

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

Source Distribution

astromansion-0.1.8.tar.gz (36.9 kB view details)

Uploaded Source

Built Distribution

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

astromansion-0.1.8-py3-none-any.whl (44.3 kB view details)

Uploaded Python 3

File details

Details for the file astromansion-0.1.8.tar.gz.

File metadata

  • Download URL: astromansion-0.1.8.tar.gz
  • Upload date:
  • Size: 36.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for astromansion-0.1.8.tar.gz
Algorithm Hash digest
SHA256 44281b3adc65473dfd4ee6db95a0b282f154837eec4dc186aef899b28f795e81
MD5 94b59a8c14a356721ef0c969b42c844b
BLAKE2b-256 95979b8401b91a4d108eb58a3bd10a792818dfc19f03334b8f65c54976f69b8c

See more details on using hashes here.

File details

Details for the file astromansion-0.1.8-py3-none-any.whl.

File metadata

  • Download URL: astromansion-0.1.8-py3-none-any.whl
  • Upload date:
  • Size: 44.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for astromansion-0.1.8-py3-none-any.whl
Algorithm Hash digest
SHA256 abba06d1aca0276cea6aad2a19f27bde029407976427f8501628d503f5150e2d
MD5 cde062cdefeca16379b12243f0c156c1
BLAKE2b-256 5a86525cddb20b7bb4f660c96dff535d5d1f1b22bff880b316412c57748c5cf0

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.14

2 files

0.1.13

2 files

0.1.12

2 files

0.1.11

2 files

0.1.10

2 files

0.1.9

2 files

This release

0.1.8 This release

2 files

0.1.7

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

0.0.2

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page