kerykeion 4.8.1__py3-none-any.whl → 4.12.3__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.

@@ -8,6 +8,7 @@ import swisseph as swe
8
8
  import logging
9
9
 
10
10
  from datetime import datetime
11
+ from functools import cached_property
11
12
  from kerykeion.fetch_geonames import FetchGeonames
12
13
  from kerykeion.kr_types import (
13
14
  KerykeionException,
@@ -15,6 +16,10 @@ from kerykeion.kr_types import (
15
16
  AstrologicalSubjectModel,
16
17
  LunarPhaseModel,
17
18
  KerykeionPointModel,
19
+ PointType,
20
+ SiderealMode,
21
+ HousesSystemIdentifier,
22
+ PerspectiveType
18
23
  )
19
24
  from kerykeion.utilities import (
20
25
  get_number_from_name,
@@ -22,11 +27,27 @@ from kerykeion.utilities import (
22
27
  get_planet_house,
23
28
  get_moon_emoji_from_phase_int,
24
29
  get_moon_phase_name_from_phase_int,
30
+ check_and_adjust_polar_latitude
25
31
  )
26
32
  from pathlib import Path
27
- from typing import Union, Literal
33
+ from typing import Union, get_args
28
34
 
29
35
  DEFAULT_GEONAMES_USERNAME = "century.boy"
36
+ DEFAULT_SIDEREAL_MODE = "FAGAN_BRADLEY"
37
+ DEFAULT_HOUSES_SYSTEM_IDENTIFIER = "P"
38
+ DEFAULT_ZODIAC_TYPE = "Tropic"
39
+ DEFAULT_PERSPECTIVE_TYPE = "Apparent Geocentric"
40
+ GEONAMES_DEFAULT_USERNAME_WARNING = (
41
+ "\n********\n"
42
+ "NO GEONAMES USERNAME SET!\n"
43
+ "Using the default geonames username is not recommended, please set a custom one!\n"
44
+ "You can get one for free here:\n"
45
+ "https://www.geonames.org/login\n"
46
+ "Keep in mind that the default username is limited to 2000 requests per hour and is shared with everyone else using this library.\n"
47
+ "********"
48
+ )
49
+
50
+ NOW = datetime.now()
30
51
 
31
52
 
32
53
  class AstrologicalSubject:
@@ -35,34 +56,41 @@ class AstrologicalSubject:
35
56
  it's utc and julian day and returns an object with all that data.
36
57
 
37
58
  Args:
38
- - name (str, optional): _ Defaults to "Now".
39
- - year (int, optional): _ Defaults to now.year.
40
- - month (int, optional): _ Defaults to now.month.
41
- - day (int, optional): _ Defaults to now.day.
42
- - hour (int, optional): _ Defaults to now.hour.
43
- - minute (int, optional): _ Defaults to now.minute.
59
+ - name (str, optional): The name of the subject. Defaults to "Now".
60
+ - year (int, optional): The year of birth. Defaults to the current year.
61
+ - month (int, optional): The month of birth. Defaults to the current month.
62
+ - day (int, optional): The day of birth. Defaults to the current day.
63
+ - hour (int, optional): The hour of birth. Defaults to the current hour.
64
+ - minute (int, optional): Defaults to the current minute.
44
65
  - city (str, optional): City or location of birth. Defaults to "London", which is GMT time.
45
66
  The city argument is used to get the coordinates and timezone from geonames just in case
46
67
  you don't insert them manually (see _get_tz).
47
68
  If you insert the coordinates and timezone manually, the city argument is not used for calculations
48
69
  but it's still used as a value for the city attribute.
49
70
  - nat (str, optional): _ Defaults to "".
50
- - lng (Union[int, float], optional): _ Defaults to False.
51
- - lat (Union[int, float], optional): _ Defaults to False.
52
- - tz_str (Union[str, bool], optional): _ Defaults to False.
53
- - geonames_username (str, optional): _ Defaults to 'century.boy'.
54
- - online (bool, optional): Sets if you want to use the online mode (using
55
- geonames) or not. Defaults to True.
56
- - utc_datetime (datetime, optional): An alternative way of constructing the object,
57
- if you know the UTC datetime but do not have easy access to e.g. timezone identifier
58
- _ Defaults to None.
71
+ - lng (Union[int, float], optional): Longitude of the birth location. Defaults to 0 (Greenwich, London).
72
+ - lat (Union[int, float], optional): Latitude of the birth location. Defaults to 51.5074 (Greenwich, London).
73
+ - tz_str (Union[str, bool], optional): Timezone of the birth location. Defaults to "GMT".
74
+ - geonames_username (str, optional): The username for the geonames API. Note: Change this to your own username to avoid rate limits!
75
+ You can get one for free here: https://www.geonames.org/login
76
+ - online (bool, optional): Sets if you want to use the online mode, which fetches the timezone and coordinates from geonames.
77
+ If you already have the coordinates and timezone, set this to False. Defaults to True.
59
78
  - disable_chiron (bool, optional): Disables the calculation of Chiron. Defaults to False.
60
79
  Chiron calculation can create some issues with the Swiss Ephemeris when the date is too far in the past.
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.
61
90
  """
62
91
 
63
92
  # Defined by the user
64
93
  name: str
65
- utc_datetime: Union[datetime, None]
66
94
  year: int
67
95
  month: int
68
96
  day: int
@@ -76,14 +104,17 @@ class AstrologicalSubject:
76
104
  geonames_username: str
77
105
  online: bool
78
106
  zodiac_type: ZodiacType
107
+ sidereal_mode: SiderealMode
108
+ houses_system_identifier: HousesSystemIdentifier
109
+ houses_system_name: str
110
+ perspective_type: PerspectiveType
79
111
 
80
112
  # Generated internally
81
113
  city_data: dict[str, str]
82
114
  julian_day: Union[int, float]
83
- utc_time: float
84
- local_time: float
85
- utc: datetime
86
115
  json_dir: Path
116
+ iso_formatted_local_datetime: str
117
+ iso_formatted_utc_datetime: str
87
118
 
88
119
  # Planets
89
120
  sun: KerykeionPointModel
@@ -120,36 +151,29 @@ class AstrologicalSubject:
120
151
  planets_degrees_ut: list[float]
121
152
  houses_degree_ut: list[float]
122
153
 
123
- now = datetime.now()
124
-
125
154
  def __init__(
126
155
  self,
127
156
  name="Now",
128
- year: int = now.year,
129
- month: int = now.month,
130
- day: int = now.day,
131
- hour: int = now.hour,
132
- minute: int = now.minute,
157
+ year: int = NOW.year,
158
+ month: int = NOW.month,
159
+ day: int = NOW.day,
160
+ hour: int = NOW.hour,
161
+ minute: int = NOW.minute,
133
162
  city: Union[str, None] = None,
134
163
  nation: Union[str, None] = None,
135
164
  lng: Union[int, float, None] = None,
136
165
  lat: Union[int, float, None] = None,
137
166
  tz_str: Union[str, None] = None,
138
167
  geonames_username: Union[str, None] = None,
139
- zodiac_type: ZodiacType = "Tropic",
168
+ zodiac_type: ZodiacType = DEFAULT_ZODIAC_TYPE,
140
169
  online: bool = True,
141
- utc_datetime: Union[datetime, None] = None,
142
- disable_chiron: bool = False
170
+ disable_chiron: bool = False,
171
+ sidereal_mode: Union[SiderealMode, None] = None,
172
+ houses_system_identifier: HousesSystemIdentifier = DEFAULT_HOUSES_SYSTEM_IDENTIFIER,
173
+ perspective_type: PerspectiveType = DEFAULT_PERSPECTIVE_TYPE
143
174
  ) -> None:
144
175
  logging.debug("Starting Kerykeion")
145
176
 
146
- # We set the swisseph path to the current directory
147
- swe.set_ephe_path(
148
- str(
149
- Path(__file__).parent.absolute() / "sweph"
150
- )
151
- )
152
-
153
177
  self.name = name
154
178
  self.year = year
155
179
  self.month = month
@@ -165,26 +189,19 @@ class AstrologicalSubject:
165
189
  self.online = online
166
190
  self.json_dir = Path.home()
167
191
  self.geonames_username = geonames_username
168
- self.utc_datetime = utc_datetime
169
192
  self.disable_chiron = disable_chiron
193
+ self.sidereal_mode = sidereal_mode
194
+ self.houses_system_identifier = houses_system_identifier
195
+ self.perspective_type = perspective_type
196
+
197
+ #---------------#
198
+ # General setup #
199
+ #---------------#
170
200
 
171
201
  # This message is set to encourage the user to set a custom geonames username
172
202
  if geonames_username is None and online:
173
203
  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
- "********"
204
+
188
205
  )
189
206
 
190
207
  self.geonames_username = DEFAULT_GEONAMES_USERNAME
@@ -206,27 +223,101 @@ class AstrologicalSubject:
206
223
  logging.info("No longitude specified, using London as default")
207
224
 
208
225
  if (not self.online) and (not tz_str):
209
- raise KerykeionException(
210
- "You need to set the coordinates and timezone if you want to use the offline mode!"
211
- )
226
+ raise KerykeionException("You need to set the coordinates and timezone if you want to use the offline mode!")
212
227
 
213
- self._check_if_poles()
228
+ #-----------------------#
229
+ # Swiss Ephemeris setup #
230
+ #-----------------------#
231
+
232
+ # We set the swisseph path to the current directory
233
+ swe.set_ephe_path(str(Path(__file__).parent.absolute() / "sweph"))
234
+
235
+ # Flags for the Swiss Ephemeris
236
+ self._iflag = swe.FLG_SWIEPH + swe.FLG_SPEED
237
+
238
+ # Chart Perspective check and setup --->
239
+ if self.perspective_type not in get_args(PerspectiveType):
240
+ raise KerykeionException(f"\n* ERROR: '{self.perspective_type}' is NOT a valid chart perspective! Available perspectives are: *" + "\n" + str(get_args(PerspectiveType)))
241
+
242
+ if self.perspective_type == "True Geocentric":
243
+ self._iflag += swe.FLG_TRUEPOS
244
+ elif self.perspective_type == "Heliocentric":
245
+ self._iflag += swe.FLG_HELCTR
246
+ elif self.perspective_type == "Topocentric":
247
+ self._iflag += swe.FLG_TOPOCTR
248
+ # geopos_is_set, for topocentric
249
+ swe.set_topo(self.lng, self.lat, 0)
250
+ # <--- Chart Perspective check and setup
251
+
252
+ # House System check and setup --->
253
+ if self.houses_system_identifier not in get_args(HousesSystemIdentifier):
254
+ raise KerykeionException(f"\n* ERROR: '{self.houses_system_identifier}' is NOT a valid house system! Available systems are: *" + "\n" + str(get_args(HousesSystemIdentifier)))
255
+
256
+ self.houses_system_name = swe.house_name(self.houses_system_identifier.encode('ascii'))
257
+ # <--- House System check and setup
258
+
259
+ # Zodiac Type and Sidereal mode checks and setup --->
260
+ if zodiac_type and not zodiac_type in get_args(ZodiacType):
261
+ raise KerykeionException(f"\n* ERROR: '{zodiac_type}' is NOT a valid zodiac type! Available types are: *" + "\n" + str(get_args(ZodiacType)))
262
+
263
+ if self.sidereal_mode and self.zodiac_type == "Tropic":
264
+ raise KerykeionException("You can't set a sidereal mode with a Tropic zodiac type!")
265
+
266
+ if self.zodiac_type == "Sidereal" and not self.sidereal_mode:
267
+ self.sidereal_mode = DEFAULT_SIDEREAL_MODE
268
+ logging.info("No sidereal mode set, using default FAGAN_BRADLEY")
269
+
270
+ if self.zodiac_type == "Sidereal":
271
+ # Check if the sidereal mode is valid
272
+ if not self.sidereal_mode in get_args(SiderealMode):
273
+ raise KerykeionException(f"\n* ERROR: '{self.sidereal_mode}' is NOT a valid sidereal mode! Available modes are: *" + "\n" + str(get_args(SiderealMode)))
274
+
275
+ self._iflag += swe.FLG_SIDEREAL
276
+ mode = "SIDM_" + self.sidereal_mode
277
+ swe.set_sid_mode(getattr(swe, mode))
278
+ logging.debug(f"Using sidereal mode: {mode}")
279
+ # <--- Zodiac Type and Sidereal mode checks and setup
280
+
281
+ #------------------------#
282
+ # Start the calculations #
283
+ #------------------------#
284
+
285
+ check_and_adjust_polar_latitude(self.lat, self.lng)
286
+
287
+ # UTC, julian day and local time setup --->
288
+ if (self.online) and (not self.tz_str):
289
+ self._fetch_tz_from_geonames()
290
+
291
+ # Local time to UTC
292
+ local_time = pytz.timezone(self.tz_str)
293
+ naive_datetime = datetime(self.year, self.month, self.day, self.hour, self.minute, 0)
294
+ local_datetime = local_time.localize(naive_datetime, is_dst=None)
295
+ utc_object = local_datetime.astimezone(pytz.utc)
296
+ self.iso_formatted_utc_datetime = utc_object.isoformat()
297
+
298
+ # ISO formatted local datetime
299
+ self.iso_formatted_local_datetime = local_datetime.isoformat()
300
+
301
+ # Julian day calculation
302
+ utc_float_hour_with_minutes = utc_object.hour + (utc_object.minute / 60)
303
+ self.julian_day = float(swe.julday(utc_object.year, utc_object.month, utc_object.day, utc_float_hour_with_minutes))
304
+ # <--- UTC, julian day and local time setup
214
305
 
215
- # Initialize everything
216
- self._get_utc()
217
- self._get_jd()
218
306
  self._planets_degrees_lister()
219
307
  self._planets()
220
308
  self._houses()
221
-
222
309
  self._planets_in_houses()
223
310
  self._lunar_phase_calc()
224
311
 
312
+ # Deprecated properties
313
+ self.utc_time
314
+ self.local_time
315
+
225
316
  def __str__(self) -> str:
226
- return f"Astrological data for: {self.name}, {self.utc} UTC\nBirth location: {self.city}, Lat {self.lat}, Lon {self.lng}"
317
+ return f"Astrological data for: {self.name}, {self.iso_formatted_utc_datetime} UTC\nBirth location: {self.city}, Lat {self.lat}, Lon {self.lng}"
227
318
 
228
319
  def __repr__(self) -> str:
229
- return f"Astrological data for: {self.name}, {self.utc} UTC\nBirth location: {self.city}, Lat {self.lat}, Lon {self.lng}"
320
+ return f"Astrological data for: {self.name}, {self.iso_formatted_utc_datetime} UTC\nBirth location: {self.city}, Lat {self.lat}, Lon {self.lng}"
230
321
 
231
322
  def __getitem__(self, item):
232
323
  return getattr(self, item)
@@ -258,32 +349,7 @@ class AstrologicalSubject:
258
349
  self.lat = float(self.city_data["lat"])
259
350
  self.tz_str = self.city_data["timezonestr"]
260
351
 
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))
352
+ check_and_adjust_polar_latitude(self.lat, self.lng)
287
353
 
288
354
  def _houses(self) -> None:
289
355
  """
@@ -321,18 +387,21 @@ class AstrologicalSubject:
321
387
 
322
388
  if self.zodiac_type == "Sidereal":
323
389
  self.houses_degree_ut = swe.houses_ex(
324
- tjdut=self.julian_day, lat=self.lat, lon=self.lng, hsys=str.encode('P'), flags=swe.FLG_SIDEREAL
390
+ tjdut=self.julian_day,
391
+ lat=self.lat, lon=self.lng,
392
+ hsys=str.encode(self.houses_system_identifier),
393
+ flags=swe.FLG_SIDEREAL
325
394
  )[0]
395
+
326
396
  elif self.zodiac_type == "Tropic":
327
397
  self.houses_degree_ut = swe.houses(
328
- tjdut=self.julian_day, lat=self.lat, lon=self.lng, hsys=str.encode('P')
398
+ tjdut=self.julian_day, lat=self.lat,
399
+ lon=self.lng,
400
+ hsys=str.encode(self.houses_system_identifier)
329
401
  )[0]
330
- else:
331
- raise KerykeionException("Zodiac type not recognized! Please use 'Tropic' or 'Sidereal'")
332
402
 
333
- point_type: Literal["Planet", "House"] = "House"
334
- # creates the list of the house in 360°
335
- self.houses_degree_ut = swe.houses(self.julian_day, self.lat, self.lng)[0]
403
+ point_type: PointType = "House"
404
+
336
405
  # stores the house in singular dictionaries.
337
406
  self.first_house = calculate_position(self.houses_degree_ut[0], "First_House", point_type=point_type)
338
407
  self.second_house = calculate_position(self.houses_degree_ut[1], "Second_House", point_type=point_type)
@@ -364,12 +433,6 @@ class AstrologicalSubject:
364
433
 
365
434
  def _planets_degrees_lister(self):
366
435
  """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
436
 
374
437
  # Calculates the position of the planets and stores it in a list.
375
438
  sun_deg = swe.calc(self.julian_day, 0, self._iflag)[0][0]
@@ -412,7 +475,7 @@ class AstrologicalSubject:
412
475
  """Defines body positon in signs and information and
413
476
  stores them in dictionaries"""
414
477
 
415
- point_type: Literal["Planet", "House"] = "Planet"
478
+ point_type: PointType = "Planet"
416
479
  # stores the planets in singular dictionaries.
417
480
  self.sun = calculate_position(self.planets_degrees_ut[0], "Sun", point_type=point_type)
418
481
  self.moon = calculate_position(self.planets_degrees_ut[1], "Moon", point_type=point_type)
@@ -474,13 +537,13 @@ class AstrologicalSubject:
474
537
 
475
538
  # Check in retrograde or not:
476
539
  planets_ret = []
477
- for p in self.planets_list:
478
- planet_number = get_number_from_name(p["name"])
540
+ for planet in self.planets_list:
541
+ planet_number = get_number_from_name(planet["name"])
479
542
  if swe.calc(self.julian_day, planet_number, self._iflag)[0][3] < 0:
480
- p["retrograde"] = True
543
+ planet["retrograde"] = True
481
544
  else:
482
- p["retrograde"] = False
483
- planets_ret.append(p)
545
+ planet["retrograde"] = False
546
+ planets_ret.append(planet)
484
547
 
485
548
  def _lunar_phase_calc(self) -> None:
486
549
  """Function to calculate the lunar phase"""
@@ -555,20 +618,7 @@ class AstrologicalSubject:
555
618
 
556
619
  self.lunar_phase = LunarPhaseModel(**lunar_phase_dictionary)
557
620
 
558
- def _check_if_poles(self):
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:
621
+ def json(self, dump=False, destination_folder: Union[str, None] = None, indent: Union[int, None] = None) -> str:
572
622
  """
573
623
  Dumps the Kerykeion object to a json string foramt,
574
624
  if dump=True also dumps to file located in destination
@@ -576,8 +626,7 @@ class AstrologicalSubject:
576
626
  """
577
627
 
578
628
  KrData = AstrologicalSubjectModel(**self.__dict__)
579
- json_string = KrData.model_dump_json(exclude_none=True)
580
- print(json_string)
629
+ json_string = KrData.model_dump_json(exclude_none=True, indent=indent)
581
630
 
582
631
  if dump:
583
632
  if destination_folder:
@@ -600,6 +649,131 @@ class AstrologicalSubject:
600
649
 
601
650
  return AstrologicalSubjectModel(**self.__dict__)
602
651
 
652
+ @cached_property
653
+ def utc_time(self) -> float:
654
+ """
655
+ Deprecated property, use iso_formatted_utc_datetime instead, will be removed in the future.
656
+ Returns the UTC time as a float.
657
+ """
658
+ dt = datetime.fromisoformat(self.iso_formatted_utc_datetime)
659
+
660
+ # Extract the hours, minutes, and seconds
661
+ hours = dt.hour
662
+ minutes = dt.minute
663
+ seconds = dt.second + dt.microsecond / 1_000_000
664
+
665
+ # Convert time to float hours
666
+ float_time = hours + minutes / 60 + seconds / 3600
667
+
668
+ return float_time
669
+
670
+ @cached_property
671
+ def local_time(self) -> float:
672
+ """
673
+ Deprecated property, use iso_formatted_local_datetime instead, will be removed in the future.
674
+ Returns the local time as a float.
675
+ """
676
+ dt = datetime.fromisoformat(self.iso_formatted_local_datetime)
677
+
678
+ # Extract the hours, minutes, and seconds
679
+ hours = dt.hour
680
+ minutes = dt.minute
681
+ seconds = dt.second + dt.microsecond / 1_000_000
682
+
683
+ # Convert time to float hours
684
+ float_time = hours + minutes / 60 + seconds / 3600
685
+
686
+ return float_time
687
+
688
+
689
+ @staticmethod
690
+ def get_from_iso_utc_time(
691
+ name: str,
692
+ iso_utc_time: str,
693
+ city: str = "Greenwich",
694
+ nation: str = "GB",
695
+ tz_str: str = "Etc/GMT",
696
+ online: bool = False,
697
+ lng: Union[int, float] = 0,
698
+ lat: Union[int, float] = 51.5074,
699
+ geonames_username: str = DEFAULT_GEONAMES_USERNAME,
700
+ zodiac_type: ZodiacType = DEFAULT_ZODIAC_TYPE,
701
+ disable_chiron: bool = False,
702
+ sidereal_mode: Union[SiderealMode, None] = None,
703
+ houses_system_identifier: HousesSystemIdentifier = DEFAULT_HOUSES_SYSTEM_IDENTIFIER,
704
+ perspective_type: PerspectiveType = DEFAULT_PERSPECTIVE_TYPE
705
+
706
+ ) -> "AstrologicalSubject":
707
+ """
708
+ Creates an AstrologicalSubject object from an iso formatted UTC time.
709
+ This method is offline by default, set online=True to fetch the timezone and coordinates from geonames.
710
+
711
+ Args:
712
+ - name (str): The name of the subject.
713
+ - iso_utc_time (str): The iso formatted UTC time.
714
+ - city (str, optional): City or location of birth. Defaults to "Greenwich".
715
+ - nation (str, optional): Nation of birth. Defaults to "GB".
716
+ - tz_str (str, optional): Timezone of the birth location. Defaults to "Etc/GMT".
717
+ - online (bool, optional): Sets if you want to use the online mode, which fetches the timezone and coordinates from geonames.
718
+ If you already have the coordinates and timezone, set this to False. Defaults to False.
719
+ - lng (Union[int, float], optional): Longitude of the birth location. Defaults to 0 (Greenwich, London).
720
+ - lat (Union[int, float], optional): Latitude of the birth location. Defaults to 51.5074 (Greenwich, London).
721
+ - geonames_username (str, optional): The username for the geonames API. Note: Change this to your own username to avoid rate limits!
722
+ You can get one for free here: https://www.geonames.org/login
723
+ - zodiac_type (ZodiacType, optional): The zodiac type to use. Defaults to "Tropic".
724
+ - disable_chiron (bool, optional): Disables the calculation of Chiron. Defaults to False.
725
+ Chiron calculation can create some issues with the Swiss Ephemeris when the date is too far in the past.
726
+ - sidereal_mode (SiderealMode, optional): Also known as Ayanamsa.
727
+ The mode to use for the sidereal zodiac, according to the Swiss Ephemeris.
728
+ Defaults to None.
729
+ Available modes are visible in the SiderealMode Literal.
730
+ - houses_system_identifier (HousesSystemIdentifier, optional): The system to use for the calculation of the houses.
731
+ Defaults to "P" (Placidus).
732
+ Available systems are visible in the HousesSystemIdentifier Literal.
733
+ - perspective_type (PerspectiveType, optional): The perspective to use for the calculation of the chart.
734
+ Defaults to "Apparent Geocentric".
735
+
736
+ Returns:
737
+ - AstrologicalSubject: The AstrologicalSubject object.
738
+ """
739
+ dt = datetime.fromisoformat(iso_utc_time)
740
+
741
+ if online == True:
742
+ if geonames_username == DEFAULT_GEONAMES_USERNAME:
743
+ logging.warning(GEONAMES_DEFAULT_USERNAME_WARNING)
744
+
745
+ geonames = FetchGeonames(
746
+ city,
747
+ nation,
748
+ username=geonames_username,
749
+ )
750
+
751
+ city_data: dict[str, str] = geonames.get_serialized_data()
752
+ lng = float(city_data["lng"])
753
+ lat = float(city_data["lat"])
754
+
755
+ subject = AstrologicalSubject(
756
+ name=name,
757
+ year=dt.year,
758
+ month=dt.month,
759
+ day=dt.day,
760
+ hour=dt.hour,
761
+ minute=dt.minute,
762
+ city=city,
763
+ nation=city,
764
+ lng=lng,
765
+ lat=lat,
766
+ tz_str=tz_str,
767
+ online=False,
768
+ geonames_username=geonames_username,
769
+ zodiac_type=zodiac_type,
770
+ disable_chiron=disable_chiron,
771
+ sidereal_mode=sidereal_mode,
772
+ houses_system_identifier=houses_system_identifier,
773
+ perspective_type=perspective_type
774
+ )
775
+
776
+ return subject
603
777
 
604
778
  if __name__ == "__main__":
605
779
  import json
@@ -620,3 +794,22 @@ if __name__ == "__main__":
620
794
 
621
795
  print('\n')
622
796
  print(johnny.chiron)
797
+
798
+ # With Sidereal Zodiac
799
+ johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US", zodiac_type="Sidereal", sidereal_mode="LAHIRI")
800
+ print(johnny.json(dump=True, indent=2))
801
+
802
+ # With Morinus Houses
803
+ johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US", houses_system_identifier="M")
804
+ print(johnny.json(dump=True, indent=2))
805
+
806
+ # With True Geocentric Perspective
807
+ johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US", perspective_type="True Geocentric")
808
+ print(johnny.json(dump=True, indent=2))
809
+
810
+ # With Heliocentric Perspective
811
+ johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US", perspective_type="Heliocentric")
812
+ print(johnny.json(dump=True, indent=2))
813
+
814
+ # With Topocentric Perspective
815
+ johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US", perspective_type="Topocentric")
@@ -126,7 +126,7 @@ def draw_zodiac_slice(
126
126
 
127
127
  Args:
128
128
  - c1 (Union[int, float]): The value of c1.
129
- - chart_type (Literal["Natal", "ExternalNatal", "Synastry", "Transit"]): The type of chart.
129
+ - chart_type (ChartType): The type of chart.
130
130
  - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house.
131
131
  - num (int): The number of the sign. Note: In OpenAstro it did refer to self.zodiac,
132
132
  which is a list of the signs in order, starting with Aries. Eg: