kerykeion 5.0.0a6__py3-none-any.whl → 5.0.0a8__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.

@@ -96,7 +96,12 @@ def draw_planets_v2(
96
96
  point_idx = position_index_map[abs_position]
97
97
 
98
98
  # Find previous and next point positions for distance calculations
99
- if position_idx == 0:
99
+ # Handle special case when there's only one planet
100
+ if len(sorted_positions) == 1:
101
+ # With only one planet, there are no adjacent planets
102
+ prev_position = main_points_abs_positions[point_idx]
103
+ next_position = main_points_abs_positions[point_idx]
104
+ elif position_idx == 0:
100
105
  prev_position = main_points_abs_positions[position_index_map[sorted_positions[-1]]]
101
106
  next_position = main_points_abs_positions[position_index_map[sorted_positions[1]]]
102
107
  elif position_idx == len(sorted_positions) - 1:
@@ -107,8 +112,13 @@ def draw_planets_v2(
107
112
  next_position = main_points_abs_positions[position_index_map[sorted_positions[position_idx + 1]]]
108
113
 
109
114
  # Calculate distance to adjacent points
110
- distance_to_prev = degreeDiff(prev_position, main_points_abs_positions[point_idx])
111
- distance_to_next = degreeDiff(next_position, main_points_abs_positions[point_idx])
115
+ # When there's only one planet, set distances to a large value to prevent grouping
116
+ if len(sorted_positions) == 1:
117
+ distance_to_prev = 360.0 # Maximum possible distance
118
+ distance_to_next = 360.0 # Maximum possible distance
119
+ else:
120
+ distance_to_prev = degreeDiff(prev_position, main_points_abs_positions[point_idx])
121
+ distance_to_next = degreeDiff(next_position, main_points_abs_positions[point_idx])
112
122
 
113
123
  # Store position and distance information
114
124
  planets_by_position[position_idx] = [point_idx, distance_to_prev, distance_to_next]
@@ -0,0 +1,679 @@
1
+ """
2
+ TODO: Not stable at all, check it very well before using it!
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import Union, Optional, List, Dict, Tuple, get_args
8
+ import logging
9
+
10
+ from kerykeion.charts.charts_utils import degreeDiff, sliceToX, sliceToY, convert_decimal_to_degree_string
11
+ from kerykeion.kr_types import KerykeionException, ChartType, KerykeionPointModel
12
+ from kerykeion.kr_types.settings_models import KerykeionSettingsCelestialPointModel
13
+ from kerykeion.kr_types.kr_literals import Houses
14
+
15
+
16
+ class ChartRadius(Enum):
17
+ """Standard radius values for different chart elements."""
18
+ ANGLE_TRANSIT = 76
19
+ ANGLE_NATAL = 40
20
+ ANGLE_EXTERNAL_NATAL = 30
21
+ PLANET_PRIMARY = 94
22
+ PLANET_ALTERNATE = 74
23
+ PLANET_TRANSIT_PRIMARY = 130
24
+ PLANET_TRANSIT_ALTERNATE = 110
25
+ PLANET_EXTERNAL_PRIMARY = 84
26
+ PLANET_EXTERNAL_ALTERNATE = 64
27
+
28
+
29
+ class ChartConstants:
30
+ """Configuration constants for chart drawing."""
31
+ PLANET_GROUPING_THRESHOLD = 3.4
32
+ SECONDARY_GROUPING_THRESHOLD = 2.5
33
+ ANGLE_INDEX_RANGE = (22, 27) # ASC, MC, DSC, IC indices
34
+ SYMBOL_SCALE_FACTOR = 0.8
35
+ SYMBOL_SIZE = 12
36
+ MAX_DEGREE_DISTANCE = 360.0
37
+
38
+ # Line styling
39
+ LINE_OPACITY_PRIMARY = 0.3
40
+ LINE_OPACITY_SECONDARY = 0.5
41
+ LINE_WIDTH_THIN = 1
42
+ LINE_WIDTH_THICK = 2
43
+
44
+ # Text styling
45
+ TEXT_SIZE = 10
46
+
47
+
48
+ @dataclass
49
+ class PointPosition:
50
+ """Represents a celestial point's position and distance information."""
51
+ index: int
52
+ position_index: int
53
+ distance_to_prev: float
54
+ distance_to_next: float
55
+ label: str
56
+
57
+
58
+ @dataclass
59
+ class ChartConfiguration:
60
+ """Configuration for chart drawing parameters."""
61
+ radius: float
62
+ third_circle_radius: float
63
+ first_house_degree: float
64
+ seventh_house_degree: float
65
+ chart_type: ChartType
66
+ scale_factor: float = 1.0
67
+
68
+ def __post_init__(self):
69
+ """Set scale factor based on chart type."""
70
+ if self.chart_type in ["Transit", "Synastry", "Return", "ExternalNatal"]:
71
+ self.scale_factor = ChartConstants.SYMBOL_SCALE_FACTOR
72
+
73
+
74
+ class CelestialPointGrouper:
75
+ """Handles grouping of celestial points that are close together."""
76
+
77
+ def __init__(self, threshold: float = ChartConstants.PLANET_GROUPING_THRESHOLD):
78
+ self.threshold = threshold
79
+
80
+ def create_position_mapping(
81
+ self,
82
+ celestial_points: List[KerykeionPointModel],
83
+ settings: List[KerykeionSettingsCelestialPointModel]
84
+ ) -> Tuple[Dict[float, int], List[float]]:
85
+ """Create mapping from absolute positions to point indices."""
86
+ position_index_map = {}
87
+ for i, point in enumerate(celestial_points):
88
+ position_index_map[point.abs_pos] = i
89
+ logging.debug(f"Point {settings[i]['label']}: index {i}, degree {point.abs_pos}")
90
+
91
+ return position_index_map, sorted(position_index_map.keys())
92
+
93
+ def calculate_distances(
94
+ self,
95
+ sorted_positions: List[float],
96
+ position_index_map: Dict[float, int],
97
+ abs_positions: List[float]
98
+ ) -> List[PointPosition]:
99
+ """Calculate distances between adjacent points."""
100
+ point_positions = []
101
+
102
+ for position_idx, abs_position in enumerate(sorted_positions):
103
+ point_idx = position_index_map[abs_position]
104
+
105
+ if len(sorted_positions) == 1:
106
+ # Single point case
107
+ distance_to_prev = distance_to_next = ChartConstants.MAX_DEGREE_DISTANCE
108
+ else:
109
+ prev_idx, next_idx = self._get_adjacent_indices(
110
+ position_idx, sorted_positions, position_index_map
111
+ )
112
+ distance_to_prev = degreeDiff(abs_positions[prev_idx], abs_positions[point_idx])
113
+ distance_to_next = degreeDiff(abs_positions[next_idx], abs_positions[point_idx])
114
+
115
+ point_positions.append(PointPosition(
116
+ index=point_idx,
117
+ position_index=position_idx,
118
+ distance_to_prev=distance_to_prev,
119
+ distance_to_next=distance_to_next,
120
+ label=f"point_{point_idx}" # Will be updated by caller
121
+ ))
122
+
123
+ return point_positions
124
+
125
+ def _get_adjacent_indices(
126
+ self,
127
+ position_idx: int,
128
+ sorted_positions: List[float],
129
+ position_index_map: Dict[float, int]
130
+ ) -> Tuple[int, int]:
131
+ """Get indices of previous and next points."""
132
+ total_positions = len(sorted_positions)
133
+
134
+ if position_idx == 0:
135
+ prev_position = sorted_positions[-1]
136
+ next_position = sorted_positions[1]
137
+ elif position_idx == total_positions - 1:
138
+ prev_position = sorted_positions[position_idx - 1]
139
+ next_position = sorted_positions[0]
140
+ else:
141
+ prev_position = sorted_positions[position_idx - 1]
142
+ next_position = sorted_positions[position_idx + 1]
143
+
144
+ return position_index_map[prev_position], position_index_map[next_position]
145
+
146
+ def identify_groups(self, point_positions: List[PointPosition]) -> List[List[PointPosition]]:
147
+ """Identify groups of points that are close together."""
148
+ groups = []
149
+ current_group = []
150
+
151
+ for point_pos in point_positions:
152
+ if point_pos.distance_to_next < self.threshold:
153
+ current_group.append(point_pos)
154
+ else:
155
+ if current_group:
156
+ current_group.append(point_pos)
157
+ groups.append(current_group)
158
+ current_group = []
159
+
160
+ # Handle case where last group wraps around
161
+ if current_group and groups and point_positions[0] in groups[0]:
162
+ groups[0] = current_group + groups[0]
163
+ elif current_group:
164
+ groups.append(current_group)
165
+
166
+ return groups
167
+
168
+
169
+ class PositionAdjuster:
170
+ """Calculates position adjustments to prevent overlapping points."""
171
+
172
+ def __init__(self, threshold: float = ChartConstants.PLANET_GROUPING_THRESHOLD):
173
+ self.threshold = threshold
174
+
175
+ def calculate_adjustments(
176
+ self,
177
+ groups: List[List[PointPosition]],
178
+ total_points: int
179
+ ) -> List[float]:
180
+ """Calculate position adjustments for all points."""
181
+ adjustments = [0.0] * total_points
182
+
183
+ for group in groups:
184
+ if len(group) == 2:
185
+ self._handle_two_point_group(group, adjustments)
186
+ elif len(group) >= 3:
187
+ self._handle_multi_point_group(group, adjustments)
188
+
189
+ return adjustments
190
+
191
+ def _handle_two_point_group(
192
+ self,
193
+ group: List[PointPosition],
194
+ adjustments: List[float]
195
+ ) -> None:
196
+ """Handle positioning for a group of two points."""
197
+ point_a, point_b = group[0], group[1]
198
+
199
+ # Check available space around the group
200
+ if (point_a.distance_to_prev > 2 * self.threshold and
201
+ point_b.distance_to_next > 2 * self.threshold):
202
+ # Both points have room
203
+ offset = (self.threshold - point_a.distance_to_next) / 2
204
+ adjustments[point_a.position_index] = -offset
205
+ adjustments[point_b.position_index] = +offset
206
+ elif point_a.distance_to_prev > 2 * self.threshold:
207
+ # Only first point has room
208
+ adjustments[point_a.position_index] = -self.threshold
209
+ elif point_b.distance_to_next > 2 * self.threshold:
210
+ # Only second point has room
211
+ adjustments[point_b.position_index] = +self.threshold
212
+
213
+ def _handle_multi_point_group(
214
+ self,
215
+ group: List[PointPosition],
216
+ adjustments: List[float]
217
+ ) -> None:
218
+ """Handle positioning for groups of three or more points."""
219
+ group_size = len(group)
220
+
221
+ # Calculate available and needed space
222
+ available_space = group[0].distance_to_prev
223
+ for point in group:
224
+ available_space += point.distance_to_next
225
+
226
+ needed_space = 3 * self.threshold + 1.2 * (group_size - 1) * self.threshold
227
+
228
+ if available_space > needed_space:
229
+ # Distribute points evenly
230
+ spacing = 1.2 * self.threshold
231
+ start_offset = (available_space - needed_space) / 2
232
+
233
+ for i, point in enumerate(group):
234
+ adjustments[point.position_index] = start_offset + i * spacing - group[0].distance_to_prev
235
+
236
+
237
+ class RadiusCalculator:
238
+ """Calculates appropriate radius for different point types and chart types."""
239
+
240
+ @staticmethod
241
+ def get_point_radius(
242
+ point_idx: int,
243
+ chart_type: ChartType,
244
+ is_alternate: bool = False
245
+ ) -> int:
246
+ """Get radius for a celestial point based on its type and chart context."""
247
+ is_angle = ChartConstants.ANGLE_INDEX_RANGE[0] < point_idx < ChartConstants.ANGLE_INDEX_RANGE[1]
248
+
249
+ if chart_type in ["Transit", "Synastry", "Return"]:
250
+ if is_angle:
251
+ return ChartRadius.ANGLE_TRANSIT.value
252
+ return (ChartRadius.PLANET_TRANSIT_ALTERNATE.value if is_alternate
253
+ else ChartRadius.PLANET_TRANSIT_PRIMARY.value)
254
+
255
+ elif chart_type == "ExternalNatal":
256
+ if is_angle:
257
+ return ChartRadius.ANGLE_EXTERNAL_NATAL.value
258
+ return (ChartRadius.PLANET_EXTERNAL_ALTERNATE.value if is_alternate
259
+ else ChartRadius.PLANET_EXTERNAL_PRIMARY.value)
260
+
261
+ else: # Natal chart
262
+ if is_angle:
263
+ return ChartRadius.ANGLE_NATAL.value
264
+ return (ChartRadius.PLANET_ALTERNATE.value if is_alternate
265
+ else ChartRadius.PLANET_PRIMARY.value)
266
+
267
+
268
+ class SVGRenderer:
269
+ """Handles SVG generation for celestial points."""
270
+
271
+ def __init__(self, config: ChartConfiguration):
272
+ self.config = config
273
+
274
+ def calculate_offset(self, point_degree: float, adjustment: float = 0) -> float:
275
+ """Calculate the angular offset for positioning a point."""
276
+ return (-self.config.seventh_house_degree) + point_degree + adjustment
277
+
278
+ def generate_point_svg(
279
+ self,
280
+ point: KerykeionPointModel,
281
+ x: float,
282
+ y: float,
283
+ point_name: str
284
+ ) -> str:
285
+ """Generate SVG element for a celestial point."""
286
+ scale = self.config.scale_factor
287
+ transform_offset = ChartConstants.SYMBOL_SIZE * scale
288
+
289
+ svg_parts = [
290
+ f'<g kr:node="ChartPoint" kr:house="{point.house}" kr:sign="{point.sign}" ',
291
+ f'kr:slug="{point.name}" transform="translate(-{transform_offset},-{transform_offset}) scale({scale})">',
292
+ f'<use x="{x / scale}" y="{y / scale}" xlink:href="#{point_name}" />',
293
+ '</g>'
294
+ ]
295
+
296
+ return ''.join(svg_parts)
297
+
298
+ def draw_external_natal_lines(
299
+ self,
300
+ point_radius: float,
301
+ true_offset: float,
302
+ adjusted_offset: float,
303
+ color: str
304
+ ) -> str:
305
+ """Draw connecting lines for ExternalNatal chart type."""
306
+ lines = []
307
+
308
+ # First line segment
309
+ x1 = sliceToX(0, self.config.radius - self.config.third_circle_radius, true_offset) + self.config.third_circle_radius
310
+ y1 = sliceToY(0, self.config.radius - self.config.third_circle_radius, true_offset) + self.config.third_circle_radius
311
+ x2 = sliceToX(0, self.config.radius - point_radius - 30, true_offset) + point_radius + 30
312
+ y2 = sliceToY(0, self.config.radius - point_radius - 30, true_offset) + point_radius + 30
313
+
314
+ lines.append(f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" '
315
+ f'style="stroke-width:{ChartConstants.LINE_WIDTH_THIN}px;stroke:{color};'
316
+ f'stroke-opacity:{ChartConstants.LINE_OPACITY_PRIMARY};"/>')
317
+
318
+ # Second line segment
319
+ x3 = sliceToX(0, self.config.radius - point_radius - 10, adjusted_offset) + point_radius + 10
320
+ y3 = sliceToY(0, self.config.radius - point_radius - 10, adjusted_offset) + point_radius + 10
321
+
322
+ lines.append(f'<line x1="{x2}" y1="{y2}" x2="{x3}" y2="{y3}" '
323
+ f'style="stroke-width:{ChartConstants.LINE_WIDTH_THIN}px;stroke:{color};'
324
+ f'stroke-opacity:{ChartConstants.LINE_OPACITY_SECONDARY};"/>')
325
+
326
+ return '\n'.join(lines) + '\n'
327
+
328
+
329
+ class SecondaryPointsRenderer:
330
+ """Handles rendering of secondary points (transit, synastry, return)."""
331
+
332
+ def __init__(self, config: ChartConfiguration):
333
+ self.config = config
334
+ self.grouper = CelestialPointGrouper(ChartConstants.SECONDARY_GROUPING_THRESHOLD)
335
+
336
+ def draw_secondary_points(
337
+ self,
338
+ points_abs_positions: List[float],
339
+ points_rel_positions: List[float],
340
+ points_settings: List[KerykeionSettingsCelestialPointModel],
341
+ exclude_points: List[str]
342
+ ) -> str:
343
+ """Draw all secondary celestial points."""
344
+ if not points_abs_positions:
345
+ return ""
346
+
347
+ # Filter out excluded points
348
+ filtered_indices = [
349
+ i for i, setting in enumerate(points_settings)
350
+ if self.config.chart_type != "Transit" or setting["name"] not in exclude_points
351
+ ]
352
+
353
+ if not filtered_indices:
354
+ return ""
355
+
356
+ # Calculate position adjustments for grouping
357
+ adjustments = self._calculate_secondary_adjustments(
358
+ filtered_indices, points_abs_positions, points_settings
359
+ )
360
+
361
+ # Render each secondary point
362
+ output_parts = []
363
+ alternate_position = False
364
+
365
+ for idx in filtered_indices:
366
+ point_svg = self._render_single_secondary_point(
367
+ idx, points_abs_positions, points_rel_positions,
368
+ points_settings, adjustments, alternate_position
369
+ )
370
+ output_parts.append(point_svg)
371
+ alternate_position = not alternate_position
372
+
373
+ return ''.join(output_parts)
374
+
375
+ def _calculate_secondary_adjustments(
376
+ self,
377
+ indices: List[int],
378
+ positions: List[float],
379
+ settings: List[KerykeionSettingsCelestialPointModel]
380
+ ) -> Dict[int, float]:
381
+ """Calculate position adjustments for secondary points."""
382
+ # Create position mapping for filtered indices
383
+ position_map = {positions[i]: i for i in indices}
384
+ sorted_positions = sorted(position_map.keys())
385
+
386
+ # Find groups
387
+ groups = []
388
+ current_group = []
389
+
390
+ for i, pos in enumerate(sorted_positions):
391
+ point_idx = position_map[pos]
392
+ next_pos = sorted_positions[(i + 1) % len(sorted_positions)]
393
+ next_idx = position_map[next_pos]
394
+
395
+ distance = degreeDiff(positions[point_idx], positions[next_idx])
396
+
397
+ if distance <= self.grouper.threshold:
398
+ if not current_group:
399
+ current_group = [point_idx]
400
+ current_group.append(next_idx)
401
+ else:
402
+ if current_group:
403
+ groups.append(current_group)
404
+ current_group = []
405
+
406
+ if current_group:
407
+ groups.append(current_group)
408
+
409
+ # Calculate adjustments
410
+ adjustments = {i: 0.0 for i in indices}
411
+
412
+ for group in groups:
413
+ if len(group) == 2:
414
+ adjustments[group[0]] = -1.0
415
+ adjustments[group[1]] = 1.0
416
+ elif len(group) == 3:
417
+ adjustments[group[0]] = -1.5
418
+ adjustments[group[1]] = 0.0
419
+ adjustments[group[2]] = 1.5
420
+ elif len(group) >= 4:
421
+ for j, point_idx in enumerate(group):
422
+ adjustments[point_idx] = -2.0 + j * (4.0 / (len(group) - 1))
423
+
424
+ return adjustments
425
+
426
+ def _render_single_secondary_point(
427
+ self,
428
+ point_idx: int,
429
+ abs_positions: List[float],
430
+ rel_positions: List[float],
431
+ settings: List[KerykeionSettingsCelestialPointModel],
432
+ adjustments: Dict[int, float],
433
+ is_alternate: bool
434
+ ) -> str:
435
+ """Render a single secondary point with symbol, line, and degree text."""
436
+ # Determine radius
437
+ is_angle = ChartConstants.ANGLE_INDEX_RANGE[0] < point_idx < ChartConstants.ANGLE_INDEX_RANGE[1]
438
+ point_radius = 9 if is_angle else (18 if is_alternate else 26)
439
+
440
+ # Calculate position
441
+ point_offset = self._calculate_secondary_offset(abs_positions[point_idx])
442
+
443
+ # Generate SVG components
444
+ symbol_svg = self._generate_secondary_symbol(point_idx, point_radius, point_offset, settings)
445
+ line_svg = self._generate_secondary_line(point_idx, point_offset, settings)
446
+ text_svg = self._generate_secondary_text(
447
+ point_idx, abs_positions, rel_positions, settings, adjustments, point_offset
448
+ )
449
+
450
+ return symbol_svg + line_svg + text_svg
451
+
452
+ def _calculate_secondary_offset(self, abs_position: float) -> float:
453
+ """Calculate offset for secondary point positioning."""
454
+ zero_point = 360 - self.config.seventh_house_degree
455
+ offset = zero_point + abs_position
456
+ return offset - 360 if offset > 360 else offset
457
+
458
+ def _generate_secondary_symbol(
459
+ self, point_idx: int, radius: int, offset: float,
460
+ settings: List[KerykeionSettingsCelestialPointModel]
461
+ ) -> str:
462
+ """Generate SVG for secondary point symbol."""
463
+ x = sliceToX(0, self.config.radius - radius, offset) + radius
464
+ y = sliceToY(0, self.config.radius - radius, offset) + radius
465
+
466
+ return (f'<g class="transit-planet-name" transform="translate(-6,-6)">'
467
+ f'<g transform="scale(0.5)">'
468
+ f'<use x="{x*2}" y="{y*2}" xlink:href="#{settings[point_idx]["name"]}" />'
469
+ f'</g></g>')
470
+
471
+ def _generate_secondary_line(
472
+ self, point_idx: int, offset: float,
473
+ settings: List[KerykeionSettingsCelestialPointModel]
474
+ ) -> str:
475
+ """Generate connecting line for secondary point."""
476
+ x1 = sliceToX(0, self.config.radius + 3, offset) - 3
477
+ y1 = sliceToY(0, self.config.radius + 3, offset) - 3
478
+ x2 = sliceToX(0, self.config.radius - 3, offset) + 3
479
+ y2 = sliceToY(0, self.config.radius - 3, offset) + 3
480
+
481
+ color = settings[point_idx]["color"]
482
+ return (f'<line class="transit-planet-line" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" '
483
+ f'style="stroke: {color}; stroke-width: {ChartConstants.LINE_WIDTH_THIN}px; '
484
+ f'stroke-opacity:.8;"/>')
485
+
486
+ def _generate_secondary_text(
487
+ self,
488
+ point_idx: int,
489
+ abs_positions: List[float],
490
+ rel_positions: List[float],
491
+ settings: List[KerykeionSettingsCelestialPointModel],
492
+ adjustments: Dict[int, float],
493
+ point_offset: float
494
+ ) -> str:
495
+ """Generate degree text for secondary point."""
496
+ # Calculate rotation and text anchor
497
+ rotation = self.config.first_house_degree - abs_positions[point_idx]
498
+ text_anchor = "end"
499
+
500
+ # Adjust for readability
501
+ if -270 < rotation < -90:
502
+ rotation += 180.0
503
+ text_anchor = "start"
504
+ elif 90 < rotation < 270:
505
+ rotation -= 180.0
506
+ text_anchor = "start"
507
+
508
+ # Position text
509
+ x_offset = 1 if text_anchor == "end" else -1
510
+ adjusted_offset = point_offset + adjustments[point_idx]
511
+ text_radius = -3.0
512
+
513
+ deg_x = sliceToX(0, self.config.radius - text_radius, adjusted_offset + x_offset) + text_radius
514
+ deg_y = sliceToY(0, self.config.radius - text_radius, adjusted_offset + x_offset) + text_radius
515
+
516
+ # Format degree text
517
+ degree_text = convert_decimal_to_degree_string(rel_positions[point_idx], format_type="1")
518
+ color = settings[point_idx]["color"]
519
+
520
+ return (f'<g transform="translate({deg_x},{deg_y})">'
521
+ f'<text transform="rotate({rotation})" text-anchor="{text_anchor}" '
522
+ f'style="fill: {color}; font-size: {ChartConstants.TEXT_SIZE}px;">{degree_text}</text>'
523
+ f'</g>')
524
+
525
+
526
+ def _validate_chart_inputs(
527
+ chart_type: ChartType,
528
+ secondary_points: Optional[List[KerykeionPointModel]]
529
+ ) -> None:
530
+ """Validate that required secondary points are provided for chart types that need them."""
531
+ if _requires_secondary_points(chart_type) and secondary_points is None:
532
+ raise KerykeionException(f"Secondary celestial points are required for {chart_type} charts")
533
+
534
+
535
+ def _requires_secondary_points(chart_type: ChartType) -> bool:
536
+ """Check if chart type requires secondary celestial points."""
537
+ return chart_type in ["Transit", "Synastry", "Return"]
538
+
539
+
540
+ def _draw_main_points(
541
+ celestial_points: List[KerykeionPointModel],
542
+ settings: List[KerykeionSettingsCelestialPointModel],
543
+ config: ChartConfiguration,
544
+ grouper: CelestialPointGrouper,
545
+ adjuster: PositionAdjuster,
546
+ renderer: SVGRenderer
547
+ ) -> str:
548
+ """Draw the main celestial points with proper grouping and positioning."""
549
+ # Create position mapping and calculate distances
550
+ position_map, sorted_positions = grouper.create_position_mapping(celestial_points, settings)
551
+ abs_positions = [p.abs_pos for p in celestial_points]
552
+
553
+ point_positions = grouper.calculate_distances(sorted_positions, position_map, abs_positions)
554
+
555
+ # Update labels
556
+ for point_pos in point_positions:
557
+ point_pos.label = settings[point_pos.index]["label"]
558
+
559
+ # Identify groups and calculate adjustments
560
+ groups = grouper.identify_groups(point_positions)
561
+ adjustments = adjuster.calculate_adjustments(groups, len(settings))
562
+
563
+ # Draw each point
564
+ output_parts = []
565
+
566
+ for position_idx, abs_position in enumerate(sorted_positions):
567
+ point_idx = position_map[abs_position]
568
+
569
+ # Calculate positioning
570
+ point_radius = RadiusCalculator.get_point_radius(
571
+ point_idx, config.chart_type, bool(position_idx % 2)
572
+ )
573
+
574
+ adjusted_offset = renderer.calculate_offset(
575
+ abs_positions[point_idx], adjustments[position_idx]
576
+ )
577
+
578
+ # Calculate coordinates
579
+ point_x = sliceToX(0, config.radius - point_radius, adjusted_offset) + point_radius
580
+ point_y = sliceToY(0, config.radius - point_radius, adjusted_offset) + point_radius
581
+
582
+ # Draw external natal lines if needed
583
+ if config.chart_type == "ExternalNatal":
584
+ true_offset = renderer.calculate_offset(abs_positions[point_idx])
585
+ line_svg = renderer.draw_external_natal_lines(
586
+ point_radius, true_offset, adjusted_offset, settings[point_idx]["color"]
587
+ )
588
+ output_parts.append(line_svg)
589
+
590
+ # Generate point SVG
591
+ point_svg = renderer.generate_point_svg(
592
+ celestial_points[point_idx], point_x, point_y, settings[point_idx]["name"]
593
+ )
594
+ output_parts.append(point_svg)
595
+
596
+ return ''.join(output_parts)
597
+
598
+
599
+ def draw_planets_v2(
600
+ radius: Union[int, float],
601
+ available_kerykeion_celestial_points: List[KerykeionPointModel],
602
+ available_planets_setting: List[KerykeionSettingsCelestialPointModel],
603
+ third_circle_radius: Union[int, float],
604
+ main_subject_first_house_degree_ut: Union[int, float],
605
+ main_subject_seventh_house_degree_ut: Union[int, float],
606
+ chart_type: ChartType,
607
+ second_subject_available_kerykeion_celestial_points: Optional[List[KerykeionPointModel]] = None,
608
+ ) -> str:
609
+ """
610
+ Draw celestial points on an astrological chart.
611
+
612
+ This is the main entry point for drawing planets and other celestial points
613
+ on astrological charts. It handles positioning, grouping, and overlap resolution.
614
+
615
+ Args:
616
+ radius: Chart radius in pixels
617
+ available_kerykeion_celestial_points: Main subject's celestial points
618
+ available_planets_setting: Settings for celestial points
619
+ third_circle_radius: Radius of the third circle
620
+ main_subject_first_house_degree_ut: First house degree
621
+ main_subject_seventh_house_degree_ut: Seventh house degree
622
+ chart_type: Type of chart being drawn
623
+ second_subject_available_kerykeion_celestial_points: Secondary subject's points
624
+
625
+ Returns:
626
+ SVG string for the celestial points
627
+
628
+ Raises:
629
+ KerykeionException: If secondary points are required but not provided
630
+ """
631
+ # Validate inputs
632
+ _validate_chart_inputs(chart_type, second_subject_available_kerykeion_celestial_points)
633
+
634
+ # Create configuration
635
+ config = ChartConfiguration(
636
+ radius=float(radius),
637
+ third_circle_radius=float(third_circle_radius),
638
+ first_house_degree=float(main_subject_first_house_degree_ut),
639
+ seventh_house_degree=float(main_subject_seventh_house_degree_ut),
640
+ chart_type=chart_type
641
+ )
642
+
643
+ # Initialize components
644
+ grouper = CelestialPointGrouper()
645
+ adjuster = PositionAdjuster()
646
+ renderer = SVGRenderer(config)
647
+
648
+ # Process main celestial points
649
+ output_parts = []
650
+
651
+ if available_kerykeion_celestial_points:
652
+ main_svg = _draw_main_points(
653
+ available_kerykeion_celestial_points,
654
+ available_planets_setting,
655
+ config,
656
+ grouper,
657
+ adjuster,
658
+ renderer
659
+ )
660
+ output_parts.append(main_svg)
661
+
662
+ # Process secondary points if needed
663
+ if _requires_secondary_points(chart_type) and second_subject_available_kerykeion_celestial_points:
664
+ secondary_renderer = SecondaryPointsRenderer(config)
665
+
666
+ secondary_abs_positions = [p.abs_pos for p in second_subject_available_kerykeion_celestial_points]
667
+ secondary_rel_positions = [p.position for p in second_subject_available_kerykeion_celestial_points]
668
+ exclude_points = list(get_args(Houses)) if chart_type == "Transit" else []
669
+
670
+ secondary_svg = secondary_renderer.draw_secondary_points(
671
+ secondary_abs_positions,
672
+ secondary_rel_positions,
673
+ available_planets_setting,
674
+ exclude_points
675
+ )
676
+ output_parts.append(secondary_svg)
677
+
678
+ return ''.join(output_parts)
679
+
@@ -1,6 +1,4 @@
1
- <?xml version='1.0' encoding='UTF-8'?>
2
- <!--- This file is part of Kerykeion and is based on
3
- OpenAstro.org -->
1
+ <!--- This file is part of Kerykeion and is based on OpenAstro.org -->
4
2
  <svg
5
3
  xmlns="http://www.w3.org/2000/svg"
6
4
  xmlns:xlink="http://www.w3.org/1999/xlink"
@@ -1,6 +1,4 @@
1
- <?xml version='1.0' encoding='UTF-8'?>
2
- <!--- This file is part of Kerykeion and is based on
3
- OpenAstro.org -->
1
+ <!--- This file is part of Kerykeion and is based on OpenAstro.org -->
4
2
  <svg
5
3
  xmlns="http://www.w3.org/2000/svg"
6
4
  xmlns:xlink="http://www.w3.org/1999/xlink"
@@ -1,4 +1,3 @@
1
- <?xml version='1.0' encoding='UTF-8'?>
2
1
  <!--- This file is part of Kerykeion and is based on OpenAstro.org -->
3
2
  <svg
4
3
  xmlns="http://www.w3.org/2000/svg"
@@ -79,18 +79,18 @@ class AstrologicalBaseModel(SubscriptableBaseModel):
79
79
  # Common identification data
80
80
  name: str
81
81
 
82
- # Common location data
83
- city: str
84
- nation: str
85
- lng: float
86
- lat: float
87
- tz_str: str
88
-
89
- # Common time data
90
- iso_formatted_local_datetime: str
91
- iso_formatted_utc_datetime: str
92
- julian_day: float
93
- day_of_week: str
82
+ # Common location data (optional for composite charts)
83
+ city: Optional[str] = None
84
+ nation: Optional[str] = None
85
+ lng: Optional[float] = None
86
+ lat: Optional[float] = None
87
+ tz_str: Optional[str] = None
88
+
89
+ # Common time data (optional for composite charts)
90
+ iso_formatted_local_datetime: Optional[str] = None
91
+ iso_formatted_utc_datetime: Optional[str] = None
92
+ julian_day: Optional[float] = None
93
+ day_of_week: Optional[str] = None
94
94
 
95
95
  # Common configuration
96
96
  zodiac_type: ZodiacType
@@ -188,6 +188,17 @@ class AstrologicalSubjectModel(AstrologicalBaseModel):
188
188
  """
189
189
  Pydantic Model for Astrological Subject
190
190
  """
191
+ # Override base model to make location and time data required for subjects
192
+ city: str
193
+ nation: str
194
+ lng: float
195
+ lat: float
196
+ tz_str: str
197
+ iso_formatted_local_datetime: str
198
+ iso_formatted_utc_datetime: str
199
+ julian_day: float
200
+ day_of_week: str
201
+
191
202
  # Specific birth/event data
192
203
  year: int
193
204
  month: int
@@ -1,40 +1,36 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: kerykeion
3
- Version: 5.0.0a6
3
+ Version: 5.0.0a8
4
4
  Summary: A python library for astrology.
5
+ Project-URL: Homepage, https://www.kerykeion.net/
6
+ Project-URL: Repository, https://github.com/g-battaglia/kerykeion
7
+ Author-email: Giacomo Battaglia <kerykeion.astrology@gmail.com>
5
8
  License: AGPL-3.0
6
- Keywords: astrology,ephemeris,astrology library,birtchart,svg,zodiac,zodiac-sing,astronomical-algorithms,synastry,astrology-calculator
7
- Author: Giacomo Battaglia
8
- Author-email: kerykeion.astrology@gmail.com
9
- Requires-Python: >=3.9,<4.0
9
+ License-File: LICENSE
10
+ Keywords: astrology,astrology library,astrology-calculator,astronomical-algorithms,birtchart,ephemeris,svg,synastry,zodiac,zodiac-sing
10
11
  Classifier: Development Status :: 5 - Production/Stable
11
12
  Classifier: Intended Audience :: Developers
12
13
  Classifier: Intended Audience :: Information Technology
13
- Classifier: License :: OSI Approved :: GNU Affero General Public License v3
14
14
  Classifier: License :: OSI Approved :: GNU General Public License (GPL)
15
15
  Classifier: Operating System :: OS Independent
16
- Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
17
  Classifier: Programming Language :: Python :: 3.9
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
- Classifier: Programming Language :: Python :: 3.12
21
- Classifier: Programming Language :: Python :: 3.13
22
- Classifier: Programming Language :: Python :: 3 :: Only
23
20
  Classifier: Topic :: Scientific/Engineering :: Astronomy
24
21
  Classifier: Topic :: Software Development
25
22
  Classifier: Topic :: Software Development :: Libraries
26
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
24
  Classifier: Typing :: Typed
28
- Requires-Dist: pydantic (>=2.5,<3.0)
29
- Requires-Dist: pyswisseph (>=2.10.3.1,<3.0.0.0)
30
- Requires-Dist: pytz (>=2024.2,<2025.0)
31
- Requires-Dist: requests (>=2.32.3,<3.0.0)
32
- Requires-Dist: requests-cache (>=1.2.1,<2.0.0)
33
- Requires-Dist: scour (>=0.38.2,<0.39.0)
34
- Requires-Dist: simple-ascii-tables (>=1.0.0,<2.0.0)
35
- Requires-Dist: typing-extensions (>=4.12.2,<5.0.0)
36
- Project-URL: Homepage, https://www.kerykeion.net/
37
- Project-URL: Repository, https://github.com/g-battaglia/kerykeion
25
+ Requires-Python: >=3.9
26
+ Requires-Dist: pydantic>=2.5
27
+ Requires-Dist: pyswisseph>=2.10.3.1
28
+ Requires-Dist: pytz>=2024.2
29
+ Requires-Dist: requests-cache>=1.2.1
30
+ Requires-Dist: requests>=2.32.3
31
+ Requires-Dist: scour>=0.38.2
32
+ Requires-Dist: simple-ascii-tables>=1.0.0
33
+ Requires-Dist: typing-extensions>=4.12.2
38
34
  Description-Content-Type: text/markdown
39
35
 
40
36
  <h1 align="center">Kerykeion</h1>
@@ -638,4 +634,3 @@ If using Kerykeion in published or academic work, please cite as follows:
638
634
  Battaglia, G. (2025). Kerykeion: A Python Library for Astrological Calculations and Chart Generation.
639
635
  https://github.com/g-battaglia/kerykeion
640
636
  ```
641
-
@@ -1,27 +1,33 @@
1
1
  kerykeion/__init__.py,sha256=18VYgL_5JcB3JwzfICoHxtQTqzwlgUXHY5j4HG2gjZQ,920
2
+ kerykeion/astrological_subject_factory.py,sha256=z8ehPW6_wT6fwayxAmST0oSdLMM1pAmHXuwncSF_A20,52340
3
+ kerykeion/composite_subject_factory.py,sha256=d3omKRw7AlJFQVrOa1nBOu9jQx7W-2UrL8V7TKyVXHg,7551
4
+ kerykeion/enums.py,sha256=nPXgP_ocsRnRno5H-yunZy3fp-hLZ9aYRaUb-2gBdvw,1199
5
+ kerykeion/ephemeris_data.py,sha256=h56Y4FCTYPcIzSnDiKNXYzEpeiqU554ccUmfXENigls,10750
6
+ kerykeion/fetch_geonames.py,sha256=e66Nh6yq9A4VjnuvVSiV1TW1IkJ9m3Q2LKPWrkOGgO0,4764
7
+ kerykeion/planetary_return_factory.py,sha256=Y3jMHsI1epuBC4N3Mto0mubVE8BIT89dBh15eOATg2Y,11449
8
+ kerykeion/relationship_score_factory.py,sha256=g0RL1sx_LsMlfX1XVDf-z2LrlZ_SAL_1C0kuOGhKhsA,8495
9
+ kerykeion/report.py,sha256=w4KNQz0ADPPXE60PX6NVTxjppZ9q_naSbgYKQiX6Epk,2827
10
+ kerykeion/transits_time_range.py,sha256=iJSkXcNKJBcfrN8gN9P0xGKLsRBwJs-mMuAtPsvUqNE,5505
11
+ kerykeion/utilities.py,sha256=mD-oPKlbpwyXv5CXOS4aS6e6u0fFYl2lf9ptUr06jxs,22217
2
12
  kerykeion/aspects/__init__.py,sha256=9kx_Rx1NJx5SM7nDCSbI79S1neZ3c-q2NvQr-S6A9PY,292
3
13
  kerykeion/aspects/aspects_utils.py,sha256=NgxdeKd9CAPbzFlLe-yr9RFumM0Cc0kfWEONYREOFJ4,3252
4
14
  kerykeion/aspects/natal_aspects.py,sha256=SfI3ferjtyV47fBG6q3q1aczc-GFOwN4P7WpyZTgPSA,7269
5
15
  kerykeion/aspects/synastry_aspects.py,sha256=4ZBiFrbyyXxclnyaEIBeLJQO3uQVnpsqvI5Kj3-ijuI,5979
6
16
  kerykeion/aspects/transits_time_range.py,sha256=E03BJYcNK3kO30EC96Ys2EaH-EDyZF2nj4C7_LcFrOk,1182
7
- kerykeion/astrological_subject_factory.py,sha256=z8ehPW6_wT6fwayxAmST0oSdLMM1pAmHXuwncSF_A20,52340
8
17
  kerykeion/charts/__init__.py,sha256=i9NMZ7LdkllPlqQSi1or9gTobHbROGDKmJhBDO4R0mA,128
9
18
  kerykeion/charts/charts_utils.py,sha256=GCC20W4qHQXXKdUMlt72THHJxqp6ytj4X61oLONMSt4,62450
10
19
  kerykeion/charts/draw_planets.py,sha256=dbVCA4MFs7ChZKH4w1eErBiF_mluuxAD4fouBrXs-x0,17696
11
- kerykeion/charts/draw_planets_v2.py,sha256=IqyTLs1salrfJguwMtEpuscXg3kqyrrAAdXuGqzl-XQ,27630
20
+ kerykeion/charts/draw_planets_v2.py,sha256=ft0KjngCIfYKNGlZ4cv7apX9V6uyHetIVOxbNRzEmWU,28210
21
+ kerykeion/charts/draw_planets_v3.py,sha256=3nrpef56qBsc0hfCOGdfCOr-61C2c5aWcRSiBcQiAuo,25811
12
22
  kerykeion/charts/kerykeion_chart_svg.py,sha256=2Xhe2XvZLdoAIRxYh1MYceencbYJKxWXRoZZ30Kgra4,95638
13
- kerykeion/charts/templates/aspect_grid_only.xml,sha256=ZiBVeToVmCA8QxYlB_cfnsAO1NNeQAnJ_6rIYkr4F58,70091
14
- kerykeion/charts/templates/chart.xml,sha256=_VCtqsH7E97JUQbvx9i5OktdRLOAhlI2Znr7mEIMK8E,79839
15
- kerykeion/charts/templates/wheel_only.xml,sha256=hSV4FYD7qLKd93jq82B9mnGoH0PKyMRaqp3jL16T9YI,71287
23
+ kerykeion/charts/templates/aspect_grid_only.xml,sha256=goTM5zXKZMZCZPxsJAk80PQvQCgo2QccJLyH4HHpguo,70052
24
+ kerykeion/charts/templates/chart.xml,sha256=ZNfA19u1objUui1vCGJTvvwdE_Sg9R9O3LNdcNHEuaI,79800
25
+ kerykeion/charts/templates/wheel_only.xml,sha256=zR2EJBy6Io69enkuRfHWZxYdyeQsXGpl9_BkY2f4L2c,71248
16
26
  kerykeion/charts/themes/classic.css,sha256=LYYYWQHNG7-vuPCq8F2EPl3LauWXVrY3Q_tpwM0klgo,4660
17
27
  kerykeion/charts/themes/dark-high-contrast.css,sha256=YW5X5-M0dz5Vy2oz-JyDNXJYHSmV83lYCodydC-KS1A,7506
18
28
  kerykeion/charts/themes/dark.css,sha256=XVspznDRNMXsFzk7hY7IB7AI58alPAV_-CgV3gCKyzg,7581
19
29
  kerykeion/charts/themes/light.css,sha256=5eyUzhVlRjG6lPHKnprLum0HuRtPIJhMBzpGfzoTjnQ,7590
20
30
  kerykeion/charts/themes/strawberry.css,sha256=UtcfRsCT-M9OZs_SoclWGZ0jDJiWvQjHTeI4M1jf7pQ,8314
21
- kerykeion/composite_subject_factory.py,sha256=d3omKRw7AlJFQVrOa1nBOu9jQx7W-2UrL8V7TKyVXHg,7551
22
- kerykeion/enums.py,sha256=nPXgP_ocsRnRno5H-yunZy3fp-hLZ9aYRaUb-2gBdvw,1199
23
- kerykeion/ephemeris_data.py,sha256=h56Y4FCTYPcIzSnDiKNXYzEpeiqU554ccUmfXENigls,10750
24
- kerykeion/fetch_geonames.py,sha256=e66Nh6yq9A4VjnuvVSiV1TW1IkJ9m3Q2LKPWrkOGgO0,4764
25
31
  kerykeion/house_comparison/__init__.py,sha256=FQutYoG1T_ljkjJ0OAFvkeH8rASwMK7ieCpH_hDFJWU,139
26
32
  kerykeion/house_comparison/house_comparison_factory.py,sha256=R3O3R786yH6W2c8cKma8P7FNXU6JGl29omRHmucNN_Q,3369
27
33
  kerykeion/house_comparison/house_comparison_models.py,sha256=R9EW5F3fiIKIm8qrwTxtXhNOGDpf1-Lh9hbM1uK83oE,1549
@@ -30,11 +36,8 @@ kerykeion/kr_types/__init__.py,sha256=jshJOccCQcYZuoOvrILRZH6imy4RBvKpFPujlNLFyG
30
36
  kerykeion/kr_types/chart_types.py,sha256=ou9mIMNrQ7vlgWW4e209z_wMa2XZa4OGvHBr-CGk_rs,2309
31
37
  kerykeion/kr_types/kerykeion_exception.py,sha256=kE1y0K0rmuz32b4K_ZppSsZ59I2Get0ZkvOkTE5HejI,314
32
38
  kerykeion/kr_types/kr_literals.py,sha256=4kJhzm_0LERiJEJks0KgDyThueMZj_F1OK2Far5SMZc,4870
33
- kerykeion/kr_types/kr_models.py,sha256=ncMzW63eTkDNMSX7JaxXpCQyA1k0yEeaNeLhAHc0y_w,8345
39
+ kerykeion/kr_types/kr_models.py,sha256=nzq8no2dBd4b529ybznF46_VUEvDj5ZdDdlLRBE0RmM,8835
34
40
  kerykeion/kr_types/settings_models.py,sha256=qKlcn-Go1dAhC7oMFY_saogNcRa4WvKSewyvsKZt2S0,16604
35
- kerykeion/planetary_return_factory.py,sha256=Y3jMHsI1epuBC4N3Mto0mubVE8BIT89dBh15eOATg2Y,11449
36
- kerykeion/relationship_score_factory.py,sha256=g0RL1sx_LsMlfX1XVDf-z2LrlZ_SAL_1C0kuOGhKhsA,8495
37
- kerykeion/report.py,sha256=w4KNQz0ADPPXE60PX6NVTxjppZ9q_naSbgYKQiX6Epk,2827
38
41
  kerykeion/settings/__init__.py,sha256=QQNFCl7sgN27MKaVscqtpPk10HGz4wZS3I_7KEGMaVA,69
39
42
  kerykeion/settings/config_constants.py,sha256=R4fEe4WzRSq3BXqsnfgHj1oe_swkVMx3iGe-C_Zu0mw,1772
40
43
  kerykeion/settings/kerykeion_settings.py,sha256=umd8TZy-8ywowsd4TTkhwxSLLyX3xYj3A1zvNsTV_Y8,2955
@@ -45,10 +48,8 @@ kerykeion/settings/legacy/legacy_chart_aspects_settings.py,sha256=tO4tgPgPP07_wu
45
48
  kerykeion/settings/legacy/legacy_color_settings.py,sha256=gBUmGSNvvLzRYbdVtzwTDnMwWoh4tOCyT_9Q6aQRv_s,2620
46
49
  kerykeion/sweph/README.md,sha256=L7FtNAJTWtrZNGKa8MX87SjduFYPYxwWhaI5fmtzNZo,73
47
50
  kerykeion/sweph/seas_18.se1,sha256=X9nCqhZU43wJpq61WAdueVQJt9xL2UjrwPqn1Kdoa1s,223002
48
- kerykeion/transits_time_range.py,sha256=iJSkXcNKJBcfrN8gN9P0xGKLsRBwJs-mMuAtPsvUqNE,5505
49
- kerykeion/utilities.py,sha256=mD-oPKlbpwyXv5CXOS4aS6e6u0fFYl2lf9ptUr06jxs,22217
50
- kerykeion-5.0.0a6.dist-info/LICENSE,sha256=UTLH8EdbAsgQei4PA2PnBCPGLSZkq5J-dhkyJuXgWQU,34273
51
- kerykeion-5.0.0a6.dist-info/METADATA,sha256=nEomd4tVQo18cU9U5xSjE40Sjyy6cf9OsdZr6Nxt2PM,25398
52
- kerykeion-5.0.0a6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
53
- kerykeion-5.0.0a6.dist-info/entry_points.txt,sha256=5SmANYscFDDTdeovHvGQ-cnj0hdFvGoxPaWLCpyDFnQ,49
54
- kerykeion-5.0.0a6.dist-info/RECORD,,
51
+ kerykeion-5.0.0a8.dist-info/METADATA,sha256=XRm8bm5JWbAt-t0A4LUUOedawGjeACYyYBQQ_oGqLlc,25100
52
+ kerykeion-5.0.0a8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
+ kerykeion-5.0.0a8.dist-info/entry_points.txt,sha256=j9J6dg4czXFgM3LDOxoWFl8UDU4LLe2bD_U0RgpyLGA,50
54
+ kerykeion-5.0.0a8.dist-info/licenses/LICENSE,sha256=UTLH8EdbAsgQei4PA2PnBCPGLSZkq5J-dhkyJuXgWQU,34273
55
+ kerykeion-5.0.0a8.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ create-docs = scripts.docs:main
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- create-docs=scripts.docs:main
3
-