DEV Community

Cover image for I Turned the GetBirthChart Astrology Engine Into a Python Package

I Turned the GetBirthChart Astrology Engine Into a Python Package

A few weeks ago, I open-sourced the Python calculation engine behind GetBirthChart.

The source was public, but using it still meant cloning the repository, setting up the environment, and understanding the internal package structure.

That is useful if you want to inspect the code.

It is less useful if you just want to use the engine.

So I packaged it properly.

Today, gbc-astro can be installed directly from PyPI:

pip install gbc-astro
Enter fullscreen mode Exit fullscreen mode

The source remains open on GitHub.

But now the calculation engine can also behave like what it really is: a Python library.

I didn't want to create a second engine

This was the most important constraint.

GetBirthChart already had a working calculation engine.

Publishing to PyPI should not create another implementation.

I wanted this:

GetBirthChart
      ↓
  gbc-astro
      ↑
Python package
Enter fullscreen mode Exit fullscreen mode

Not this:

GetBirthChart → engine A

PyPI           → engine B
Enter fullscreen mode Exit fullscreen mode

So the public package is a thin interface around the same calculation code used by GetBirthChart.

A basic chart looks like this:

from gbc_astro import calculate_chart

chart = calculate_chart(
    date="1990-05-15",
    time="09:30",
    latitude=51.5074,
    longitude=-0.1278,
    timezone="Europe/London",
)

print(chart.bodies["sun"].sign)
print(chart.bodies["moon"].sign)
print(chart.angles)
Enter fullscreen mode Exit fullscreen mode

Underneath that function, the package still delegates to the existing engine.

calculate_chart(...)
        ↓
AstrologyEngine.natal(...)
        ↓
calculation providers
        ↓
Swiss Ephemeris
Enter fullscreen mode Exit fullscreen mode

No separate astrology math was introduced for PyPI.

What the package calculates

The engine currently handles things like:

  • planetary positions
  • zodiac placements
  • houses
  • Ascendant and Midheaven
  • aspects
  • retrograde state
  • timezone-aware birth data
  • derived chart data
  • structured warnings

Swiss Ephemeris is accessed through pyswisseph.

One thing I wanted to preserve exactly was how the engine handles missing information.

Unknown birth time is still unknown

This has been one of the design decisions I care about most in GetBirthChart.

If the birth time is unknown, the library does not silently invent one.

For example:

from gbc_astro import calculate_chart

chart = calculate_chart(
    date="1990-05-15",
    time=None,
    latitude=51.5074,
    longitude=-0.1278,
    timezone="Europe/London",
)
Enter fullscreen mode Exit fullscreen mode

time=None means the birth time is actually unknown.

The engine does not quietly substitute 12:00.

Time-dependent values are omitted instead.

Conceptually:

birth_time_known = False
angles = {}
houses = ()
rising = None
Enter fullscreen mode Exit fullscreen mode

This matters because the Ascendant and houses depend on birth time.

A library should not turn missing input into fake precision just because a complete-looking result is easier to return.

The HTTP API is optional

GetBirthChart also uses the engine behind a FastAPI service.

But someone installing a calculation library should not need FastAPI and Uvicorn unless they actually want the HTTP layer.

The normal installation is:

pip install gbc-astro
Enter fullscreen mode Exit fullscreen mode

For the API dependencies:

pip install "gbc-astro[api]"
Enter fullscreen mode Exit fullscreen mode

So the architecture stays fairly simple:

Python application
       ↓
   gbc-astro

HTTP service
       ↓
FastAPI adapter
       ↓
   gbc-astro
Enter fullscreen mode Exit fullscreen mode

Same engine, different interface.

Package version and engine version are not always the same thing

Packaging also forced me to separate two versions that are easy to confuse.

The current package version is:

1.12.2
Enter fullscreen mode Exit fullscreen mode

The calculation engine version is:

1.12.1
Enter fullscreen mode Exit fullscreen mode

That difference is intentional.

Version 1.12.2 was a packaging and distribution release.

It did not change the underlying astrology calculations.

So I did not bump the calculation engine version just to make the numbers look identical.

I would rather have the versions describe what actually changed.

Making a release traceable

Once the package was public, I also wanted a clear path from an installed Python package back to its source.

The project now has three public representations:

GitHub
  ↓
source code

PyPI
  ↓
installable package

Zenodo
  ↓
archived release
Enter fullscreen mode Exit fullscreen mode

The 1.12.2 source release is archived on Zenodo with this DOI:

10.5281/zenodo.22052875
Enter fullscreen mode Exit fullscreen mode

A DOI does not validate the astrology.

It does not prove that a calculation is correct.

It simply gives this particular software release a persistent, citable identifier.

For open-source infrastructure, that is useful.

Someone looking at the package later can trace it back to a specific archived release instead of relying only on whatever happens to be on the repository's main branch at that time.

Packaging exposed assumptions

The interesting part of turning the engine into a package wasn't uploading a wheel.

It was deciding what the public contract should be.

Which functions should be public?

Which exceptions should developers rely on?

What happens when birth time is missing?

Which dependencies belong in the core package?

Should the HTTP layer be installed by default?

Can a clean Python environment reproduce a real chart?

Can a published package be traced back to its source?

Those questions already existed when the engine was private infrastructure.

Publishing it just made them harder to ignore.

And I think that's one of the useful side effects of open sourcing internal systems.

It forces implicit assumptions to become explicit interfaces.

Try it

Install the package:

pip install gbc-astro
Enter fullscreen mode Exit fullscreen mode

PyPI:

https://pypi.org/project/gbc-astro/1.12.2/

Source code:

https://github.com/getbirthchart-com/gbc-astro-engine

Archived release:

https://zenodo.org/records/22052875

DOI:

https://doi.org/10.5281/zenodo.22052875

GetBirthChart:

https://getbirthchart.com/

I'm going to keep documenting the less obvious parts of building this engine — especially time handling, house calculations, testing, uncertainty, and the boundary between deterministic calculations and AI interpretation.

Top comments (0)