kerykeion 4.8.1__py3-none-any.whl → 4.14.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kerykeion might be problematic. Click here for more details.
- kerykeion/__init__.py +3 -95
- kerykeion/aspects/aspects_utils.py +10 -21
- kerykeion/aspects/natal_aspects.py +9 -2
- kerykeion/aspects/synastry_aspects.py +11 -4
- kerykeion/astrological_subject.py +386 -146
- kerykeion/charts/charts_utils.py +390 -25
- kerykeion/charts/kerykeion_chart_svg.py +162 -439
- kerykeion/charts/templates/chart.xml +381 -358
- kerykeion/enums.py +1 -0
- kerykeion/ephemeris_data.py +174 -0
- kerykeion/fetch_geonames.py +2 -3
- kerykeion/kr_types/chart_types.py +4 -9
- kerykeion/kr_types/kr_literals.py +87 -54
- kerykeion/kr_types/kr_models.py +75 -97
- kerykeion/kr_types/settings_models.py +14 -28
- kerykeion/settings/kr.config.json +19 -5
- kerykeion/utilities.py +40 -23
- {kerykeion-4.8.1.dist-info → kerykeion-4.14.2.dist-info}/METADATA +128 -32
- kerykeion-4.14.2.dist-info/RECORD +33 -0
- kerykeion-4.8.1.dist-info/RECORD +0 -32
- {kerykeion-4.8.1.dist-info → kerykeion-4.14.2.dist-info}/LICENSE +0 -0
- {kerykeion-4.8.1.dist-info → kerykeion-4.14.2.dist-info}/WHEEL +0 -0
- {kerykeion-4.8.1.dist-info → kerykeion-4.14.2.dist-info}/entry_points.txt +0 -0
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
import pytz
|
|
7
7
|
import swisseph as swe
|
|
8
8
|
import logging
|
|
9
|
+
import warnings
|
|
9
10
|
|
|
10
11
|
from datetime import datetime
|
|
12
|
+
from functools import cached_property
|
|
11
13
|
from kerykeion.fetch_geonames import FetchGeonames
|
|
12
14
|
from kerykeion.kr_types import (
|
|
13
15
|
KerykeionException,
|
|
@@ -15,6 +17,10 @@ from kerykeion.kr_types import (
|
|
|
15
17
|
AstrologicalSubjectModel,
|
|
16
18
|
LunarPhaseModel,
|
|
17
19
|
KerykeionPointModel,
|
|
20
|
+
PointType,
|
|
21
|
+
SiderealMode,
|
|
22
|
+
HousesSystemIdentifier,
|
|
23
|
+
PerspectiveType
|
|
18
24
|
)
|
|
19
25
|
from kerykeion.utilities import (
|
|
20
26
|
get_number_from_name,
|
|
@@ -22,11 +28,27 @@ from kerykeion.utilities import (
|
|
|
22
28
|
get_planet_house,
|
|
23
29
|
get_moon_emoji_from_phase_int,
|
|
24
30
|
get_moon_phase_name_from_phase_int,
|
|
31
|
+
check_and_adjust_polar_latitude
|
|
25
32
|
)
|
|
26
33
|
from pathlib import Path
|
|
27
|
-
from typing import Union,
|
|
34
|
+
from typing import Union, get_args
|
|
28
35
|
|
|
29
36
|
DEFAULT_GEONAMES_USERNAME = "century.boy"
|
|
37
|
+
DEFAULT_SIDEREAL_MODE = "FAGAN_BRADLEY"
|
|
38
|
+
DEFAULT_HOUSES_SYSTEM_IDENTIFIER = "P"
|
|
39
|
+
DEFAULT_ZODIAC_TYPE = "Tropic"
|
|
40
|
+
DEFAULT_PERSPECTIVE_TYPE = "Apparent Geocentric"
|
|
41
|
+
GEONAMES_DEFAULT_USERNAME_WARNING = (
|
|
42
|
+
"\n********\n"
|
|
43
|
+
"NO GEONAMES USERNAME SET!\n"
|
|
44
|
+
"Using the default geonames username is not recommended, please set a custom one!\n"
|
|
45
|
+
"You can get one for free here:\n"
|
|
46
|
+
"https://www.geonames.org/login\n"
|
|
47
|
+
"Keep in mind that the default username is limited to 2000 requests per hour and is shared with everyone else using this library.\n"
|
|
48
|
+
"********"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
NOW = datetime.now()
|
|
30
52
|
|
|
31
53
|
|
|
32
54
|
class AstrologicalSubject:
|
|
@@ -35,55 +57,70 @@ class AstrologicalSubject:
|
|
|
35
57
|
it's utc and julian day and returns an object with all that data.
|
|
36
58
|
|
|
37
59
|
Args:
|
|
38
|
-
- name (str, optional):
|
|
39
|
-
- year (int, optional):
|
|
40
|
-
- month (int, optional):
|
|
41
|
-
- day (int, optional):
|
|
42
|
-
- hour (int, optional):
|
|
43
|
-
- minute (int, optional):
|
|
60
|
+
- name (str, optional): The name of the subject. Defaults to "Now".
|
|
61
|
+
- year (int, optional): The year of birth. Defaults to the current year.
|
|
62
|
+
- month (int, optional): The month of birth. Defaults to the current month.
|
|
63
|
+
- day (int, optional): The day of birth. Defaults to the current day.
|
|
64
|
+
- hour (int, optional): The hour of birth. Defaults to the current hour.
|
|
65
|
+
- minute (int, optional): Defaults to the current minute.
|
|
44
66
|
- city (str, optional): City or location of birth. Defaults to "London", which is GMT time.
|
|
45
67
|
The city argument is used to get the coordinates and timezone from geonames just in case
|
|
46
68
|
you don't insert them manually (see _get_tz).
|
|
47
69
|
If you insert the coordinates and timezone manually, the city argument is not used for calculations
|
|
48
70
|
but it's still used as a value for the city attribute.
|
|
49
71
|
- nat (str, optional): _ Defaults to "".
|
|
50
|
-
- lng (Union[int, float], optional):
|
|
51
|
-
- lat (Union[int, float], optional):
|
|
52
|
-
- tz_str (Union[str, bool], optional):
|
|
53
|
-
- geonames_username (str, optional):
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
- lng (Union[int, float], optional): Longitude of the birth location. Defaults to 0 (Greenwich, London).
|
|
73
|
+
- lat (Union[int, float], optional): Latitude of the birth location. Defaults to 51.5074 (Greenwich, London).
|
|
74
|
+
- tz_str (Union[str, bool], optional): Timezone of the birth location. Defaults to "GMT".
|
|
75
|
+
- geonames_username (str, optional): The username for the geonames API. Note: Change this to your own username to avoid rate limits!
|
|
76
|
+
You can get one for free here: https://www.geonames.org/login
|
|
77
|
+
- online (bool, optional): Sets if you want to use the online mode, which fetches the timezone and coordinates from geonames.
|
|
78
|
+
If you already have the coordinates and timezone, set this to False. Defaults to True.
|
|
79
|
+
- disable_chiron: Deprecated, use disable_chiron_and_lilith instead.
|
|
80
|
+
- sidereal_mode (SiderealMode, optional): Also known as Ayanamsa.
|
|
81
|
+
The mode to use for the sidereal zodiac, according to the Swiss Ephemeris.
|
|
82
|
+
Defaults to "FAGAN_BRADLEY".
|
|
83
|
+
Available modes are visible in the SiderealMode Literal.
|
|
84
|
+
- houses_system_identifier (HousesSystemIdentifier, optional): The system to use for the calculation of the houses.
|
|
85
|
+
Defaults to "P" (Placidus).
|
|
86
|
+
Available systems are visible in the HousesSystemIdentifier Literal.
|
|
87
|
+
- perspective_type (PerspectiveType, optional): The perspective to use for the calculation of the chart.
|
|
88
|
+
Defaults to "Apparent Geocentric".
|
|
89
|
+
Available perspectives are visible in the PerspectiveType Literal.
|
|
90
|
+
- is_dst (Union[None, bool], optional): Specify if the time is in DST. Defaults to None.
|
|
91
|
+
By default (None), the library will try to guess if the time is in DST or not and raise an AmbiguousTimeError
|
|
92
|
+
if it can't guess. If you know the time is in DST, set this to True, if you know it's not, set it to False.
|
|
93
|
+
- disable_chiron_and_lilith (bool, optional): boolean representing if Chiron and Lilith should be disabled. Default is False.
|
|
60
94
|
Chiron calculation can create some issues with the Swiss Ephemeris when the date is too far in the past.
|
|
61
95
|
"""
|
|
62
96
|
|
|
63
97
|
# Defined by the user
|
|
64
98
|
name: str
|
|
65
|
-
utc_datetime: Union[datetime, None]
|
|
66
99
|
year: int
|
|
67
100
|
month: int
|
|
68
101
|
day: int
|
|
69
102
|
hour: int
|
|
70
103
|
minute: int
|
|
71
|
-
city: str
|
|
72
|
-
nation: str
|
|
73
|
-
lng: Union[int, float]
|
|
74
|
-
lat: Union[int, float]
|
|
75
|
-
tz_str: str
|
|
76
|
-
geonames_username: str
|
|
104
|
+
city: Union[str, None]
|
|
105
|
+
nation: Union[str, None]
|
|
106
|
+
lng: Union[int, float, None]
|
|
107
|
+
lat: Union[int, float, None]
|
|
108
|
+
tz_str: Union[str, None]
|
|
109
|
+
geonames_username: Union[str, None]
|
|
77
110
|
online: bool
|
|
78
111
|
zodiac_type: ZodiacType
|
|
112
|
+
sidereal_mode: Union[SiderealMode, None]
|
|
113
|
+
houses_system_identifier: HousesSystemIdentifier
|
|
114
|
+
houses_system_name: str
|
|
115
|
+
perspective_type: PerspectiveType
|
|
116
|
+
is_dst: Union[None, bool]
|
|
79
117
|
|
|
80
118
|
# Generated internally
|
|
81
119
|
city_data: dict[str, str]
|
|
82
120
|
julian_day: Union[int, float]
|
|
83
|
-
utc_time: float
|
|
84
|
-
local_time: float
|
|
85
|
-
utc: datetime
|
|
86
121
|
json_dir: Path
|
|
122
|
+
iso_formatted_local_datetime: str
|
|
123
|
+
iso_formatted_utc_datetime: str
|
|
87
124
|
|
|
88
125
|
# Planets
|
|
89
126
|
sun: KerykeionPointModel
|
|
@@ -99,6 +136,7 @@ class AstrologicalSubject:
|
|
|
99
136
|
true_node: KerykeionPointModel
|
|
100
137
|
mean_node: KerykeionPointModel
|
|
101
138
|
chiron: Union[KerykeionPointModel, None]
|
|
139
|
+
mean_lilit: Union[KerykeionPointModel, None]
|
|
102
140
|
|
|
103
141
|
# Houses
|
|
104
142
|
first_house: KerykeionPointModel
|
|
@@ -120,35 +158,48 @@ class AstrologicalSubject:
|
|
|
120
158
|
planets_degrees_ut: list[float]
|
|
121
159
|
houses_degree_ut: list[float]
|
|
122
160
|
|
|
123
|
-
|
|
161
|
+
# Enable or disable features
|
|
162
|
+
disable_chiron: bool # Deprecated
|
|
163
|
+
disable_chiron_and_lilith: bool
|
|
124
164
|
|
|
125
165
|
def __init__(
|
|
126
166
|
self,
|
|
127
167
|
name="Now",
|
|
128
|
-
year: int =
|
|
129
|
-
month: int =
|
|
130
|
-
day: int =
|
|
131
|
-
hour: int =
|
|
132
|
-
minute: int =
|
|
168
|
+
year: int = NOW.year,
|
|
169
|
+
month: int = NOW.month,
|
|
170
|
+
day: int = NOW.day,
|
|
171
|
+
hour: int = NOW.hour,
|
|
172
|
+
minute: int = NOW.minute,
|
|
133
173
|
city: Union[str, None] = None,
|
|
134
174
|
nation: Union[str, None] = None,
|
|
135
175
|
lng: Union[int, float, None] = None,
|
|
136
176
|
lat: Union[int, float, None] = None,
|
|
137
177
|
tz_str: Union[str, None] = None,
|
|
138
178
|
geonames_username: Union[str, None] = None,
|
|
139
|
-
zodiac_type: ZodiacType =
|
|
179
|
+
zodiac_type: ZodiacType = DEFAULT_ZODIAC_TYPE,
|
|
140
180
|
online: bool = True,
|
|
141
|
-
|
|
142
|
-
|
|
181
|
+
disable_chiron: Union[None, bool] = None,
|
|
182
|
+
sidereal_mode: Union[SiderealMode, None] = None,
|
|
183
|
+
houses_system_identifier: HousesSystemIdentifier = DEFAULT_HOUSES_SYSTEM_IDENTIFIER,
|
|
184
|
+
perspective_type: PerspectiveType = DEFAULT_PERSPECTIVE_TYPE,
|
|
185
|
+
is_dst: Union[None, bool] = None,
|
|
186
|
+
disable_chiron_and_lilith: bool = False
|
|
143
187
|
) -> None:
|
|
144
188
|
logging.debug("Starting Kerykeion")
|
|
145
189
|
|
|
146
|
-
#
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
190
|
+
# Deprecation warnings --->
|
|
191
|
+
if disable_chiron is not None:
|
|
192
|
+
warnings.warn(
|
|
193
|
+
"The 'disable_chiron' argument is deprecated and will be removed in a future version. "
|
|
194
|
+
"Please use 'disable_chiron' instead.",
|
|
195
|
+
DeprecationWarning
|
|
150
196
|
)
|
|
151
|
-
|
|
197
|
+
|
|
198
|
+
if disable_chiron_and_lilith:
|
|
199
|
+
raise ValueError("Cannot specify both 'disable_chiron' and 'disable_chiron_and_lilith'. Use 'disable_chiron_and_lilith' only.")
|
|
200
|
+
|
|
201
|
+
self.disable_chiron_and_lilith = disable_chiron
|
|
202
|
+
# <--- Deprecation warnings
|
|
152
203
|
|
|
153
204
|
self.name = name
|
|
154
205
|
self.year = year
|
|
@@ -165,27 +216,20 @@ class AstrologicalSubject:
|
|
|
165
216
|
self.online = online
|
|
166
217
|
self.json_dir = Path.home()
|
|
167
218
|
self.geonames_username = geonames_username
|
|
168
|
-
self.utc_datetime = utc_datetime
|
|
169
219
|
self.disable_chiron = disable_chiron
|
|
220
|
+
self.sidereal_mode = sidereal_mode
|
|
221
|
+
self.houses_system_identifier = houses_system_identifier
|
|
222
|
+
self.perspective_type = perspective_type
|
|
223
|
+
self.is_dst = is_dst
|
|
224
|
+
self.disable_chiron_and_lilith = disable_chiron_and_lilith
|
|
225
|
+
|
|
226
|
+
#---------------#
|
|
227
|
+
# General setup #
|
|
228
|
+
#---------------#
|
|
170
229
|
|
|
171
230
|
# This message is set to encourage the user to set a custom geonames username
|
|
172
231
|
if geonames_username is None and online:
|
|
173
|
-
logging.warning(
|
|
174
|
-
"\n"
|
|
175
|
-
"********" +
|
|
176
|
-
"\n" +
|
|
177
|
-
"NO GEONAMES USERNAME SET!" +
|
|
178
|
-
"\n" +
|
|
179
|
-
"Using the default geonames username is not recommended, please set a custom one!" +
|
|
180
|
-
"\n" +
|
|
181
|
-
"You can get one for free here:" +
|
|
182
|
-
"\n" +
|
|
183
|
-
"https://www.geonames.org/login" +
|
|
184
|
-
"\n" +
|
|
185
|
-
"Keep in mind that the default username is limited to 2000 requests per hour and is shared with everyone else using this library." +
|
|
186
|
-
"\n" +
|
|
187
|
-
"********"
|
|
188
|
-
)
|
|
232
|
+
logging.warning(GEONAMES_DEFAULT_USERNAME_WARNING)
|
|
189
233
|
|
|
190
234
|
self.geonames_username = DEFAULT_GEONAMES_USERNAME
|
|
191
235
|
|
|
@@ -197,36 +241,117 @@ class AstrologicalSubject:
|
|
|
197
241
|
self.nation = "GB"
|
|
198
242
|
logging.info("No nation specified, using GB as default")
|
|
199
243
|
|
|
200
|
-
if not self.lat:
|
|
244
|
+
if not self.lat and not self.online:
|
|
201
245
|
self.lat = 51.5074
|
|
202
246
|
logging.info("No latitude specified, using London as default")
|
|
203
247
|
|
|
204
|
-
if not self.lng:
|
|
248
|
+
if not self.lng and not self.online:
|
|
205
249
|
self.lng = 0
|
|
206
250
|
logging.info("No longitude specified, using London as default")
|
|
207
251
|
|
|
208
252
|
if (not self.online) and (not tz_str):
|
|
209
|
-
raise KerykeionException(
|
|
210
|
-
|
|
211
|
-
|
|
253
|
+
raise KerykeionException("You need to set the coordinates and timezone if you want to use the offline mode!")
|
|
254
|
+
|
|
255
|
+
#-----------------------#
|
|
256
|
+
# Swiss Ephemeris setup #
|
|
257
|
+
#-----------------------#
|
|
258
|
+
|
|
259
|
+
# We set the swisseph path to the current directory
|
|
260
|
+
swe.set_ephe_path(str(Path(__file__).parent.absolute() / "sweph"))
|
|
212
261
|
|
|
213
|
-
|
|
262
|
+
# Flags for the Swiss Ephemeris
|
|
263
|
+
self._iflag = swe.FLG_SWIEPH + swe.FLG_SPEED
|
|
264
|
+
|
|
265
|
+
# Chart Perspective check and setup --->
|
|
266
|
+
if self.perspective_type not in get_args(PerspectiveType):
|
|
267
|
+
raise KerykeionException(f"\n* ERROR: '{self.perspective_type}' is NOT a valid chart perspective! Available perspectives are: *" + "\n" + str(get_args(PerspectiveType)))
|
|
268
|
+
|
|
269
|
+
if self.perspective_type == "True Geocentric":
|
|
270
|
+
self._iflag += swe.FLG_TRUEPOS
|
|
271
|
+
elif self.perspective_type == "Heliocentric":
|
|
272
|
+
self._iflag += swe.FLG_HELCTR
|
|
273
|
+
elif self.perspective_type == "Topocentric":
|
|
274
|
+
self._iflag += swe.FLG_TOPOCTR
|
|
275
|
+
# geopos_is_set, for topocentric
|
|
276
|
+
if (self.online) and (not self.tz_str) and (not self.lat) and (not self.lng):
|
|
277
|
+
self._fetch_and_set_tz_and_coordinates_from_geonames()
|
|
278
|
+
swe.set_topo(self.lng, self.lat, 0)
|
|
279
|
+
# <--- Chart Perspective check and setup
|
|
280
|
+
|
|
281
|
+
# House System check and setup --->
|
|
282
|
+
if self.houses_system_identifier not in get_args(HousesSystemIdentifier):
|
|
283
|
+
raise KerykeionException(f"\n* ERROR: '{self.houses_system_identifier}' is NOT a valid house system! Available systems are: *" + "\n" + str(get_args(HousesSystemIdentifier)))
|
|
284
|
+
|
|
285
|
+
self.houses_system_name = swe.house_name(self.houses_system_identifier.encode('ascii'))
|
|
286
|
+
# <--- House System check and setup
|
|
287
|
+
|
|
288
|
+
# Zodiac Type and Sidereal mode checks and setup --->
|
|
289
|
+
if zodiac_type and not zodiac_type in get_args(ZodiacType):
|
|
290
|
+
raise KerykeionException(f"\n* ERROR: '{zodiac_type}' is NOT a valid zodiac type! Available types are: *" + "\n" + str(get_args(ZodiacType)))
|
|
291
|
+
|
|
292
|
+
if self.sidereal_mode and self.zodiac_type == "Tropic":
|
|
293
|
+
raise KerykeionException("You can't set a sidereal mode with a Tropic zodiac type!")
|
|
294
|
+
|
|
295
|
+
if self.zodiac_type == "Sidereal" and not self.sidereal_mode:
|
|
296
|
+
self.sidereal_mode = DEFAULT_SIDEREAL_MODE
|
|
297
|
+
logging.info("No sidereal mode set, using default FAGAN_BRADLEY")
|
|
298
|
+
|
|
299
|
+
if self.zodiac_type == "Sidereal":
|
|
300
|
+
# Check if the sidereal mode is valid
|
|
301
|
+
if not self.sidereal_mode in get_args(SiderealMode):
|
|
302
|
+
raise KerykeionException(f"\n* ERROR: '{self.sidereal_mode}' is NOT a valid sidereal mode! Available modes are: *" + "\n" + str(get_args(SiderealMode)))
|
|
303
|
+
|
|
304
|
+
self._iflag += swe.FLG_SIDEREAL
|
|
305
|
+
mode = "SIDM_" + self.sidereal_mode
|
|
306
|
+
swe.set_sid_mode(getattr(swe, mode))
|
|
307
|
+
logging.debug(f"Using sidereal mode: {mode}")
|
|
308
|
+
# <--- Zodiac Type and Sidereal mode checks and setup
|
|
309
|
+
|
|
310
|
+
#------------------------#
|
|
311
|
+
# Start the calculations #
|
|
312
|
+
#------------------------#
|
|
313
|
+
|
|
314
|
+
# UTC, julian day and local time setup --->
|
|
315
|
+
if (self.online) and (not self.tz_str) and (not self.lat) and (not self.lng):
|
|
316
|
+
self._fetch_and_set_tz_and_coordinates_from_geonames()
|
|
317
|
+
|
|
318
|
+
self.lat = check_and_adjust_polar_latitude(self.lat)
|
|
319
|
+
|
|
320
|
+
# Local time to UTC
|
|
321
|
+
local_time = pytz.timezone(self.tz_str)
|
|
322
|
+
naive_datetime = datetime(self.year, self.month, self.day, self.hour, self.minute, 0)
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
local_datetime = local_time.localize(naive_datetime, is_dst=self.is_dst)
|
|
326
|
+
except pytz.exceptions.AmbiguousTimeError:
|
|
327
|
+
raise KerykeionException("Ambiguous time! Please specify if the time is in DST or not with the is_dst argument.")
|
|
328
|
+
|
|
329
|
+
utc_object = local_datetime.astimezone(pytz.utc)
|
|
330
|
+
self.iso_formatted_utc_datetime = utc_object.isoformat()
|
|
331
|
+
|
|
332
|
+
# ISO formatted local datetime
|
|
333
|
+
self.iso_formatted_local_datetime = local_datetime.isoformat()
|
|
334
|
+
|
|
335
|
+
# Julian day calculation
|
|
336
|
+
utc_float_hour_with_minutes = utc_object.hour + (utc_object.minute / 60)
|
|
337
|
+
self.julian_day = float(swe.julday(utc_object.year, utc_object.month, utc_object.day, utc_float_hour_with_minutes))
|
|
338
|
+
# <--- UTC, julian day and local time setup
|
|
214
339
|
|
|
215
|
-
# Initialize everything
|
|
216
|
-
self._get_utc()
|
|
217
|
-
self._get_jd()
|
|
218
340
|
self._planets_degrees_lister()
|
|
219
341
|
self._planets()
|
|
220
342
|
self._houses()
|
|
221
|
-
|
|
222
343
|
self._planets_in_houses()
|
|
223
344
|
self._lunar_phase_calc()
|
|
224
345
|
|
|
346
|
+
# Deprecated properties
|
|
347
|
+
self.utc_time
|
|
348
|
+
self.local_time
|
|
349
|
+
|
|
225
350
|
def __str__(self) -> str:
|
|
226
|
-
return f"Astrological data for: {self.name}, {self.
|
|
351
|
+
return f"Astrological data for: {self.name}, {self.iso_formatted_utc_datetime} UTC\nBirth location: {self.city}, Lat {self.lat}, Lon {self.lng}"
|
|
227
352
|
|
|
228
353
|
def __repr__(self) -> str:
|
|
229
|
-
return f"Astrological data for: {self.name}, {self.
|
|
354
|
+
return f"Astrological data for: {self.name}, {self.iso_formatted_utc_datetime} UTC\nBirth location: {self.city}, Lat {self.lat}, Lon {self.lng}"
|
|
230
355
|
|
|
231
356
|
def __getitem__(self, item):
|
|
232
357
|
return getattr(self, item)
|
|
@@ -234,7 +359,7 @@ class AstrologicalSubject:
|
|
|
234
359
|
def get(self, item, default=None):
|
|
235
360
|
return getattr(self, item, default)
|
|
236
361
|
|
|
237
|
-
def
|
|
362
|
+
def _fetch_and_set_tz_and_coordinates_from_geonames(self) -> None:
|
|
238
363
|
"""Gets the nearest time zone for the calculation"""
|
|
239
364
|
logging.info("Fetching timezone/coordinates from geonames")
|
|
240
365
|
|
|
@@ -258,33 +383,6 @@ class AstrologicalSubject:
|
|
|
258
383
|
self.lat = float(self.city_data["lat"])
|
|
259
384
|
self.tz_str = self.city_data["timezonestr"]
|
|
260
385
|
|
|
261
|
-
self._check_if_poles()
|
|
262
|
-
|
|
263
|
-
def _get_utc(self) -> None:
|
|
264
|
-
"""Converts local time to utc time."""
|
|
265
|
-
|
|
266
|
-
# If the coordinates are not set, get them from geonames.
|
|
267
|
-
if (self.online) and (not self.tz_str):
|
|
268
|
-
self._fetch_tz_from_geonames()
|
|
269
|
-
|
|
270
|
-
# If UTC datetime is provided, then use it directly
|
|
271
|
-
if (self.utc_datetime):
|
|
272
|
-
self.utc = self.utc_datetime
|
|
273
|
-
return
|
|
274
|
-
|
|
275
|
-
local_time = pytz.timezone(self.tz_str)
|
|
276
|
-
|
|
277
|
-
naive_datetime = datetime(self.year, self.month, self.day, self.hour, self.minute, 0)
|
|
278
|
-
|
|
279
|
-
local_datetime = local_time.localize(naive_datetime, is_dst=None)
|
|
280
|
-
self.utc = local_datetime.astimezone(pytz.utc)
|
|
281
|
-
|
|
282
|
-
def _get_jd(self) -> None:
|
|
283
|
-
"""Calculates julian day from the utc time."""
|
|
284
|
-
self.utc_time = self.utc.hour + self.utc.minute / 60
|
|
285
|
-
self.local_time = self.hour + self.minute / 60
|
|
286
|
-
self.julian_day = float(swe.julday(self.utc.year, self.utc.month, self.utc.day, self.utc_time))
|
|
287
|
-
|
|
288
386
|
def _houses(self) -> None:
|
|
289
387
|
"""
|
|
290
388
|
Calculate positions and store them in dictionaries
|
|
@@ -321,18 +419,21 @@ class AstrologicalSubject:
|
|
|
321
419
|
|
|
322
420
|
if self.zodiac_type == "Sidereal":
|
|
323
421
|
self.houses_degree_ut = swe.houses_ex(
|
|
324
|
-
tjdut=self.julian_day,
|
|
422
|
+
tjdut=self.julian_day,
|
|
423
|
+
lat=self.lat, lon=self.lng,
|
|
424
|
+
hsys=str.encode(self.houses_system_identifier),
|
|
425
|
+
flags=swe.FLG_SIDEREAL
|
|
325
426
|
)[0]
|
|
427
|
+
|
|
326
428
|
elif self.zodiac_type == "Tropic":
|
|
327
429
|
self.houses_degree_ut = swe.houses(
|
|
328
|
-
tjdut=self.julian_day, lat=self.lat,
|
|
430
|
+
tjdut=self.julian_day, lat=self.lat,
|
|
431
|
+
lon=self.lng,
|
|
432
|
+
hsys=str.encode(self.houses_system_identifier)
|
|
329
433
|
)[0]
|
|
330
|
-
else:
|
|
331
|
-
raise KerykeionException("Zodiac type not recognized! Please use 'Tropic' or 'Sidereal'")
|
|
332
434
|
|
|
333
|
-
point_type:
|
|
334
|
-
|
|
335
|
-
self.houses_degree_ut = swe.houses(self.julian_day, self.lat, self.lng)[0]
|
|
435
|
+
point_type: PointType = "House"
|
|
436
|
+
|
|
336
437
|
# stores the house in singular dictionaries.
|
|
337
438
|
self.first_house = calculate_position(self.houses_degree_ut[0], "First_House", point_type=point_type)
|
|
338
439
|
self.second_house = calculate_position(self.houses_degree_ut[1], "Second_House", point_type=point_type)
|
|
@@ -364,12 +465,6 @@ class AstrologicalSubject:
|
|
|
364
465
|
|
|
365
466
|
def _planets_degrees_lister(self):
|
|
366
467
|
"""Sidereal or tropic mode."""
|
|
367
|
-
self._iflag = swe.FLG_SWIEPH + swe.FLG_SPEED
|
|
368
|
-
|
|
369
|
-
if self.zodiac_type == "Sidereal":
|
|
370
|
-
self._iflag += swe.FLG_SIDEREAL
|
|
371
|
-
mode = "SIDM_FAGAN_BRADLEY"
|
|
372
|
-
swe.set_sid_mode(getattr(swe, mode))
|
|
373
468
|
|
|
374
469
|
# Calculates the position of the planets and stores it in a list.
|
|
375
470
|
sun_deg = swe.calc(self.julian_day, 0, self._iflag)[0][0]
|
|
@@ -384,11 +479,6 @@ class AstrologicalSubject:
|
|
|
384
479
|
pluto_deg = swe.calc(self.julian_day, 9, self._iflag)[0][0]
|
|
385
480
|
mean_node_deg = swe.calc(self.julian_day, 10, self._iflag)[0][0]
|
|
386
481
|
true_node_deg = swe.calc(self.julian_day, 11, self._iflag)[0][0]
|
|
387
|
-
|
|
388
|
-
if not self.disable_chiron:
|
|
389
|
-
chiron_deg = swe.calc(self.julian_day, 15, self._iflag)[0][0]
|
|
390
|
-
else:
|
|
391
|
-
chiron_deg = 0
|
|
392
482
|
|
|
393
483
|
self.planets_degrees_ut = [
|
|
394
484
|
sun_deg,
|
|
@@ -405,14 +495,23 @@ class AstrologicalSubject:
|
|
|
405
495
|
true_node_deg,
|
|
406
496
|
]
|
|
407
497
|
|
|
408
|
-
if not self.
|
|
498
|
+
if not self.disable_chiron_and_lilith:
|
|
499
|
+
chiron_deg = swe.calc(self.julian_day, 15, self._iflag)[0][0]
|
|
409
500
|
self.planets_degrees_ut.append(chiron_deg)
|
|
410
501
|
|
|
502
|
+
mean_lilith_deg = swe.calc(self.julian_day, 12, self._iflag)[0][0]
|
|
503
|
+
self.planets_degrees_ut.append(mean_lilith_deg)
|
|
504
|
+
|
|
505
|
+
else:
|
|
506
|
+
self.chiron = None
|
|
507
|
+
self.mean_lilith = None
|
|
508
|
+
|
|
509
|
+
|
|
411
510
|
def _planets(self) -> None:
|
|
412
511
|
"""Defines body positon in signs and information and
|
|
413
512
|
stores them in dictionaries"""
|
|
414
513
|
|
|
415
|
-
point_type:
|
|
514
|
+
point_type: PointType = "Planet"
|
|
416
515
|
# stores the planets in singular dictionaries.
|
|
417
516
|
self.sun = calculate_position(self.planets_degrees_ut[0], "Sun", point_type=point_type)
|
|
418
517
|
self.moon = calculate_position(self.planets_degrees_ut[1], "Moon", point_type=point_type)
|
|
@@ -427,10 +526,14 @@ class AstrologicalSubject:
|
|
|
427
526
|
self.mean_node = calculate_position(self.planets_degrees_ut[10], "Mean_Node", point_type=point_type)
|
|
428
527
|
self.true_node = calculate_position(self.planets_degrees_ut[11], "True_Node", point_type=point_type)
|
|
429
528
|
|
|
430
|
-
if not self.
|
|
529
|
+
if not self.disable_chiron_and_lilith:
|
|
431
530
|
self.chiron = calculate_position(self.planets_degrees_ut[12], "Chiron", point_type=point_type)
|
|
531
|
+
self.mean_lilith = calculate_position(self.planets_degrees_ut[13], "Mean_Lilith", point_type=point_type)
|
|
532
|
+
|
|
432
533
|
else:
|
|
433
534
|
self.chiron = None
|
|
535
|
+
self.mean_lilith = None
|
|
536
|
+
|
|
434
537
|
|
|
435
538
|
def _planets_in_houses(self) -> None:
|
|
436
539
|
"""Calculates the house of the planet and updates
|
|
@@ -449,11 +552,6 @@ class AstrologicalSubject:
|
|
|
449
552
|
self.mean_node.house = get_planet_house(self.planets_degrees_ut[10], self.houses_degree_ut)
|
|
450
553
|
self.true_node.house = get_planet_house(self.planets_degrees_ut[11], self.houses_degree_ut)
|
|
451
554
|
|
|
452
|
-
if not self.disable_chiron:
|
|
453
|
-
self.chiron.house = get_planet_house(self.planets_degrees_ut[12], self.houses_degree_ut)
|
|
454
|
-
else:
|
|
455
|
-
self.chiron = None
|
|
456
|
-
|
|
457
555
|
self.planets_list = [
|
|
458
556
|
self.sun,
|
|
459
557
|
self.moon,
|
|
@@ -469,18 +567,26 @@ class AstrologicalSubject:
|
|
|
469
567
|
self.true_node,
|
|
470
568
|
]
|
|
471
569
|
|
|
472
|
-
if not self.
|
|
570
|
+
if not self.disable_chiron_and_lilith:
|
|
571
|
+
self.chiron.house = get_planet_house(self.planets_degrees_ut[12], self.houses_degree_ut)
|
|
473
572
|
self.planets_list.append(self.chiron)
|
|
474
573
|
|
|
574
|
+
self.mean_lilith.house = get_planet_house(self.planets_degrees_ut[13], self.houses_degree_ut)
|
|
575
|
+
self.planets_list.append(self.mean_lilith)
|
|
576
|
+
|
|
577
|
+
else:
|
|
578
|
+
self.chiron = None
|
|
579
|
+
self.mean_lilith = None
|
|
580
|
+
|
|
475
581
|
# Check in retrograde or not:
|
|
476
582
|
planets_ret = []
|
|
477
|
-
for
|
|
478
|
-
planet_number = get_number_from_name(
|
|
583
|
+
for planet in self.planets_list:
|
|
584
|
+
planet_number = get_number_from_name(planet["name"])
|
|
479
585
|
if swe.calc(self.julian_day, planet_number, self._iflag)[0][3] < 0:
|
|
480
|
-
|
|
586
|
+
planet["retrograde"] = True
|
|
481
587
|
else:
|
|
482
|
-
|
|
483
|
-
planets_ret.append(
|
|
588
|
+
planet["retrograde"] = False
|
|
589
|
+
planets_ret.append(planet)
|
|
484
590
|
|
|
485
591
|
def _lunar_phase_calc(self) -> None:
|
|
486
592
|
"""Function to calculate the lunar phase"""
|
|
@@ -555,20 +661,7 @@ class AstrologicalSubject:
|
|
|
555
661
|
|
|
556
662
|
self.lunar_phase = LunarPhaseModel(**lunar_phase_dictionary)
|
|
557
663
|
|
|
558
|
-
def
|
|
559
|
-
"""
|
|
560
|
-
Utility function to check if the location is in the polar circle.
|
|
561
|
-
If it is, it sets the latitude to 66 or -66 degrees.
|
|
562
|
-
"""
|
|
563
|
-
if self.lat > 66.0:
|
|
564
|
-
self.lat = 66.0
|
|
565
|
-
logging.info("Polar circle override for houses, using 66 degrees")
|
|
566
|
-
|
|
567
|
-
elif self.lat < -66.0:
|
|
568
|
-
self.lat = -66.0
|
|
569
|
-
logging.info("Polar circle override for houses, using -66 degrees")
|
|
570
|
-
|
|
571
|
-
def json(self, dump=False, destination_folder: Union[str, None] = None) -> str:
|
|
664
|
+
def json(self, dump=False, destination_folder: Union[str, None] = None, indent: Union[int, None] = None) -> str:
|
|
572
665
|
"""
|
|
573
666
|
Dumps the Kerykeion object to a json string foramt,
|
|
574
667
|
if dump=True also dumps to file located in destination
|
|
@@ -576,8 +669,7 @@ class AstrologicalSubject:
|
|
|
576
669
|
"""
|
|
577
670
|
|
|
578
671
|
KrData = AstrologicalSubjectModel(**self.__dict__)
|
|
579
|
-
json_string = KrData.model_dump_json(exclude_none=True)
|
|
580
|
-
print(json_string)
|
|
672
|
+
json_string = KrData.model_dump_json(exclude_none=True, indent=indent)
|
|
581
673
|
|
|
582
674
|
if dump:
|
|
583
675
|
if destination_folder:
|
|
@@ -600,6 +692,131 @@ class AstrologicalSubject:
|
|
|
600
692
|
|
|
601
693
|
return AstrologicalSubjectModel(**self.__dict__)
|
|
602
694
|
|
|
695
|
+
@cached_property
|
|
696
|
+
def utc_time(self) -> float:
|
|
697
|
+
"""
|
|
698
|
+
Deprecated property, use iso_formatted_utc_datetime instead, will be removed in the future.
|
|
699
|
+
Returns the UTC time as a float.
|
|
700
|
+
"""
|
|
701
|
+
dt = datetime.fromisoformat(self.iso_formatted_utc_datetime)
|
|
702
|
+
|
|
703
|
+
# Extract the hours, minutes, and seconds
|
|
704
|
+
hours = dt.hour
|
|
705
|
+
minutes = dt.minute
|
|
706
|
+
seconds = dt.second + dt.microsecond / 1_000_000
|
|
707
|
+
|
|
708
|
+
# Convert time to float hours
|
|
709
|
+
float_time = hours + minutes / 60 + seconds / 3600
|
|
710
|
+
|
|
711
|
+
return float_time
|
|
712
|
+
|
|
713
|
+
@cached_property
|
|
714
|
+
def local_time(self) -> float:
|
|
715
|
+
"""
|
|
716
|
+
Deprecated property, use iso_formatted_local_datetime instead, will be removed in the future.
|
|
717
|
+
Returns the local time as a float.
|
|
718
|
+
"""
|
|
719
|
+
dt = datetime.fromisoformat(self.iso_formatted_local_datetime)
|
|
720
|
+
|
|
721
|
+
# Extract the hours, minutes, and seconds
|
|
722
|
+
hours = dt.hour
|
|
723
|
+
minutes = dt.minute
|
|
724
|
+
seconds = dt.second + dt.microsecond / 1_000_000
|
|
725
|
+
|
|
726
|
+
# Convert time to float hours
|
|
727
|
+
float_time = hours + minutes / 60 + seconds / 3600
|
|
728
|
+
|
|
729
|
+
return float_time
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
@staticmethod
|
|
733
|
+
def get_from_iso_utc_time(
|
|
734
|
+
name: str,
|
|
735
|
+
iso_utc_time: str,
|
|
736
|
+
city: str = "Greenwich",
|
|
737
|
+
nation: str = "GB",
|
|
738
|
+
tz_str: str = "Etc/GMT",
|
|
739
|
+
online: bool = False,
|
|
740
|
+
lng: Union[int, float] = 0,
|
|
741
|
+
lat: Union[int, float] = 51.5074,
|
|
742
|
+
geonames_username: str = DEFAULT_GEONAMES_USERNAME,
|
|
743
|
+
zodiac_type: ZodiacType = DEFAULT_ZODIAC_TYPE,
|
|
744
|
+
disable_chiron_and_lilith: bool = False,
|
|
745
|
+
sidereal_mode: Union[SiderealMode, None] = None,
|
|
746
|
+
houses_system_identifier: HousesSystemIdentifier = DEFAULT_HOUSES_SYSTEM_IDENTIFIER,
|
|
747
|
+
perspective_type: PerspectiveType = DEFAULT_PERSPECTIVE_TYPE
|
|
748
|
+
|
|
749
|
+
) -> "AstrologicalSubject":
|
|
750
|
+
"""
|
|
751
|
+
Creates an AstrologicalSubject object from an iso formatted UTC time.
|
|
752
|
+
This method is offline by default, set online=True to fetch the timezone and coordinates from geonames.
|
|
753
|
+
|
|
754
|
+
Args:
|
|
755
|
+
- name (str): The name of the subject.
|
|
756
|
+
- iso_utc_time (str): The iso formatted UTC time.
|
|
757
|
+
- city (str, optional): City or location of birth. Defaults to "Greenwich".
|
|
758
|
+
- nation (str, optional): Nation of birth. Defaults to "GB".
|
|
759
|
+
- tz_str (str, optional): Timezone of the birth location. Defaults to "Etc/GMT".
|
|
760
|
+
- online (bool, optional): Sets if you want to use the online mode, which fetches the timezone and coordinates from geonames.
|
|
761
|
+
If you already have the coordinates and timezone, set this to False. Defaults to False.
|
|
762
|
+
- lng (Union[int, float], optional): Longitude of the birth location. Defaults to 0 (Greenwich, London).
|
|
763
|
+
- lat (Union[int, float], optional): Latitude of the birth location. Defaults to 51.5074 (Greenwich, London).
|
|
764
|
+
- geonames_username (str, optional): The username for the geonames API. Note: Change this to your own username to avoid rate limits!
|
|
765
|
+
You can get one for free here: https://www.geonames.org/login
|
|
766
|
+
- zodiac_type (ZodiacType, optional): The zodiac type to use. Defaults to "Tropic".
|
|
767
|
+
- disable_chiron_and_lilith: boolean representing if Chiron and Lilith should be disabled. Default is False.
|
|
768
|
+
Chiron calculation can create some issues with the Swiss Ephemeris when the date is too far in the past.
|
|
769
|
+
- sidereal_mode (SiderealMode, optional): Also known as Ayanamsa.
|
|
770
|
+
The mode to use for the sidereal zodiac, according to the Swiss Ephemeris.
|
|
771
|
+
Defaults to None.
|
|
772
|
+
Available modes are visible in the SiderealMode Literal.
|
|
773
|
+
- houses_system_identifier (HousesSystemIdentifier, optional): The system to use for the calculation of the houses.
|
|
774
|
+
Defaults to "P" (Placidus).
|
|
775
|
+
Available systems are visible in the HousesSystemIdentifier Literal.
|
|
776
|
+
- perspective_type (PerspectiveType, optional): The perspective to use for the calculation of the chart.
|
|
777
|
+
Defaults to "Apparent Geocentric".
|
|
778
|
+
|
|
779
|
+
Returns:
|
|
780
|
+
- AstrologicalSubject: The AstrologicalSubject object.
|
|
781
|
+
"""
|
|
782
|
+
dt = datetime.fromisoformat(iso_utc_time)
|
|
783
|
+
|
|
784
|
+
if online == True:
|
|
785
|
+
if geonames_username == DEFAULT_GEONAMES_USERNAME:
|
|
786
|
+
logging.warning(GEONAMES_DEFAULT_USERNAME_WARNING)
|
|
787
|
+
|
|
788
|
+
geonames = FetchGeonames(
|
|
789
|
+
city,
|
|
790
|
+
nation,
|
|
791
|
+
username=geonames_username,
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
city_data: dict[str, str] = geonames.get_serialized_data()
|
|
795
|
+
lng = float(city_data["lng"])
|
|
796
|
+
lat = float(city_data["lat"])
|
|
797
|
+
|
|
798
|
+
subject = AstrologicalSubject(
|
|
799
|
+
name=name,
|
|
800
|
+
year=dt.year,
|
|
801
|
+
month=dt.month,
|
|
802
|
+
day=dt.day,
|
|
803
|
+
hour=dt.hour,
|
|
804
|
+
minute=dt.minute,
|
|
805
|
+
city=city,
|
|
806
|
+
nation=city,
|
|
807
|
+
lng=lng,
|
|
808
|
+
lat=lat,
|
|
809
|
+
tz_str=tz_str,
|
|
810
|
+
online=False,
|
|
811
|
+
geonames_username=geonames_username,
|
|
812
|
+
zodiac_type=zodiac_type,
|
|
813
|
+
sidereal_mode=sidereal_mode,
|
|
814
|
+
houses_system_identifier=houses_system_identifier,
|
|
815
|
+
perspective_type=perspective_type,
|
|
816
|
+
disable_chiron_and_lilith=disable_chiron_and_lilith
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
return subject
|
|
603
820
|
|
|
604
821
|
if __name__ == "__main__":
|
|
605
822
|
import json
|
|
@@ -620,3 +837,26 @@ if __name__ == "__main__":
|
|
|
620
837
|
|
|
621
838
|
print('\n')
|
|
622
839
|
print(johnny.chiron)
|
|
840
|
+
|
|
841
|
+
# With Sidereal Zodiac
|
|
842
|
+
johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US", zodiac_type="Sidereal", sidereal_mode="LAHIRI")
|
|
843
|
+
print(johnny.json(dump=True, indent=2))
|
|
844
|
+
|
|
845
|
+
# With Morinus Houses
|
|
846
|
+
johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US", houses_system_identifier="M")
|
|
847
|
+
print(johnny.json(dump=True, indent=2))
|
|
848
|
+
|
|
849
|
+
# With True Geocentric Perspective
|
|
850
|
+
johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US", perspective_type="True Geocentric")
|
|
851
|
+
print(johnny.json(dump=True, indent=2))
|
|
852
|
+
|
|
853
|
+
# With Heliocentric Perspective
|
|
854
|
+
johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US", perspective_type="Heliocentric")
|
|
855
|
+
print(johnny.json(dump=True, indent=2))
|
|
856
|
+
|
|
857
|
+
# With Topocentric Perspective
|
|
858
|
+
johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US", perspective_type="Topocentric")
|
|
859
|
+
|
|
860
|
+
# Test Mean Lilith
|
|
861
|
+
johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US", disable_chiron_and_lilith=True)
|
|
862
|
+
print(johnny.mean_lilith)
|