kerykeion 5.0.0a9__py3-none-any.whl → 5.1.8__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 +50 -9
- kerykeion/aspects/__init__.py +5 -2
- kerykeion/aspects/aspects_factory.py +568 -0
- kerykeion/aspects/aspects_utils.py +78 -11
- kerykeion/astrological_subject_factory.py +1032 -275
- kerykeion/backword.py +820 -0
- kerykeion/chart_data_factory.py +552 -0
- kerykeion/charts/chart_drawer.py +2661 -0
- kerykeion/charts/charts_utils.py +652 -399
- kerykeion/charts/draw_planets.py +603 -353
- kerykeion/charts/templates/aspect_grid_only.xml +326 -198
- kerykeion/charts/templates/chart.xml +306 -256
- kerykeion/charts/templates/wheel_only.xml +330 -200
- kerykeion/charts/themes/black-and-white.css +148 -0
- kerykeion/charts/themes/classic.css +11 -0
- kerykeion/charts/themes/dark-high-contrast.css +11 -0
- kerykeion/charts/themes/dark.css +11 -0
- kerykeion/charts/themes/light.css +11 -0
- kerykeion/charts/themes/strawberry.css +10 -0
- kerykeion/composite_subject_factory.py +232 -13
- kerykeion/ephemeris_data_factory.py +443 -0
- kerykeion/fetch_geonames.py +78 -21
- kerykeion/house_comparison/__init__.py +4 -1
- kerykeion/house_comparison/house_comparison_factory.py +52 -19
- kerykeion/house_comparison/house_comparison_utils.py +37 -9
- kerykeion/kr_types/__init__.py +66 -6
- kerykeion/kr_types/chart_template_model.py +20 -0
- kerykeion/kr_types/kerykeion_exception.py +15 -9
- kerykeion/kr_types/kr_literals.py +14 -160
- kerykeion/kr_types/kr_models.py +14 -291
- kerykeion/kr_types/settings_models.py +15 -167
- kerykeion/planetary_return_factory.py +545 -40
- kerykeion/relationship_score_factory.py +137 -63
- kerykeion/report.py +749 -64
- kerykeion/schemas/__init__.py +106 -0
- kerykeion/schemas/chart_template_model.py +367 -0
- kerykeion/schemas/kerykeion_exception.py +20 -0
- kerykeion/schemas/kr_literals.py +181 -0
- kerykeion/schemas/kr_models.py +603 -0
- kerykeion/schemas/settings_models.py +188 -0
- kerykeion/settings/__init__.py +20 -1
- kerykeion/settings/chart_defaults.py +444 -0
- kerykeion/settings/config_constants.py +88 -12
- kerykeion/settings/kerykeion_settings.py +32 -75
- kerykeion/settings/translation_strings.py +1499 -0
- kerykeion/settings/translations.py +74 -0
- kerykeion/sweph/ast136/s136108s.se1 +0 -0
- kerykeion/sweph/ast136/s136199s.se1 +0 -0
- kerykeion/sweph/ast136/s136472s.se1 +0 -0
- kerykeion/sweph/ast28/se28978s.se1 +0 -0
- kerykeion/sweph/ast50/se50000s.se1 +0 -0
- kerykeion/sweph/ast90/se90377s.se1 +0 -0
- kerykeion/sweph/ast90/se90482s.se1 +0 -0
- kerykeion/sweph/sefstars.txt +1602 -0
- kerykeion/transits_time_range_factory.py +302 -0
- kerykeion/utilities.py +289 -204
- kerykeion-5.1.8.dist-info/METADATA +1793 -0
- kerykeion-5.1.8.dist-info/RECORD +63 -0
- kerykeion/aspects/natal_aspects.py +0 -181
- kerykeion/aspects/synastry_aspects.py +0 -141
- kerykeion/aspects/transits_time_range.py +0 -41
- kerykeion/charts/draw_planets_v2.py +0 -649
- kerykeion/charts/draw_planets_v3.py +0 -679
- kerykeion/charts/kerykeion_chart_svg.py +0 -2038
- kerykeion/enums.py +0 -57
- kerykeion/ephemeris_data.py +0 -238
- kerykeion/house_comparison/house_comparison_models.py +0 -38
- kerykeion/kr_types/chart_types.py +0 -106
- kerykeion/settings/kr.config.json +0 -1304
- kerykeion/settings/legacy/__init__.py +0 -0
- kerykeion/settings/legacy/legacy_celestial_points_settings.py +0 -299
- kerykeion/settings/legacy/legacy_chart_aspects_settings.py +0 -71
- kerykeion/settings/legacy/legacy_color_settings.py +0 -42
- kerykeion/transits_time_range.py +0 -128
- kerykeion-5.0.0a9.dist-info/METADATA +0 -636
- kerykeion-5.0.0a9.dist-info/RECORD +0 -55
- kerykeion-5.0.0a9.dist-info/entry_points.txt +0 -2
- {kerykeion-5.0.0a9.dist-info → kerykeion-5.1.8.dist-info}/WHEEL +0 -0
- {kerykeion-5.0.0a9.dist-info → kerykeion-5.1.8.dist-info}/licenses/LICENSE +0 -0
kerykeion/backword.py
ADDED
|
@@ -0,0 +1,820 @@
|
|
|
1
|
+
"""Backward compatibility shims for legacy Kerykeion v4 public API.
|
|
2
|
+
|
|
3
|
+
This module provides wrapper classes and aliases that emulate the old
|
|
4
|
+
(v4 and earlier) interfaces while internally delegating to the new v5
|
|
5
|
+
factory/data oriented architecture.
|
|
6
|
+
|
|
7
|
+
Import pattern supported (legacy):
|
|
8
|
+
from kerykeion import AstrologicalSubject, KerykeionChartSVG, SynastryAspects
|
|
9
|
+
|
|
10
|
+
New architecture summary:
|
|
11
|
+
- AstrologicalSubjectFactory.from_birth_data(...) returns an AstrologicalSubjectModel
|
|
12
|
+
- ChartDataFactory + ChartDrawer replace KerykeionChartSVG direct chart building
|
|
13
|
+
- AspectsFactory provides both single and dual chart aspects
|
|
14
|
+
|
|
15
|
+
Classes provided here:
|
|
16
|
+
AstrologicalSubject (wrapper around AstrologicalSubjectFactory)
|
|
17
|
+
KerykeionChartSVG (wrapper producing SVGs via ChartDataFactory + ChartDrawer)
|
|
18
|
+
SynastryAspects (wrapper over AspectsFactory.dual_chart_aspects)
|
|
19
|
+
|
|
20
|
+
Deprecation: Each class issues a DeprecationWarning guiding users to the
|
|
21
|
+
replacement APIs. They are intentionally minimal; only the most used
|
|
22
|
+
attributes / methods from the README master branch examples are reproduced.
|
|
23
|
+
|
|
24
|
+
Note: This file name is intentionally spelled 'backword.py' per user request.
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import Any, Iterable, List, Mapping, Optional, Sequence, Union, Literal, cast
|
|
29
|
+
import logging
|
|
30
|
+
import warnings
|
|
31
|
+
from datetime import datetime
|
|
32
|
+
from functools import cached_property
|
|
33
|
+
|
|
34
|
+
from .astrological_subject_factory import AstrologicalSubjectFactory
|
|
35
|
+
from .chart_data_factory import ChartDataFactory
|
|
36
|
+
from .charts.chart_drawer import ChartDrawer
|
|
37
|
+
from .aspects import AspectsFactory
|
|
38
|
+
from .settings.config_constants import DEFAULT_ACTIVE_POINTS, DEFAULT_ACTIVE_ASPECTS
|
|
39
|
+
from .utilities import normalize_zodiac_type
|
|
40
|
+
from .schemas.kr_models import (
|
|
41
|
+
AstrologicalSubjectModel,
|
|
42
|
+
CompositeSubjectModel,
|
|
43
|
+
ActiveAspect,
|
|
44
|
+
SingleChartDataModel,
|
|
45
|
+
DualChartDataModel,
|
|
46
|
+
)
|
|
47
|
+
from .schemas.kr_literals import (
|
|
48
|
+
KerykeionChartLanguage,
|
|
49
|
+
KerykeionChartTheme,
|
|
50
|
+
ChartType,
|
|
51
|
+
AstrologicalPoint,
|
|
52
|
+
)
|
|
53
|
+
from .schemas import ZodiacType, SiderealMode, HousesSystemIdentifier, PerspectiveType
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# Helpers
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
def _deprecated(old: str, new: str) -> None:
|
|
61
|
+
warnings.warn(
|
|
62
|
+
f"'{old}' is deprecated and will be removed in a future major release. "
|
|
63
|
+
f"Please migrate to: {new}",
|
|
64
|
+
DeprecationWarning,
|
|
65
|
+
stacklevel=2,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Legacy node name mapping for backward compatibility
|
|
70
|
+
LEGACY_NODE_NAMES_MAP = {
|
|
71
|
+
"Mean_Node": "Mean_North_Lunar_Node",
|
|
72
|
+
"True_Node": "True_North_Lunar_Node",
|
|
73
|
+
"Mean_South_Node": "Mean_South_Lunar_Node",
|
|
74
|
+
"True_South_Node": "True_South_Lunar_Node",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _normalize_zodiac_type_with_warning(zodiac_type: Optional[Union[str, ZodiacType]]) -> Optional[ZodiacType]:
|
|
79
|
+
"""Normalize legacy zodiac type values with deprecation warning.
|
|
80
|
+
|
|
81
|
+
Wraps the utilities.normalize_zodiac_type function and adds a deprecation
|
|
82
|
+
warning for legacy formats like "tropic" or case-insensitive variants.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
zodiac_type: Input zodiac type (may be legacy format)
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Normalized ZodiacType or None if input was None
|
|
89
|
+
"""
|
|
90
|
+
if zodiac_type is None:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
zodiac_str = str(zodiac_type)
|
|
94
|
+
|
|
95
|
+
# Check if this is a legacy format (case-insensitive "tropic" or non-canonical case)
|
|
96
|
+
zodiac_lower = zodiac_str.lower()
|
|
97
|
+
if zodiac_lower in ("tropic", "tropical", "sidereal") and zodiac_str not in ("Tropical", "Sidereal"):
|
|
98
|
+
# Normalize using the utilities function
|
|
99
|
+
normalized = normalize_zodiac_type(zodiac_str)
|
|
100
|
+
|
|
101
|
+
# Emit deprecation warning for legacy usage
|
|
102
|
+
warnings.warn(
|
|
103
|
+
f"Zodiac type '{zodiac_str}' is deprecated in Kerykeion v5. "
|
|
104
|
+
f"Use '{normalized}' instead.",
|
|
105
|
+
DeprecationWarning,
|
|
106
|
+
stacklevel=4,
|
|
107
|
+
)
|
|
108
|
+
return normalized
|
|
109
|
+
|
|
110
|
+
# Already in correct format or will be normalized by utilities function
|
|
111
|
+
return cast(ZodiacType, normalize_zodiac_type(zodiac_str))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _normalize_active_points(points: Optional[Iterable[Union[str, AstrologicalPoint]]]) -> Optional[List[AstrologicalPoint]]:
|
|
115
|
+
"""Best-effort normalization of legacy string active points list.
|
|
116
|
+
|
|
117
|
+
- Accepts None -> None
|
|
118
|
+
- Accepts iterable of strings / AstrologicalPoint literals
|
|
119
|
+
- Filters only those present in DEFAULT_ACTIVE_POINTS to avoid invalid entries
|
|
120
|
+
- Returns None if result would be empty (to let downstream use defaults)
|
|
121
|
+
- Maps old lunar node names to new names with deprecation warning
|
|
122
|
+
"""
|
|
123
|
+
if points is None:
|
|
124
|
+
return None
|
|
125
|
+
normalized: List[AstrologicalPoint] = []
|
|
126
|
+
valid: Sequence[AstrologicalPoint] = DEFAULT_ACTIVE_POINTS # type: ignore[assignment]
|
|
127
|
+
for p in points:
|
|
128
|
+
if isinstance(p, str):
|
|
129
|
+
# Check if this is a legacy node name and map it
|
|
130
|
+
if p in LEGACY_NODE_NAMES_MAP:
|
|
131
|
+
warnings.warn(
|
|
132
|
+
f"Active point '{p}' is deprecated in Kerykeion v5. "
|
|
133
|
+
f"Use '{LEGACY_NODE_NAMES_MAP[p]}' instead.",
|
|
134
|
+
DeprecationWarning,
|
|
135
|
+
stacklevel=3,
|
|
136
|
+
)
|
|
137
|
+
p = LEGACY_NODE_NAMES_MAP[p]
|
|
138
|
+
|
|
139
|
+
# Match case-insensitive exact name in default list
|
|
140
|
+
match = next((vp for vp in valid if vp.lower() == p.lower()), None) # type: ignore[attr-defined]
|
|
141
|
+
if match:
|
|
142
|
+
normalized.append(match) # type: ignore[arg-type]
|
|
143
|
+
else:
|
|
144
|
+
if p in valid:
|
|
145
|
+
normalized.append(p)
|
|
146
|
+
return normalized or None
|
|
147
|
+
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
# Legacy AstrologicalSubject wrapper
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
class AstrologicalSubject:
|
|
152
|
+
"""Backward compatible wrapper implementing the requested __init__ signature."""
|
|
153
|
+
|
|
154
|
+
from datetime import datetime as _dt
|
|
155
|
+
NOW = _dt.utcnow()
|
|
156
|
+
|
|
157
|
+
def __init__(
|
|
158
|
+
self,
|
|
159
|
+
name: str = "Now",
|
|
160
|
+
year: int = NOW.year, # type: ignore[misc]
|
|
161
|
+
month: int = NOW.month, # type: ignore[misc]
|
|
162
|
+
day: int = NOW.day, # type: ignore[misc]
|
|
163
|
+
hour: int = NOW.hour, # type: ignore[misc]
|
|
164
|
+
minute: int = NOW.minute, # type: ignore[misc]
|
|
165
|
+
city: Union[str, None] = None,
|
|
166
|
+
nation: Union[str, None] = None,
|
|
167
|
+
lng: Union[int, float, None] = None,
|
|
168
|
+
lat: Union[int, float, None] = None,
|
|
169
|
+
tz_str: Union[str, None] = None,
|
|
170
|
+
geonames_username: Union[str, None] = None,
|
|
171
|
+
zodiac_type: Union[ZodiacType, None] = None, # default resolved below
|
|
172
|
+
online: bool = True,
|
|
173
|
+
disable_chiron: Union[None, bool] = None, # deprecated
|
|
174
|
+
sidereal_mode: Union[SiderealMode, None] = None,
|
|
175
|
+
houses_system_identifier: Union[HousesSystemIdentifier, None] = None,
|
|
176
|
+
perspective_type: Union[PerspectiveType, None] = None,
|
|
177
|
+
cache_expire_after_days: Union[int, None] = None,
|
|
178
|
+
is_dst: Union[None, bool] = None,
|
|
179
|
+
disable_chiron_and_lilith: bool = False, # currently not forwarded (not in factory)
|
|
180
|
+
) -> None:
|
|
181
|
+
from .astrological_subject_factory import (
|
|
182
|
+
DEFAULT_ZODIAC_TYPE,
|
|
183
|
+
DEFAULT_HOUSES_SYSTEM_IDENTIFIER,
|
|
184
|
+
DEFAULT_PERSPECTIVE_TYPE,
|
|
185
|
+
DEFAULT_GEONAMES_CACHE_EXPIRE_AFTER_DAYS,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
_deprecated("AstrologicalSubject", "AstrologicalSubjectFactory.from_birth_data")
|
|
189
|
+
|
|
190
|
+
if disable_chiron is not None:
|
|
191
|
+
warnings.warn("'disable_chiron' è deprecato e ignorato.", DeprecationWarning, stacklevel=2)
|
|
192
|
+
if disable_chiron_and_lilith:
|
|
193
|
+
warnings.warn(
|
|
194
|
+
"'disable_chiron_and_lilith' non è supportato da from_birth_data in questa versione ed è ignorato.",
|
|
195
|
+
UserWarning,
|
|
196
|
+
stacklevel=2,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Normalize legacy zodiac type values
|
|
200
|
+
zodiac_type = _normalize_zodiac_type_with_warning(zodiac_type)
|
|
201
|
+
zodiac_type = DEFAULT_ZODIAC_TYPE if zodiac_type is None else zodiac_type
|
|
202
|
+
|
|
203
|
+
houses_system_identifier = (
|
|
204
|
+
DEFAULT_HOUSES_SYSTEM_IDENTIFIER if houses_system_identifier is None else houses_system_identifier
|
|
205
|
+
)
|
|
206
|
+
perspective_type = DEFAULT_PERSPECTIVE_TYPE if perspective_type is None else perspective_type
|
|
207
|
+
cache_expire_after_days = (
|
|
208
|
+
DEFAULT_GEONAMES_CACHE_EXPIRE_AFTER_DAYS if cache_expire_after_days is None else cache_expire_after_days
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
self._model = AstrologicalSubjectFactory.from_birth_data(
|
|
212
|
+
name=name,
|
|
213
|
+
year=year,
|
|
214
|
+
month=month,
|
|
215
|
+
day=day,
|
|
216
|
+
hour=hour,
|
|
217
|
+
minute=minute,
|
|
218
|
+
seconds=0,
|
|
219
|
+
city=city,
|
|
220
|
+
nation=nation,
|
|
221
|
+
lng=float(lng) if lng is not None else None,
|
|
222
|
+
lat=float(lat) if lat is not None else None,
|
|
223
|
+
tz_str=tz_str,
|
|
224
|
+
geonames_username=geonames_username,
|
|
225
|
+
online=online,
|
|
226
|
+
zodiac_type=zodiac_type, # type: ignore[arg-type]
|
|
227
|
+
sidereal_mode=sidereal_mode, # type: ignore[arg-type]
|
|
228
|
+
houses_system_identifier=houses_system_identifier, # type: ignore[arg-type]
|
|
229
|
+
perspective_type=perspective_type, # type: ignore[arg-type]
|
|
230
|
+
cache_expire_after_days=cache_expire_after_days,
|
|
231
|
+
is_dst=is_dst, # type: ignore[arg-type]
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Legacy filesystem attributes
|
|
235
|
+
self.json_dir = Path.home()
|
|
236
|
+
|
|
237
|
+
# Backward compatibility properties for v4 lunar node names
|
|
238
|
+
@property
|
|
239
|
+
def mean_node(self):
|
|
240
|
+
"""Deprecated: Use mean_north_lunar_node instead."""
|
|
241
|
+
warnings.warn(
|
|
242
|
+
"'mean_node' is deprecated in Kerykeion v5. Use 'mean_north_lunar_node' instead.",
|
|
243
|
+
DeprecationWarning,
|
|
244
|
+
stacklevel=2,
|
|
245
|
+
)
|
|
246
|
+
return self._model.mean_north_lunar_node
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def true_node(self):
|
|
250
|
+
"""Deprecated: Use true_north_lunar_node instead."""
|
|
251
|
+
warnings.warn(
|
|
252
|
+
"'true_node' is deprecated in Kerykeion v5. Use 'true_north_lunar_node' instead.",
|
|
253
|
+
DeprecationWarning,
|
|
254
|
+
stacklevel=2,
|
|
255
|
+
)
|
|
256
|
+
return self._model.true_north_lunar_node
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def mean_south_node(self):
|
|
260
|
+
"""Deprecated: Use mean_south_lunar_node instead."""
|
|
261
|
+
warnings.warn(
|
|
262
|
+
"'mean_south_node' is deprecated in Kerykeion v5. Use 'mean_south_lunar_node' instead.",
|
|
263
|
+
DeprecationWarning,
|
|
264
|
+
stacklevel=2,
|
|
265
|
+
)
|
|
266
|
+
return self._model.mean_south_lunar_node
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def true_south_node(self):
|
|
270
|
+
"""Deprecated: Use true_south_lunar_node instead."""
|
|
271
|
+
warnings.warn(
|
|
272
|
+
"'true_south_node' is deprecated in Kerykeion v5. Use 'true_south_lunar_node' instead.",
|
|
273
|
+
DeprecationWarning,
|
|
274
|
+
stacklevel=2,
|
|
275
|
+
)
|
|
276
|
+
return self._model.true_south_lunar_node
|
|
277
|
+
|
|
278
|
+
# Provide attribute passthrough for planetary points / houses used in README
|
|
279
|
+
def __getattr__(self, item: str) -> Any: # pragma: no cover - dynamic proxy
|
|
280
|
+
try:
|
|
281
|
+
return getattr(self._model, item)
|
|
282
|
+
except AttributeError:
|
|
283
|
+
raise AttributeError(f"AstrologicalSubject has no attribute '{item}'") from None
|
|
284
|
+
|
|
285
|
+
def __repr__(self) -> str:
|
|
286
|
+
return self.__str__()
|
|
287
|
+
|
|
288
|
+
# Provide json() similar convenience
|
|
289
|
+
def json(
|
|
290
|
+
self,
|
|
291
|
+
dump: bool = False,
|
|
292
|
+
destination_folder: Optional[Union[str, Path]] = None,
|
|
293
|
+
indent: Optional[int] = None,
|
|
294
|
+
) -> str:
|
|
295
|
+
"""Replicate legacy json() behaviour returning a JSON string and optionally dumping to disk."""
|
|
296
|
+
|
|
297
|
+
json_string = self._model.model_dump_json(exclude_none=True, indent=indent)
|
|
298
|
+
|
|
299
|
+
if not dump:
|
|
300
|
+
return json_string
|
|
301
|
+
|
|
302
|
+
if destination_folder is not None:
|
|
303
|
+
target_dir = Path(destination_folder)
|
|
304
|
+
else:
|
|
305
|
+
target_dir = self.json_dir
|
|
306
|
+
|
|
307
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
json_path = target_dir / f"{self._model.name}_kerykeion.json"
|
|
309
|
+
|
|
310
|
+
with open(json_path, "w", encoding="utf-8") as file:
|
|
311
|
+
file.write(json_string)
|
|
312
|
+
logging.info("JSON file dumped in %s.", json_path)
|
|
313
|
+
|
|
314
|
+
return json_string
|
|
315
|
+
|
|
316
|
+
# Legacy helpers -----------------------------------------------------
|
|
317
|
+
@staticmethod
|
|
318
|
+
def _parse_iso_datetime(value: str) -> datetime:
|
|
319
|
+
if value.endswith("Z"):
|
|
320
|
+
value = value[:-1] + "+00:00"
|
|
321
|
+
return datetime.fromisoformat(value)
|
|
322
|
+
|
|
323
|
+
def model(self) -> AstrologicalSubjectModel:
|
|
324
|
+
"""Return the underlying Pydantic model (legacy compatibility)."""
|
|
325
|
+
|
|
326
|
+
return self._model
|
|
327
|
+
|
|
328
|
+
def __getitem__(self, item: str) -> Any:
|
|
329
|
+
return getattr(self, item)
|
|
330
|
+
|
|
331
|
+
def get(self, item: str, default: Any = None) -> Any:
|
|
332
|
+
return getattr(self, item, default)
|
|
333
|
+
|
|
334
|
+
def __str__(self) -> str:
|
|
335
|
+
return (
|
|
336
|
+
f"Astrological data for: {self._model.name}, {self._model.iso_formatted_utc_datetime} UTC\n"
|
|
337
|
+
f"Birth location: {self._model.city}, Lat {self._model.lat}, Lon {self._model.lng}"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
@cached_property
|
|
341
|
+
def utc_time(self) -> float:
|
|
342
|
+
"""Backwards-compatible float UTC time value."""
|
|
343
|
+
|
|
344
|
+
dt = self._parse_iso_datetime(self._model.iso_formatted_utc_datetime)
|
|
345
|
+
return dt.hour + dt.minute / 60 + dt.second / 3600 + dt.microsecond / 3_600_000_000
|
|
346
|
+
|
|
347
|
+
@cached_property
|
|
348
|
+
def local_time(self) -> float:
|
|
349
|
+
"""Backwards-compatible float local time value."""
|
|
350
|
+
|
|
351
|
+
dt = self._parse_iso_datetime(self._model.iso_formatted_local_datetime)
|
|
352
|
+
return dt.hour + dt.minute / 60 + dt.second / 3600 + dt.microsecond / 3_600_000_000
|
|
353
|
+
|
|
354
|
+
# Factory method compatibility (class method in old API)
|
|
355
|
+
@classmethod
|
|
356
|
+
def get_from_iso_utc_time(
|
|
357
|
+
cls,
|
|
358
|
+
name: str,
|
|
359
|
+
iso_utc_time: str,
|
|
360
|
+
city: str = "Greenwich",
|
|
361
|
+
nation: str = "GB",
|
|
362
|
+
tz_str: str = "Etc/GMT",
|
|
363
|
+
online: bool = False,
|
|
364
|
+
lng: Union[int, float] = 0.0,
|
|
365
|
+
lat: Union[int, float] = 51.5074,
|
|
366
|
+
geonames_username: Optional[str] = None,
|
|
367
|
+
zodiac_type: Optional[ZodiacType] = None,
|
|
368
|
+
disable_chiron_and_lilith: bool = False,
|
|
369
|
+
sidereal_mode: Optional[SiderealMode] = None,
|
|
370
|
+
houses_system_identifier: Optional[HousesSystemIdentifier] = None,
|
|
371
|
+
perspective_type: Optional[PerspectiveType] = None,
|
|
372
|
+
**kwargs: Any,
|
|
373
|
+
) -> "AstrologicalSubject":
|
|
374
|
+
from .astrological_subject_factory import (
|
|
375
|
+
DEFAULT_ZODIAC_TYPE,
|
|
376
|
+
DEFAULT_HOUSES_SYSTEM_IDENTIFIER,
|
|
377
|
+
DEFAULT_PERSPECTIVE_TYPE,
|
|
378
|
+
DEFAULT_GEONAMES_USERNAME,
|
|
379
|
+
GEONAMES_DEFAULT_USERNAME_WARNING,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
_deprecated("AstrologicalSubject.get_from_iso_utc_time", "AstrologicalSubjectFactory.from_iso_utc_time")
|
|
383
|
+
|
|
384
|
+
if disable_chiron_and_lilith:
|
|
385
|
+
warnings.warn(
|
|
386
|
+
"'disable_chiron_and_lilith' is ignored by the new factory pipeline.",
|
|
387
|
+
UserWarning,
|
|
388
|
+
stacklevel=2,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
resolved_geonames = geonames_username or DEFAULT_GEONAMES_USERNAME
|
|
392
|
+
if online and resolved_geonames == DEFAULT_GEONAMES_USERNAME:
|
|
393
|
+
warnings.warn(GEONAMES_DEFAULT_USERNAME_WARNING, UserWarning, stacklevel=2)
|
|
394
|
+
|
|
395
|
+
# Normalize legacy zodiac type values
|
|
396
|
+
normalized_zodiac_type = _normalize_zodiac_type_with_warning(zodiac_type)
|
|
397
|
+
|
|
398
|
+
model = AstrologicalSubjectFactory.from_iso_utc_time(
|
|
399
|
+
name=name,
|
|
400
|
+
iso_utc_time=iso_utc_time,
|
|
401
|
+
city=city,
|
|
402
|
+
nation=nation,
|
|
403
|
+
tz_str=tz_str,
|
|
404
|
+
online=online,
|
|
405
|
+
lng=float(lng),
|
|
406
|
+
lat=float(lat),
|
|
407
|
+
geonames_username=resolved_geonames,
|
|
408
|
+
zodiac_type=(normalized_zodiac_type or DEFAULT_ZODIAC_TYPE), # type: ignore[arg-type]
|
|
409
|
+
sidereal_mode=sidereal_mode,
|
|
410
|
+
houses_system_identifier=(houses_system_identifier or DEFAULT_HOUSES_SYSTEM_IDENTIFIER), # type: ignore[arg-type]
|
|
411
|
+
perspective_type=(perspective_type or DEFAULT_PERSPECTIVE_TYPE), # type: ignore[arg-type]
|
|
412
|
+
**kwargs,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
obj = cls.__new__(cls)
|
|
416
|
+
obj._model = model
|
|
417
|
+
obj.json_dir = Path.home()
|
|
418
|
+
return obj
|
|
419
|
+
|
|
420
|
+
# ---------------------------------------------------------------------------
|
|
421
|
+
# Legacy KerykeionChartSVG wrapper
|
|
422
|
+
# ---------------------------------------------------------------------------
|
|
423
|
+
class KerykeionChartSVG:
|
|
424
|
+
"""Wrapper emulating the v4 chart generation interface.
|
|
425
|
+
|
|
426
|
+
Old usage:
|
|
427
|
+
chart = KerykeionChartSVG(subject, chart_type="ExternalNatal", second_subject)
|
|
428
|
+
chart.makeSVG(minify_svg=True, remove_css_variables=True)
|
|
429
|
+
|
|
430
|
+
Replaced by ChartDataFactory + ChartDrawer.
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
def __init__(
|
|
434
|
+
self,
|
|
435
|
+
first_obj: Union[AstrologicalSubject, AstrologicalSubjectModel, CompositeSubjectModel],
|
|
436
|
+
chart_type: ChartType = "Natal",
|
|
437
|
+
second_obj: Union[AstrologicalSubject, AstrologicalSubjectModel, None] = None,
|
|
438
|
+
new_output_directory: Union[str, None] = None,
|
|
439
|
+
new_settings_file: Union[Path, None, dict] = None, # retained for signature compatibility (unused)
|
|
440
|
+
theme: Union[KerykeionChartTheme, None] = "classic",
|
|
441
|
+
double_chart_aspect_grid_type: Literal["list", "table"] = "list",
|
|
442
|
+
chart_language: KerykeionChartLanguage = "EN",
|
|
443
|
+
active_points: List[AstrologicalPoint] = DEFAULT_ACTIVE_POINTS, # type: ignore[assignment]
|
|
444
|
+
active_aspects: Optional[List[ActiveAspect]] = None,
|
|
445
|
+
*,
|
|
446
|
+
language_pack: Optional[Mapping[str, Any]] = None,
|
|
447
|
+
|
|
448
|
+
) -> None:
|
|
449
|
+
_deprecated("KerykeionChartSVG", "ChartDataFactory + ChartDrawer")
|
|
450
|
+
|
|
451
|
+
if new_settings_file is not None:
|
|
452
|
+
warnings.warn(
|
|
453
|
+
"'new_settings_file' is deprecated and ignored in Kerykeion v5. Use language_pack instead.",
|
|
454
|
+
DeprecationWarning,
|
|
455
|
+
stacklevel=2,
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
if isinstance(first_obj, AstrologicalSubject):
|
|
459
|
+
subject_model: Union[AstrologicalSubjectModel, CompositeSubjectModel] = first_obj.model()
|
|
460
|
+
else:
|
|
461
|
+
subject_model = first_obj
|
|
462
|
+
|
|
463
|
+
if isinstance(second_obj, AstrologicalSubject):
|
|
464
|
+
second_model: Optional[Union[AstrologicalSubjectModel, CompositeSubjectModel]] = second_obj.model()
|
|
465
|
+
else:
|
|
466
|
+
second_model = second_obj
|
|
467
|
+
|
|
468
|
+
if active_aspects is None:
|
|
469
|
+
active_aspects = list(DEFAULT_ACTIVE_ASPECTS)
|
|
470
|
+
else:
|
|
471
|
+
active_aspects = list(active_aspects)
|
|
472
|
+
|
|
473
|
+
self.chart_type = chart_type
|
|
474
|
+
self.language_pack = language_pack
|
|
475
|
+
self.theme = theme # type: ignore[assignment]
|
|
476
|
+
self.double_chart_aspect_grid_type = double_chart_aspect_grid_type
|
|
477
|
+
self.chart_language = chart_language # type: ignore[assignment]
|
|
478
|
+
|
|
479
|
+
self._subject_model = subject_model
|
|
480
|
+
self._second_model = second_model
|
|
481
|
+
self.user = subject_model
|
|
482
|
+
self.first_obj = subject_model
|
|
483
|
+
self.t_user = second_model
|
|
484
|
+
self.second_obj = second_model
|
|
485
|
+
|
|
486
|
+
self.active_points = list(active_points) if active_points is not None else list(DEFAULT_ACTIVE_POINTS) # type: ignore[list-item]
|
|
487
|
+
self._active_points = _normalize_active_points(self.active_points)
|
|
488
|
+
self.active_aspects = active_aspects
|
|
489
|
+
self._active_aspects = active_aspects
|
|
490
|
+
|
|
491
|
+
self.output_directory = Path(new_output_directory) if new_output_directory else Path.home()
|
|
492
|
+
self._output_directory = self.output_directory
|
|
493
|
+
|
|
494
|
+
self.template = ""
|
|
495
|
+
self.aspects_list: list[dict[str, Any]] = []
|
|
496
|
+
self.available_planets_setting: list[dict[str, Any]] = []
|
|
497
|
+
self.t_available_kerykeion_celestial_points = None
|
|
498
|
+
self.available_kerykeion_celestial_points: list[dict[str, Any]] = []
|
|
499
|
+
self.chart_colors_settings: dict[str, Any] = {}
|
|
500
|
+
self.planets_settings: list[dict[str, Any]] = []
|
|
501
|
+
self.aspects_settings: list[dict[str, Any]] = []
|
|
502
|
+
self.language_settings: dict[str, Any] = {}
|
|
503
|
+
self.height = None
|
|
504
|
+
self.width = None
|
|
505
|
+
self.location = None
|
|
506
|
+
self.geolat = None
|
|
507
|
+
self.geolon = None
|
|
508
|
+
|
|
509
|
+
self._chart_drawer: Optional[ChartDrawer] = None
|
|
510
|
+
self._chart_data: Optional[Union[SingleChartDataModel, DualChartDataModel]] = None
|
|
511
|
+
self._external_view = False
|
|
512
|
+
|
|
513
|
+
def _ensure_chart(self) -> None:
|
|
514
|
+
if self._chart_drawer is not None:
|
|
515
|
+
return
|
|
516
|
+
|
|
517
|
+
if self._subject_model is None:
|
|
518
|
+
raise ValueError("First object is required to build charts.")
|
|
519
|
+
|
|
520
|
+
chart_type_normalized = str(self.chart_type).lower()
|
|
521
|
+
active_points = self._active_points
|
|
522
|
+
active_aspects = self._active_aspects
|
|
523
|
+
external_view = False
|
|
524
|
+
|
|
525
|
+
if chart_type_normalized in ("natal", "birth", "externalnatal", "external_natal"):
|
|
526
|
+
data = ChartDataFactory.create_natal_chart_data(
|
|
527
|
+
self._subject_model, active_points=active_points, active_aspects=active_aspects
|
|
528
|
+
)
|
|
529
|
+
if chart_type_normalized in ("externalnatal", "external_natal"):
|
|
530
|
+
external_view = True
|
|
531
|
+
elif chart_type_normalized == "synastry":
|
|
532
|
+
if self._second_model is None:
|
|
533
|
+
raise ValueError("Second object is required for Synastry charts.")
|
|
534
|
+
if not isinstance(self._subject_model, AstrologicalSubjectModel) or not isinstance(
|
|
535
|
+
self._second_model, AstrologicalSubjectModel
|
|
536
|
+
):
|
|
537
|
+
raise ValueError("Synastry charts require two AstrologicalSubject instances.")
|
|
538
|
+
data = ChartDataFactory.create_synastry_chart_data(
|
|
539
|
+
cast(AstrologicalSubjectModel, self._subject_model),
|
|
540
|
+
cast(AstrologicalSubjectModel, self._second_model),
|
|
541
|
+
active_points=active_points,
|
|
542
|
+
active_aspects=active_aspects,
|
|
543
|
+
)
|
|
544
|
+
elif chart_type_normalized == "transit":
|
|
545
|
+
if self._second_model is None:
|
|
546
|
+
raise ValueError("Second object is required for Transit charts.")
|
|
547
|
+
if not isinstance(self._subject_model, AstrologicalSubjectModel) or not isinstance(
|
|
548
|
+
self._second_model, AstrologicalSubjectModel
|
|
549
|
+
):
|
|
550
|
+
raise ValueError("Transit charts require natal and transit AstrologicalSubject instances.")
|
|
551
|
+
data = ChartDataFactory.create_transit_chart_data(
|
|
552
|
+
cast(AstrologicalSubjectModel, self._subject_model),
|
|
553
|
+
cast(AstrologicalSubjectModel, self._second_model),
|
|
554
|
+
active_points=active_points,
|
|
555
|
+
active_aspects=active_aspects,
|
|
556
|
+
)
|
|
557
|
+
elif chart_type_normalized == "composite":
|
|
558
|
+
if not isinstance(self._subject_model, CompositeSubjectModel):
|
|
559
|
+
raise ValueError("First object must be a CompositeSubjectModel instance for composite charts.")
|
|
560
|
+
data = ChartDataFactory.create_composite_chart_data(
|
|
561
|
+
self._subject_model, active_points=active_points, active_aspects=active_aspects
|
|
562
|
+
)
|
|
563
|
+
else:
|
|
564
|
+
raise ValueError(f"Unsupported or improperly configured chart_type '{self.chart_type}'")
|
|
565
|
+
|
|
566
|
+
self._external_view = external_view
|
|
567
|
+
self._chart_data = data
|
|
568
|
+
self.chart_data = data
|
|
569
|
+
self._chart_drawer = ChartDrawer(
|
|
570
|
+
chart_data=data,
|
|
571
|
+
theme=cast(Optional[KerykeionChartTheme], self.theme),
|
|
572
|
+
double_chart_aspect_grid_type=cast(Literal["list", "table"], self.double_chart_aspect_grid_type),
|
|
573
|
+
chart_language=cast(KerykeionChartLanguage, self.chart_language),
|
|
574
|
+
language_pack=self.language_pack,
|
|
575
|
+
external_view=external_view,
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
# Mirror commonly accessed attributes from legacy class
|
|
579
|
+
drawer = self._chart_drawer
|
|
580
|
+
self.available_planets_setting = getattr(drawer, "available_planets_setting", [])
|
|
581
|
+
self.available_kerykeion_celestial_points = getattr(drawer, "available_kerykeion_celestial_points", [])
|
|
582
|
+
self.aspects_list = getattr(drawer, "aspects_list", [])
|
|
583
|
+
if hasattr(drawer, "t_available_kerykeion_celestial_points"):
|
|
584
|
+
self.t_available_kerykeion_celestial_points = getattr(drawer, "t_available_kerykeion_celestial_points")
|
|
585
|
+
self.chart_colors_settings = getattr(drawer, "chart_colors_settings", {})
|
|
586
|
+
self.planets_settings = getattr(drawer, "planets_settings", [])
|
|
587
|
+
self.aspects_settings = getattr(drawer, "aspects_settings", [])
|
|
588
|
+
self.language_settings = getattr(drawer, "language_settings", {})
|
|
589
|
+
self.height = getattr(drawer, "height", self.height)
|
|
590
|
+
self.width = getattr(drawer, "width", self.width)
|
|
591
|
+
self.location = getattr(drawer, "location", self.location)
|
|
592
|
+
self.geolat = getattr(drawer, "geolat", self.geolat)
|
|
593
|
+
self.geolon = getattr(drawer, "geolon", self.geolon)
|
|
594
|
+
for attr in ["main_radius", "first_circle_radius", "second_circle_radius", "third_circle_radius"]:
|
|
595
|
+
if hasattr(drawer, attr):
|
|
596
|
+
setattr(self, attr, getattr(drawer, attr))
|
|
597
|
+
|
|
598
|
+
# Legacy method names --------------------------------------------------
|
|
599
|
+
def makeTemplate(self, minify: bool = False, remove_css_variables: bool = False) -> str:
|
|
600
|
+
self._ensure_chart()
|
|
601
|
+
assert self._chart_drawer is not None
|
|
602
|
+
template = self._chart_drawer.generate_svg_string(minify=minify, remove_css_variables=remove_css_variables)
|
|
603
|
+
self.template = template
|
|
604
|
+
return template
|
|
605
|
+
|
|
606
|
+
def makeSVG(self, minify: bool = False, remove_css_variables: bool = False) -> None:
|
|
607
|
+
self._ensure_chart()
|
|
608
|
+
assert self._chart_drawer is not None
|
|
609
|
+
self._chart_drawer.save_svg(
|
|
610
|
+
output_path=self.output_directory,
|
|
611
|
+
minify=minify,
|
|
612
|
+
remove_css_variables=remove_css_variables,
|
|
613
|
+
)
|
|
614
|
+
self.template = getattr(self._chart_drawer, "template", self.template)
|
|
615
|
+
|
|
616
|
+
def makeWheelOnlyTemplate(self, minify: bool = False, remove_css_variables: bool = False) -> str:
|
|
617
|
+
self._ensure_chart()
|
|
618
|
+
assert self._chart_drawer is not None
|
|
619
|
+
template = self._chart_drawer.generate_wheel_only_svg_string(
|
|
620
|
+
minify=minify,
|
|
621
|
+
remove_css_variables=remove_css_variables,
|
|
622
|
+
)
|
|
623
|
+
self.template = template
|
|
624
|
+
return template
|
|
625
|
+
|
|
626
|
+
def makeWheelOnlySVG(self, minify: bool = False, remove_css_variables: bool = False) -> None:
|
|
627
|
+
self._ensure_chart()
|
|
628
|
+
assert self._chart_drawer is not None
|
|
629
|
+
self._chart_drawer.save_wheel_only_svg_file(
|
|
630
|
+
output_path=self.output_directory,
|
|
631
|
+
minify=minify,
|
|
632
|
+
remove_css_variables=remove_css_variables,
|
|
633
|
+
)
|
|
634
|
+
self.template = getattr(self._chart_drawer, "template", self.template)
|
|
635
|
+
|
|
636
|
+
def makeAspectGridOnlyTemplate(self, minify: bool = False, remove_css_variables: bool = False) -> str:
|
|
637
|
+
self._ensure_chart()
|
|
638
|
+
assert self._chart_drawer is not None
|
|
639
|
+
template = self._chart_drawer.generate_aspect_grid_only_svg_string(
|
|
640
|
+
minify=minify,
|
|
641
|
+
remove_css_variables=remove_css_variables,
|
|
642
|
+
)
|
|
643
|
+
self.template = template
|
|
644
|
+
return template
|
|
645
|
+
|
|
646
|
+
def makeAspectGridOnlySVG(self, minify: bool = False, remove_css_variables: bool = False) -> None:
|
|
647
|
+
self._ensure_chart()
|
|
648
|
+
assert self._chart_drawer is not None
|
|
649
|
+
self._chart_drawer.save_aspect_grid_only_svg_file(
|
|
650
|
+
output_path=self.output_directory,
|
|
651
|
+
minify=minify,
|
|
652
|
+
remove_css_variables=remove_css_variables,
|
|
653
|
+
)
|
|
654
|
+
self.template = getattr(self._chart_drawer, "template", self.template)
|
|
655
|
+
|
|
656
|
+
# Aliases for new naming in README next (optional convenience)
|
|
657
|
+
save_svg = makeSVG
|
|
658
|
+
save_wheel_only_svg_file = makeWheelOnlySVG
|
|
659
|
+
save_aspect_grid_only_svg_file = makeAspectGridOnlySVG
|
|
660
|
+
makeGridOnlySVG = makeAspectGridOnlySVG
|
|
661
|
+
|
|
662
|
+
# ---------------------------------------------------------------------------
|
|
663
|
+
# Legacy NatalAspects wrapper
|
|
664
|
+
# ---------------------------------------------------------------------------
|
|
665
|
+
class NatalAspects:
|
|
666
|
+
"""Wrapper replicating the master branch NatalAspects interface.
|
|
667
|
+
|
|
668
|
+
Replacement: AspectsFactory.single_subject_aspects(subject)
|
|
669
|
+
"""
|
|
670
|
+
|
|
671
|
+
def __init__(
|
|
672
|
+
self,
|
|
673
|
+
user: Union[AstrologicalSubject, AstrologicalSubjectModel, CompositeSubjectModel],
|
|
674
|
+
new_settings_file: Union[Path, None, dict] = None,
|
|
675
|
+
active_points: Iterable[Union[str, AstrologicalPoint]] = DEFAULT_ACTIVE_POINTS,
|
|
676
|
+
active_aspects: Optional[List[ActiveAspect]] = None,
|
|
677
|
+
*,
|
|
678
|
+
language_pack: Optional[Mapping[str, Any]] = None,
|
|
679
|
+
axis_orb_limit: Optional[float] = None,
|
|
680
|
+
) -> None:
|
|
681
|
+
_deprecated("NatalAspects", "AspectsFactory.single_chart_aspects")
|
|
682
|
+
|
|
683
|
+
if new_settings_file is not None:
|
|
684
|
+
warnings.warn(
|
|
685
|
+
"'new_settings_file' is deprecated and ignored in Kerykeion v5. Use language_pack instead.",
|
|
686
|
+
DeprecationWarning,
|
|
687
|
+
stacklevel=2,
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
self.user = user.model() if isinstance(user, AstrologicalSubject) else user
|
|
691
|
+
self.new_settings_file = new_settings_file
|
|
692
|
+
|
|
693
|
+
self.language_pack = language_pack
|
|
694
|
+
self.celestial_points: list[Any] = []
|
|
695
|
+
self.aspects_settings: list[Any] = []
|
|
696
|
+
self.axes_orbit_settings = axis_orb_limit
|
|
697
|
+
|
|
698
|
+
self.active_points = list(active_points)
|
|
699
|
+
self._active_points = _normalize_active_points(self.active_points)
|
|
700
|
+
if active_aspects is None:
|
|
701
|
+
active_aspects = list(DEFAULT_ACTIVE_ASPECTS)
|
|
702
|
+
else:
|
|
703
|
+
active_aspects = list(active_aspects)
|
|
704
|
+
self.active_aspects = active_aspects
|
|
705
|
+
|
|
706
|
+
self._aspects_model = None
|
|
707
|
+
self._all_aspects_cache = None
|
|
708
|
+
self._relevant_aspects_cache = None
|
|
709
|
+
|
|
710
|
+
def _build_aspects_model(self):
|
|
711
|
+
if self._aspects_model is None:
|
|
712
|
+
self._aspects_model = AspectsFactory.single_chart_aspects(
|
|
713
|
+
self.user,
|
|
714
|
+
active_points=self._active_points,
|
|
715
|
+
active_aspects=self.active_aspects,
|
|
716
|
+
axis_orb_limit=self.axes_orbit_settings,
|
|
717
|
+
)
|
|
718
|
+
return self._aspects_model
|
|
719
|
+
|
|
720
|
+
@cached_property
|
|
721
|
+
def all_aspects(self):
|
|
722
|
+
"""Legacy property - returns the same as aspects for backwards compatibility."""
|
|
723
|
+
if self._all_aspects_cache is None:
|
|
724
|
+
self._all_aspects_cache = list(self._build_aspects_model().aspects)
|
|
725
|
+
return self._all_aspects_cache
|
|
726
|
+
|
|
727
|
+
@cached_property
|
|
728
|
+
def relevant_aspects(self):
|
|
729
|
+
"""Legacy property - returns the same as aspects for backwards compatibility."""
|
|
730
|
+
if self._relevant_aspects_cache is None:
|
|
731
|
+
self._relevant_aspects_cache = list(self._build_aspects_model().aspects)
|
|
732
|
+
return self._relevant_aspects_cache
|
|
733
|
+
|
|
734
|
+
# ---------------------------------------------------------------------------
|
|
735
|
+
# Legacy SynastryAspects wrapper
|
|
736
|
+
# ---------------------------------------------------------------------------
|
|
737
|
+
class SynastryAspects:
|
|
738
|
+
"""Wrapper replicating the master branch synastry aspects interface."""
|
|
739
|
+
|
|
740
|
+
def __init__(
|
|
741
|
+
self,
|
|
742
|
+
kr_object_one: Union[AstrologicalSubject, AstrologicalSubjectModel],
|
|
743
|
+
kr_object_two: Union[AstrologicalSubject, AstrologicalSubjectModel],
|
|
744
|
+
new_settings_file: Union[Path, None, dict] = None,
|
|
745
|
+
active_points: Iterable[Union[str, AstrologicalPoint]] = DEFAULT_ACTIVE_POINTS,
|
|
746
|
+
active_aspects: Optional[List[ActiveAspect]] = None,
|
|
747
|
+
*,
|
|
748
|
+
language_pack: Optional[Mapping[str, Any]] = None,
|
|
749
|
+
axis_orb_limit: Optional[float] = None,
|
|
750
|
+
) -> None:
|
|
751
|
+
_deprecated("SynastryAspects", "AspectsFactory.dual_chart_aspects")
|
|
752
|
+
|
|
753
|
+
if new_settings_file is not None:
|
|
754
|
+
warnings.warn(
|
|
755
|
+
"'new_settings_file' is deprecated and ignored in Kerykeion v5. Use language_pack instead.",
|
|
756
|
+
DeprecationWarning,
|
|
757
|
+
stacklevel=2,
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
self.first_user = kr_object_one.model() if isinstance(kr_object_one, AstrologicalSubject) else kr_object_one
|
|
761
|
+
self.second_user = kr_object_two.model() if isinstance(kr_object_two, AstrologicalSubject) else kr_object_two
|
|
762
|
+
self.new_settings_file = new_settings_file
|
|
763
|
+
|
|
764
|
+
self.language_pack = language_pack
|
|
765
|
+
self.celestial_points: list[Any] = []
|
|
766
|
+
self.aspects_settings: list[Any] = []
|
|
767
|
+
self.axes_orbit_settings = axis_orb_limit
|
|
768
|
+
|
|
769
|
+
self.active_points = list(active_points)
|
|
770
|
+
self._active_points = _normalize_active_points(self.active_points)
|
|
771
|
+
if active_aspects is None:
|
|
772
|
+
active_aspects = list(DEFAULT_ACTIVE_ASPECTS)
|
|
773
|
+
else:
|
|
774
|
+
active_aspects = list(active_aspects)
|
|
775
|
+
self.active_aspects = active_aspects
|
|
776
|
+
|
|
777
|
+
self._dual_model = None
|
|
778
|
+
self._all_aspects_cache = None
|
|
779
|
+
self._relevant_aspects_cache = None
|
|
780
|
+
self._all_aspects: Union[list, None] = None
|
|
781
|
+
self._relevant_aspects: Union[list, None] = None
|
|
782
|
+
|
|
783
|
+
def _build_dual_model(self):
|
|
784
|
+
if self._dual_model is None:
|
|
785
|
+
self._dual_model = AspectsFactory.dual_chart_aspects(
|
|
786
|
+
self.first_user,
|
|
787
|
+
self.second_user,
|
|
788
|
+
active_points=self._active_points,
|
|
789
|
+
active_aspects=self.active_aspects,
|
|
790
|
+
axis_orb_limit=self.axes_orbit_settings,
|
|
791
|
+
)
|
|
792
|
+
return self._dual_model
|
|
793
|
+
|
|
794
|
+
@cached_property
|
|
795
|
+
def all_aspects(self):
|
|
796
|
+
"""Legacy property - returns the same as aspects for backwards compatibility."""
|
|
797
|
+
if self._all_aspects_cache is None:
|
|
798
|
+
self._all_aspects_cache = list(self._build_dual_model().aspects)
|
|
799
|
+
return self._all_aspects_cache
|
|
800
|
+
|
|
801
|
+
@cached_property
|
|
802
|
+
def relevant_aspects(self):
|
|
803
|
+
"""Legacy property - returns the same as aspects for backwards compatibility."""
|
|
804
|
+
if self._relevant_aspects_cache is None:
|
|
805
|
+
self._relevant_aspects_cache = list(self._build_dual_model().aspects)
|
|
806
|
+
return self._relevant_aspects_cache
|
|
807
|
+
|
|
808
|
+
def get_relevant_aspects(self):
|
|
809
|
+
"""Legacy method for compatibility with master branch."""
|
|
810
|
+
return self.relevant_aspects
|
|
811
|
+
|
|
812
|
+
# ---------------------------------------------------------------------------
|
|
813
|
+
# Convenience exports (mirroring old implicit surface API)
|
|
814
|
+
# ---------------------------------------------------------------------------
|
|
815
|
+
__all__ = [
|
|
816
|
+
"AstrologicalSubject",
|
|
817
|
+
"KerykeionChartSVG",
|
|
818
|
+
"NatalAspects",
|
|
819
|
+
"SynastryAspects",
|
|
820
|
+
]
|