Преглед на файлове

refactor: use `enum` instead of `dict`s for the events and moon phase (#129)

BREAKING CHANGE: some methodes in Event and MoonPhase have been dropped
in favor of `enum.Enum`'s `name` and `value` properties.
tags/v0.9.0
Jérôme Deuchnord преди 4 години
committed by Jérôme Deuchnord
родител
ревизия
8b723bfd22
No known key found for this signature in database GPG ключ ID: 72F9D1A7272D53DD
променени са 11 файла, в които са добавени 282 реда и са изтрити 267 реда
  1. +15
    -2
      .github/workflows/i18n.yml
  2. +6
    -2
      Makefile
  3. +17
    -94
      kosmorrolib/data.py
  4. +5
    -7
      kosmorrolib/dumper.py
  5. +40
    -0
      kosmorrolib/enum.py
  6. +41
    -2
      kosmorrolib/ephemerides.py
  7. +9
    -6
      kosmorrolib/events.py
  8. +90
    -94
      kosmorrolib/locales/messages.pot
  9. +4
    -3
      test/dumper.py
  10. +34
    -31
      test/ephemerides.py
  11. +21
    -26
      test/events.py

+ 15
- 2
.github/workflows/i18n.yml Целия файл

@@ -19,13 +19,26 @@ jobs:
- name: Check i18n
run: |
pipenv run python setup.py extract_messages --output-file=/tmp/kosmorro-messages.pot > /dev/null
n=$(diff -y --suppress-common-lines kosmorrolib/locales/messages.pot /tmp/kosmorro-messages.pot | grep -v -E '^"POT-Creation-Date: ' | wc -l)
diff=$(diff kosmorrolib/locales/messages.pot /tmp/kosmorro-messages.pot | grep '^>')
n=$(echo "$diff" | grep -v '> "POT-Creation-Date: ' | wc -l)

if [ "$(echo "$diff" | grep -E '^"Generated-By: Babel' | wc -l)" -eq "1" ]; then
echo "❌ You dependencies may be out of date!"
echo " Please run the following command to fix this:"
echo
echo " pipenv sync --dev"
echo
echo " Then update the messages file:"
echo
echo " make messages"
exit 2
fi

if [ "$n" -ne "0" ]; then
echo "❌ The messages file is not up-to-date!"
echo " Please run the following command to fix this:"
echo
echo " pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot"
echo " make messages"
exit 1
fi



+ 6
- 2
Makefile Целия файл

@@ -6,13 +6,17 @@ test:
unset KOSMORRO_TIMEZONE; \
LANG=C pipenv run python3 -m coverage run -m unittest test

build: i18n
build: i18n manpages
python3 setup.py sdist bdist_wheel

i18n:
messages:
pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot

manpages:
ronn --roff manpage/kosmorro.1.md
ronn --roff manpage/kosmorro.7.md

i18n:
if [ "$$POEDITOR_API_ACCESS" != "" ]; then \
python3 .scripts/build/getlangs.py; \
python3 setup.py compile_catalog; \


+ 17
- 94
kosmorrolib/data.py Целия файл

@@ -25,30 +25,10 @@ from numpy import pi, arcsin
from skyfield.api import Topos, Time
from skyfield.vectorlib import VectorSum as SkfPlanet

from .core import get_skf_objects, get_timescale
from .core import get_skf_objects
from .enum import MoonPhaseType, EventType
from .i18n import _

MOON_PHASES = {
'NEW_MOON': _('New Moon'),
'WAXING_CRESCENT': _('Waxing crescent'),
'FIRST_QUARTER': _('First Quarter'),
'WAXING_GIBBOUS': _('Waxing gibbous'),
'FULL_MOON': _('Full Moon'),
'WANING_GIBBOUS': _('Waning gibbous'),
'LAST_QUARTER': _('Last Quarter'),
'WANING_CRESCENT': _('Waning crescent'),
'UNKNOWN': _('Unavailable')
}

EVENTS = {
'OPPOSITION': {'message': _('%s is in opposition')},
'CONJUNCTION': {'message': _('%s and %s are in conjunction')},
'OCCULTATION': {'message': _('%s occults %s')},
'MAXIMAL_ELONGATION': {'message': _("%s's largest elongation")},
'MOON_PERIGEE': {'message': _("%s is at its perigee")},
'MOON_APOGEE': {'message': _("%s is at its apogee")},
}


class Serializable(ABC):
@abstractmethod
@@ -57,40 +37,27 @@ class Serializable(ABC):


class MoonPhase(Serializable):
def __init__(self, identifier: str, time: datetime = None, next_phase_date: datetime = None):
if identifier not in MOON_PHASES.keys():
raise ValueError('identifier parameter must be one of %s (got %s)' % (', '.join(MOON_PHASES.keys()),
identifier))

self.identifier = identifier
def __init__(self, phase_type: MoonPhaseType, time: datetime = None, next_phase_date: datetime = None):
self.phase_type = phase_type
self.time = time
self.next_phase_date = next_phase_date

def get_phase(self):
return MOON_PHASES[self.identifier]

def get_next_phase_name(self):
next_identifier = self.get_next_phase()

return MOON_PHASES[next_identifier]

def get_next_phase(self):
if self.identifier == 'NEW_MOON' or self.identifier == 'WAXING_CRESCENT':
next_identifier = 'FIRST_QUARTER'
elif self.identifier == 'FIRST_QUARTER' or self.identifier == 'WAXING_GIBBOUS':
next_identifier = 'FULL_MOON'
elif self.identifier == 'FULL_MOON' or self.identifier == 'WANING_GIBBOUS':
next_identifier = 'LAST_QUARTER'
else:
next_identifier = 'NEW_MOON'
return next_identifier
if self.phase_type in [MoonPhaseType.NEW_MOON, MoonPhaseType.WAXING_CRESCENT]:
return MoonPhaseType.FIRST_QUARTER
if self.phase_type in [MoonPhaseType.FIRST_QUARTER, MoonPhaseType.WAXING_GIBBOUS]:
return MoonPhaseType.FULL_MOON
if self.phase_type in [MoonPhaseType.FULL_MOON, MoonPhaseType.WANING_GIBBOUS]:
return MoonPhaseType.LAST_QUARTER

return MoonPhaseType.NEW_MOON

def serialize(self) -> dict:
return {
'phase': self.identifier,
'phase': self.phase_type.name,
'time': self.time.isoformat() if self.time is not None else None,
'next': {
'phase': self.get_next_phase(),
'phase': self.get_next_phase().name,
'time': self.next_phase_date.isoformat()
}
}
@@ -168,13 +135,8 @@ class Satellite(Object):


class Event(Serializable):
def __init__(self, event_type: str, objects: [Object], start_time: datetime,
def __init__(self, event_type: EventType, objects: [Object], start_time: datetime,
end_time: Union[datetime, None] = None, details: str = None):
if event_type not in EVENTS.keys():
accepted_types = ', '.join(EVENTS.keys())
raise ValueError('event_type parameter must be one of the following: %s (got %s)' % (accepted_types,
event_type))

self.event_type = event_type
self.objects = objects
self.start_time = start_time
@@ -189,7 +151,7 @@ class Event(Serializable):
self.details)

def get_description(self, show_details: bool = True) -> str:
description = EVENTS[self.event_type]['message'] % self._get_objects_name()
description = self.event_type.value % self._get_objects_name()
if show_details and self.details is not None:
description += ' ({:s})'.format(self.details)
return description
@@ -203,50 +165,13 @@ class Event(Serializable):
def serialize(self) -> dict:
return {
'objects': [object.serialize() for object in self.objects],
'event': self.event_type,
'event': self.event_type.name,
'starts_at': self.start_time.isoformat(),
'ends_at': self.end_time.isoformat() if self.end_time is not None else None,
'details': self.details
}


def skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhase, None]:
tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1)

phases = list(MOON_PHASES.keys())
current_phase = None
current_phase_time = None
next_phase_time = None
i = 0

if len(times) == 0:
return None

for i, time in enumerate(times):
if now.utc_iso() <= time.utc_iso():
if vals[i] in [0, 2, 4, 6]:
if time.utc_datetime() < tomorrow.utc_datetime():
current_phase_time = time
current_phase = phases[vals[i]]
else:
i -= 1
current_phase_time = None
current_phase = phases[vals[i]]
else:
current_phase = phases[vals[i]]

break

for j in range(i + 1, len(times)):
if vals[j] in [0, 2, 4, 6]:
next_phase_time = times[j]
break

return MoonPhase(current_phase,
current_phase_time.utc_datetime() if current_phase_time is not None else None,
next_phase_time.utc_datetime() if next_phase_time is not None else None)


class AsterEphemerides(Serializable):
def __init__(self,
rise_time: Union[datetime, None],
@@ -267,8 +192,6 @@ class AsterEphemerides(Serializable):
}


MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']

EARTH = Planet('Earth', 'EARTH')

ASTERS = [Star(_('Sun'), 'SUN', radius=696342),


+ 5
- 7
kosmorrolib/dumper.py Целия файл

@@ -23,6 +23,7 @@ import os
from pathlib import Path
from tabulate import tabulate
from termcolor import colored

from .data import ASTERS, AsterEphemerides, MoonPhase, Event
from .i18n import _, FULL_DATE_FORMAT, SHORT_DATETIME_FORMAT, TIME_FORMAT
from .version import VERSION
@@ -159,9 +160,9 @@ class TextDumper(Dumper):
if moon_phase is None:
return _('Moon phase is unavailable for this date.')

current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), moon_phase.get_phase()])
current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), moon_phase.phase_type.value])
new_moon_phase = _('{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}').format(
next_moon_phase=moon_phase.get_next_phase_name(),
next_moon_phase=moon_phase.get_next_phase().value,
next_moon_phase_date=moon_phase.next_phase_date.strftime(FULL_DATE_FORMAT),
next_moon_phase_time=moon_phase.next_phase_date.strftime(TIME_FORMAT)
)
@@ -183,12 +184,9 @@ class _LatexDumper(Dumper):
kosmorro_logo_path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'assets', 'png', 'kosmorro-logo.png')

if self.moon_phase is None:
self.moon_phase = MoonPhase('UNKNOWN')

moon_phase_graphics = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'assets', 'moonphases', 'png',
'.'.join([self.moon_phase.identifier.lower().replace('_', '-'),
'.'.join([self.moon_phase.phase_type.name.lower().replace('_', '-'),
'png']))

document = template
@@ -234,7 +232,7 @@ class _LatexDumper(Dumper):
.replace('+++GRAPH_LABEL_HOURS+++', _('hours')) \
.replace('+++MOON-PHASE-GRAPHICS+++', moon_phase_graphics) \
.replace('+++CURRENT-MOON-PHASE-TITLE+++', _('Moon phase:')) \
.replace('+++CURRENT-MOON-PHASE+++', self.moon_phase.get_phase()) \
.replace('+++CURRENT-MOON-PHASE+++', self.moon_phase.phase_type.value) \
.replace('+++SECTION-EVENTS+++', _('Expected events')) \
.replace('+++EVENTS+++', self._make_events())



+ 40
- 0
kosmorrolib/enum.py Целия файл

@@ -0,0 +1,40 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from enum import Enum
from .i18n import _


class MoonPhaseType(Enum):
NEW_MOON = _('New Moon')
WAXING_CRESCENT = _('Waxing crescent')
FIRST_QUARTER = _('First Quarter')
WAXING_GIBBOUS = _('Waxing gibbous')
FULL_MOON = _('Full Moon')
WANING_GIBBOUS = _('Waning gibbous')
LAST_QUARTER = _('Last Quarter')
WANING_CRESCENT = _('Waning crescent')


class EventType(Enum):
OPPOSITION = _('%s is in opposition')
CONJUNCTION = _('%s and %s are in conjunction')
OCCULTATION = _('%s occults %s')
MAXIMAL_ELONGATION = _("%s's largest elongation")
MOON_PERIGEE = _("%s is at its perigee")
MOON_APOGEE = _("%s is at its apogee")

+ 41
- 2
kosmorrolib/ephemerides.py Целия файл

@@ -17,20 +17,59 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import datetime
from typing import Union

from skyfield.searchlib import find_discrete, find_maxima
from skyfield.timelib import Time
from skyfield.constants import tau
from skyfield.errors import EphemerisRangeError

from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS, skyfield_to_moon_phase
from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS
from .dateutil import translate_to_timezone
from .core import get_skf_objects, get_timescale, get_iau2000b
from .enum import MoonPhaseType
from .exceptions import OutOfRangeDateError

RISEN_ANGLE = -0.8333


def _get_skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhase, None]:
tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1)

phases = list(MoonPhaseType)
current_phase = None
current_phase_time = None
next_phase_time = None
i = 0

if len(times) == 0:
return None

for i, time in enumerate(times):
if now.utc_iso() <= time.utc_iso():
if vals[i] in [0, 2, 4, 6]:
if time.utc_datetime() < tomorrow.utc_datetime():
current_phase_time = time
current_phase = phases[vals[i]]
else:
i -= 1
current_phase_time = None
current_phase = phases[vals[i]]
else:
current_phase = phases[vals[i]]

break

for j in range(i + 1, len(times)):
if vals[j] in [0, 2, 4, 6]:
next_phase_time = times[j]
break

return MoonPhase(current_phase,
current_phase_time.utc_datetime() if current_phase_time is not None else None,
next_phase_time.utc_datetime() if next_phase_time is not None else None)


def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase:
earth = get_skf_objects()['earth']
moon = get_skf_objects()['moon']
@@ -60,7 +99,7 @@ def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase:

raise OutOfRangeDateError(start, end)

return skyfield_to_moon_phase(times, phase, today)
return _get_skyfield_to_moon_phase(times, phase, today)


def get_ephemerides(date: datetime.date, position: Position, timezone: int = 0) -> [AsterEphemerides]:


+ 9
- 6
kosmorrolib/events.py Целия файл

@@ -25,6 +25,7 @@ from numpy import pi

from .data import Event, Star, Planet, ASTERS
from .dateutil import translate_to_timezone
from .enum import EventType
from .exceptions import OutOfRangeDateError
from .core import get_timescale, get_skf_objects, flatten_list

@@ -68,10 +69,10 @@ def _search_conjunction(start_time: Time, end_time: Time, timezone: int) -> [Eve
aster2] if aster1_pos.distance().km < aster2_pos.distance().km else [aster2,
aster1]

conjunctions.append(Event('OCCULTATION', occulting_aster,
conjunctions.append(Event(EventType.OCCULTATION, occulting_aster,
translate_to_timezone(time.utc_datetime(), timezone)))
else:
conjunctions.append(Event('CONJUNCTION', [aster1, aster2],
conjunctions.append(Event(EventType.CONJUNCTION, [aster1, aster2],
translate_to_timezone(time.utc_datetime(), timezone)))

computed.append(aster1)
@@ -101,7 +102,7 @@ def _search_oppositions(start_time: Time, end_time: Time, timezone: int) -> [Eve

times, _ = find_discrete(start_time, end_time, is_oppositing)
for time in times:
events.append(Event('OPPOSITION', [aster], translate_to_timezone(time.utc_datetime(), timezone)))
events.append(Event(EventType.OPPOSITION, [aster], translate_to_timezone(time.utc_datetime(), timezone)))

return events

@@ -129,7 +130,9 @@ def _search_maximal_elongations(start_time: Time, end_time: Time, timezone: int)

for i, time in enumerate(times):
elongation = elongations[i]
events.append(Event('MAXIMAL_ELONGATION', [aster], translate_to_timezone(time.utc_datetime(), timezone),
events.append(Event(EventType.MAXIMAL_ELONGATION,
[aster],
translate_to_timezone(time.utc_datetime(), timezone),
details='{:.3n}°'.format(elongation)))

return events
@@ -157,7 +160,7 @@ def _search_moon_apogee(start_time: Time, end_time: Time, timezone: int) -> [Eve
times, _ = find_maxima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60)

for time in times:
events.append(Event('MOON_APOGEE', [moon], translate_to_timezone(time.utc_datetime(), timezone)))
events.append(Event(EventType.MOON_APOGEE, [moon], translate_to_timezone(time.utc_datetime(), timezone)))

return events

@@ -169,7 +172,7 @@ def _search_moon_perigee(start_time: Time, end_time: Time, timezone: int) -> [Ev
times, _ = find_minima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60)

for time in times:
events.append(Event('MOON_PERIGEE', [moon], translate_to_timezone(time.utc_datetime(), timezone)))
events.append(Event(EventType.MOON_PERIGEE, [moon], translate_to_timezone(time.utc_datetime(), timezone)))

return events



+ 90
- 94
kosmorrolib/locales/messages.pot Целия файл

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kosmorro 0.8.1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-01 12:01+0100\n"
"POT-Creation-Date: 2020-12-02 10:22+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -27,182 +27,116 @@ msgid ""
"offset format."
msgstr ""

#: kosmorrolib/data.py:32
msgid "New Moon"
msgstr ""

#: kosmorrolib/data.py:33
msgid "Waxing crescent"
msgstr ""

#: kosmorrolib/data.py:34
msgid "First Quarter"
msgstr ""

#: kosmorrolib/data.py:35
msgid "Waxing gibbous"
msgstr ""

#: kosmorrolib/data.py:36
msgid "Full Moon"
msgstr ""

#: kosmorrolib/data.py:37
msgid "Waning gibbous"
msgstr ""

#: kosmorrolib/data.py:38
msgid "Last Quarter"
msgstr ""

#: kosmorrolib/data.py:39
msgid "Waning crescent"
msgstr ""

#: kosmorrolib/data.py:40
msgid "Unavailable"
msgstr ""

#: kosmorrolib/data.py:44
#, python-format
msgid "%s is in opposition"
msgstr ""

#: kosmorrolib/data.py:45
#, python-format
msgid "%s and %s are in conjunction"
msgstr ""

#: kosmorrolib/data.py:46
#, python-format
msgid "%s occults %s"
msgstr ""

#: kosmorrolib/data.py:47
#, python-format
msgid "%s's largest elongation"
msgstr ""

#: kosmorrolib/data.py:48
#, python-format
msgid "%s is at its perigee"
msgstr ""

#: kosmorrolib/data.py:49
#, python-format
msgid "%s is at its apogee"
msgstr ""

#: kosmorrolib/data.py:274
#: kosmorrolib/data.py:197
msgid "Sun"
msgstr ""

#: kosmorrolib/data.py:275
#: kosmorrolib/data.py:198
msgid "Moon"
msgstr ""

#: kosmorrolib/data.py:276
#: kosmorrolib/data.py:199
msgid "Mercury"
msgstr ""

#: kosmorrolib/data.py:277
#: kosmorrolib/data.py:200
msgid "Venus"
msgstr ""

#: kosmorrolib/data.py:278
#: kosmorrolib/data.py:201
msgid "Mars"
msgstr ""

#: kosmorrolib/data.py:279
#: kosmorrolib/data.py:202
msgid "Jupiter"
msgstr ""

#: kosmorrolib/data.py:280
#: kosmorrolib/data.py:203
msgid "Saturn"
msgstr ""

#: kosmorrolib/data.py:281
#: kosmorrolib/data.py:204
msgid "Uranus"
msgstr ""

#: kosmorrolib/data.py:282
#: kosmorrolib/data.py:205
msgid "Neptune"
msgstr ""

#: kosmorrolib/data.py:283
#: kosmorrolib/data.py:206
msgid "Pluto"
msgstr ""

#: kosmorrolib/dumper.py:87
#: kosmorrolib/dumper.py:88
msgid "Expected events:"
msgstr ""

#: kosmorrolib/dumper.py:91
#: kosmorrolib/dumper.py:92
msgid "Note: All the hours are given in UTC."
msgstr ""

#: kosmorrolib/dumper.py:96
#: kosmorrolib/dumper.py:97
msgid "Note: All the hours are given in the UTC{offset} timezone."
msgstr ""

#: kosmorrolib/dumper.py:142 kosmorrolib/dumper.py:229
#: kosmorrolib/dumper.py:143 kosmorrolib/dumper.py:227
msgid "Object"
msgstr ""

#: kosmorrolib/dumper.py:143 kosmorrolib/dumper.py:230
#: kosmorrolib/dumper.py:144 kosmorrolib/dumper.py:228
msgid "Rise time"
msgstr ""

#: kosmorrolib/dumper.py:144 kosmorrolib/dumper.py:231
#: kosmorrolib/dumper.py:145 kosmorrolib/dumper.py:229
msgid "Culmination time"
msgstr ""

#: kosmorrolib/dumper.py:145 kosmorrolib/dumper.py:232
#: kosmorrolib/dumper.py:146 kosmorrolib/dumper.py:230
msgid "Set time"
msgstr ""

#: kosmorrolib/dumper.py:160
#: kosmorrolib/dumper.py:161
msgid "Moon phase is unavailable for this date."
msgstr ""

#: kosmorrolib/dumper.py:162 kosmorrolib/dumper.py:236
#: kosmorrolib/dumper.py:163 kosmorrolib/dumper.py:234
msgid "Moon phase:"
msgstr ""

#: kosmorrolib/dumper.py:163
#: kosmorrolib/dumper.py:164
msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}"
msgstr ""

#: kosmorrolib/dumper.py:216
#: kosmorrolib/dumper.py:214
msgid "A Summary of your Sky"
msgstr ""

#: kosmorrolib/dumper.py:220
#: kosmorrolib/dumper.py:218
msgid ""
"This document summarizes the ephemerides and the events of {date}. It "
"aims to help you to prepare your observation session. All the hours are "
"given in {timezone}."
msgstr ""

#: kosmorrolib/dumper.py:226
#: kosmorrolib/dumper.py:224
msgid ""
"Don't forget to check the weather forecast before you go out with your "
"equipment."
msgstr ""

#: kosmorrolib/dumper.py:228
#: kosmorrolib/dumper.py:226
msgid "Ephemerides of the day"
msgstr ""

#: kosmorrolib/dumper.py:234
#: kosmorrolib/dumper.py:232
msgid "hours"
msgstr ""

#: kosmorrolib/dumper.py:238
#: kosmorrolib/dumper.py:236
msgid "Expected events"
msgstr ""

#: kosmorrolib/dumper.py:352
#: kosmorrolib/dumper.py:350
msgid ""
"Building PDFs was not possible, because some dependencies are not "
"installed.\n"
@@ -210,6 +144,68 @@ msgid ""
"information."
msgstr ""

#: kosmorrolib/enum.py:24
msgid "New Moon"
msgstr ""

#: kosmorrolib/enum.py:25
msgid "Waxing crescent"
msgstr ""

#: kosmorrolib/enum.py:26
msgid "First Quarter"
msgstr ""

#: kosmorrolib/enum.py:27
msgid "Waxing gibbous"
msgstr ""

#: kosmorrolib/enum.py:28
msgid "Full Moon"
msgstr ""

#: kosmorrolib/enum.py:29
msgid "Waning gibbous"
msgstr ""

#: kosmorrolib/enum.py:30
msgid "Last Quarter"
msgstr ""

#: kosmorrolib/enum.py:31
msgid "Waning crescent"
msgstr ""

#: kosmorrolib/enum.py:35
#, python-format
msgid "%s is in opposition"
msgstr ""

#: kosmorrolib/enum.py:36
#, python-format
msgid "%s and %s are in conjunction"
msgstr ""

#: kosmorrolib/enum.py:37
#, python-format
msgid "%s occults %s"
msgstr ""

#: kosmorrolib/enum.py:38
#, python-format
msgid "%s's largest elongation"
msgstr ""

#: kosmorrolib/enum.py:39
#, python-format
msgid "%s is at its perigee"
msgstr ""

#: kosmorrolib/enum.py:40
#, python-format
msgid "%s is at its apogee"
msgstr ""

#: kosmorrolib/exceptions.py:34
msgid "The date must be between {minimum_date} and {maximum_date}"
msgstr ""


+ 4
- 3
test/dumper.py Целия файл

@@ -3,6 +3,7 @@ from datetime import date, datetime

from kosmorrolib.data import AsterEphemerides, Planet, MoonPhase, Event
from kosmorrolib.dumper import JsonDumper, TextDumper, _LatexDumper
from kosmorrolib.enum import MoonPhaseType, EventType


class DumperTestCase(unittest.TestCase):
@@ -283,14 +284,14 @@ class DumperTestCase(unittest.TestCase):

@staticmethod
def _get_moon_phase():
return MoonPhase('FULL_MOON', datetime(2019, 10, 14), datetime(2019, 10, 21))
return MoonPhase(MoonPhaseType.FULL_MOON, datetime(2019, 10, 14), datetime(2019, 10, 21))

@staticmethod
def _get_events():
return [Event('OPPOSITION',
return [Event(EventType.OPPOSITION,
[Planet('Mars', 'MARS')],
datetime(2019, 10, 14, 23, 00)),
Event('MAXIMAL_ELONGATION',
Event(EventType.MAXIMAL_ELONGATION,
[Planet('Venus', 'VENUS')],
datetime(2019, 10, 14, 12, 00), details='42.0°'),
]


+ 34
- 31
test/ephemerides.py Целия файл

@@ -1,7 +1,10 @@
import unittest

from .testutils import expect_assertions
from kosmorrolib import ephemerides
from kosmorrolib.data import EARTH, Position, MoonPhase
from kosmorrolib.enum import MoonPhaseType

from datetime import date
from kosmorrolib.exceptions import OutOfRangeDateError

@@ -29,84 +32,84 @@ class EphemeridesTestCase(unittest.TestCase):

def test_moon_phase_new_moon(self):
phase = ephemerides.get_moon_phase(date(2019, 11, 25))
self.assertEqual('WANING_CRESCENT', phase.identifier)
self.assertEqual(MoonPhaseType.WANING_CRESCENT, phase.phase_type)
self.assertIsNone(phase.time)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T')

phase = ephemerides.get_moon_phase(date(2019, 11, 26))
self.assertEqual('NEW_MOON', phase.identifier)
self.assertEqual(MoonPhaseType.NEW_MOON, phase.phase_type)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T')

phase = ephemerides.get_moon_phase(date(2019, 11, 27))
self.assertEqual('WAXING_CRESCENT', phase.identifier)
self.assertEqual(MoonPhaseType.WAXING_CRESCENT, phase.phase_type)
self.assertIsNone(phase.time)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T')

def test_moon_phase_first_crescent(self):
phase = ephemerides.get_moon_phase(date(2019, 11, 3))
self.assertEqual('WAXING_CRESCENT', phase.identifier)
self.assertEqual(MoonPhaseType.WAXING_CRESCENT, phase.phase_type)
self.assertIsNone(phase.time)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-04T')

phase = ephemerides.get_moon_phase(date(2019, 11, 4))
self.assertEqual('FIRST_QUARTER', phase.identifier)
self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.phase_type)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T')

phase = ephemerides.get_moon_phase(date(2019, 11, 5))
self.assertEqual('WAXING_GIBBOUS', phase.identifier)
self.assertEqual(MoonPhaseType.WAXING_GIBBOUS, phase.phase_type)
self.assertIsNone(phase.time)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T')

def test_moon_phase_full_moon(self):
phase = ephemerides.get_moon_phase(date(2019, 11, 11))
self.assertEqual('WAXING_GIBBOUS', phase.identifier)
self.assertEqual(MoonPhaseType.WAXING_GIBBOUS, phase.phase_type)
self.assertIsNone(phase.time)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T')

phase = ephemerides.get_moon_phase(date(2019, 11, 12))
self.assertEqual('FULL_MOON', phase.identifier)
self.assertEqual(MoonPhaseType.FULL_MOON, phase.phase_type)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T')

phase = ephemerides.get_moon_phase(date(2019, 11, 13))
self.assertEqual('WANING_GIBBOUS', phase.identifier)
self.assertEqual(MoonPhaseType.WANING_GIBBOUS, phase.phase_type)
self.assertIsNone(phase.time)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T')

def test_moon_phase_last_quarter(self):
phase = ephemerides.get_moon_phase(date(2019, 11, 18))
self.assertEqual('WANING_GIBBOUS', phase.identifier)
self.assertEqual(MoonPhaseType.WANING_GIBBOUS, phase.phase_type)
self.assertIsNone(phase.time)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T')

phase = ephemerides.get_moon_phase(date(2019, 11, 19))
self.assertEqual('LAST_QUARTER', phase.identifier)
self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.phase_type)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T')

phase = ephemerides.get_moon_phase(date(2019, 11, 20))
self.assertEqual('WANING_CRESCENT', phase.identifier)
self.assertEqual(MoonPhaseType.WANING_CRESCENT, phase.phase_type)
self.assertIsNone(phase.time)
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T')

def test_moon_phase_prediction(self):
phase = MoonPhase('NEW_MOON', None, None)
self.assertEqual('First Quarter', phase.get_next_phase_name())
phase = MoonPhase('WAXING_CRESCENT', None, None)
self.assertEqual('First Quarter', phase.get_next_phase_name())
phase = MoonPhase('FIRST_QUARTER', None, None)
self.assertEqual('Full Moon', phase.get_next_phase_name())
phase = MoonPhase('WAXING_GIBBOUS', None, None)
self.assertEqual('Full Moon', phase.get_next_phase_name())
phase = MoonPhase('FULL_MOON', None, None)
self.assertEqual('Last Quarter', phase.get_next_phase_name())
phase = MoonPhase('WANING_GIBBOUS', None, None)
self.assertEqual('Last Quarter', phase.get_next_phase_name())
phase = MoonPhase('LAST_QUARTER', None, None)
self.assertEqual('New Moon', phase.get_next_phase_name())
phase = MoonPhase('WANING_CRESCENT', None, None)
self.assertEqual('New Moon', phase.get_next_phase_name())
phase = MoonPhase(MoonPhaseType.NEW_MOON, None, None)
self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.get_next_phase())
phase = MoonPhase(MoonPhaseType.WAXING_CRESCENT, None, None)
self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.get_next_phase())
phase = MoonPhase(MoonPhaseType.FIRST_QUARTER, None, None)
self.assertEqual(MoonPhaseType.FULL_MOON, phase.get_next_phase())
phase = MoonPhase(MoonPhaseType.WAXING_GIBBOUS, None, None)
self.assertEqual(MoonPhaseType.FULL_MOON, phase.get_next_phase())
phase = MoonPhase(MoonPhaseType.FULL_MOON, None, None)
self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.get_next_phase())
phase = MoonPhase(MoonPhaseType.WANING_GIBBOUS, None, None)
self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.get_next_phase())
phase = MoonPhase(MoonPhaseType.LAST_QUARTER, None, None)
self.assertEqual(MoonPhaseType.NEW_MOON, phase.get_next_phase())
phase = MoonPhase(MoonPhaseType.WANING_CRESCENT, None, None)
self.assertEqual(MoonPhaseType.NEW_MOON, phase.get_next_phase())

def test_get_ephemerides_raises_exception_on_out_of_date_range(self):
with self.assertRaises(OutOfRangeDateError):


+ 21
- 26
test/events.py Целия файл

@@ -1,12 +1,11 @@
import unittest

from datetime import date, datetime
from json import dumps
from unittest_data_provider import data_provider

from kosmorrolib import events
from kosmorrolib.data import Event, ASTERS
from kosmorrolib.core import get_timescale
from unittest_data_provider import data_provider
from kosmorrolib.enum import EventType
from kosmorrolib.exceptions import OutOfRangeDateError


@@ -14,42 +13,38 @@ class EventTestCase(unittest.TestCase):
def setUp(self) -> None:
self.maxDiff = None

def test_event_only_accepts_valid_values(self):
with self.assertRaises(ValueError):
Event('SUPERNOVA', None, get_timescale().now())

expected_events_provider = lambda: (
(date(2020, 2, 7), []),

(date(2020, 10, 13), [Event('OPPOSITION', [ASTERS[4]], datetime(2020, 10, 13, 23, 25))]),
(date(2020, 10, 13), [Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2020, 10, 13, 23, 25))]),

(date(2022, 12, 8), [Event('CONJUNCTION', [ASTERS[1], ASTERS[4]], datetime(2022, 12, 8, 4, 18)),
Event('OPPOSITION', [ASTERS[4]], datetime(2022, 12, 8, 5, 41))]),
(date(2022, 12, 8), [Event(EventType.CONJUNCTION, [ASTERS[1], ASTERS[4]], datetime(2022, 12, 8, 4, 18)),
Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2022, 12, 8, 5, 41))]),

(date(2025, 1, 16), [Event('OPPOSITION', [ASTERS[4]], datetime(2025, 1, 16, 2, 38))]),
(date(2025, 1, 16), [Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2025, 1, 16, 2, 38))]),

(date(2027, 2, 19), [Event('MOON_PERIGEE', [ASTERS[1]], datetime(2027, 2, 19, 7, 38)),
Event('OPPOSITION', [ASTERS[4]], datetime(2027, 2, 19, 15, 50))]),
(date(2027, 2, 19), [Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2027, 2, 19, 7, 38)),
Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2027, 2, 19, 15, 50))]),

(date(2020, 1, 2), [Event('MOON_APOGEE', [ASTERS[1]], datetime(2020, 1, 2, 1, 32)),
Event('CONJUNCTION', [ASTERS[2], ASTERS[5]], datetime(2020, 1, 2, 16, 41))]),
(date(2020, 1, 2), [Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 2, 1, 32)),
Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[5]], datetime(2020, 1, 2, 16, 41))]),

(date(2020, 1, 12), [Event('CONJUNCTION', [ASTERS[2], ASTERS[6]], datetime(2020, 1, 12, 9, 51)),
Event('CONJUNCTION', [ASTERS[2], ASTERS[9]], datetime(2020, 1, 12, 10, 13)),
Event('CONJUNCTION', [ASTERS[6], ASTERS[9]], datetime(2020, 1, 12, 16, 57))]),
(date(2020, 1, 12), [Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[6]], datetime(2020, 1, 12, 9, 51)),
Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[9]], datetime(2020, 1, 12, 10, 13)),
Event(EventType.CONJUNCTION, [ASTERS[6], ASTERS[9]], datetime(2020, 1, 12, 16, 57))]),

(date(2020, 2, 10), [Event('MAXIMAL_ELONGATION', [ASTERS[2]], datetime(2020, 2, 10, 13, 46), details='18.2°'),
Event('MOON_PERIGEE', [ASTERS[1]], datetime(2020, 2, 10, 20, 34))]),
(date(2020, 2, 10), [Event(EventType.MAXIMAL_ELONGATION, [ASTERS[2]], datetime(2020, 2, 10, 13, 46), details='18.2°'),
Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 2, 10, 20, 34))]),

(date(2020, 3, 24), [Event('MAXIMAL_ELONGATION', [ASTERS[2]], datetime(2020, 3, 24, 1, 56), details='27.8°'),
Event('MOON_APOGEE', [ASTERS[1]], datetime(2020, 3, 24, 15, 39)),
Event('MAXIMAL_ELONGATION', [ASTERS[3]], datetime(2020, 3, 24, 21, 58), details='46.1°')]),
(date(2020, 3, 24), [Event(EventType.MAXIMAL_ELONGATION, [ASTERS[2]], datetime(2020, 3, 24, 1, 56), details='27.8°'),
Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 3, 24, 15, 39)),
Event(EventType.MAXIMAL_ELONGATION, [ASTERS[3]], datetime(2020, 3, 24, 21, 58), details='46.1°')]),

(date(2005, 6, 16), [Event('OCCULTATION', [ASTERS[1], ASTERS[5]], datetime(2005, 6, 16, 6, 31))]),
(date(2005, 6, 16), [Event(EventType.OCCULTATION, [ASTERS[1], ASTERS[5]], datetime(2005, 6, 16, 6, 31))]),

(date(2020, 4, 7), [Event('MOON_PERIGEE', [ASTERS[1]], datetime(2020, 4, 7, 18, 14))]),
(date(2020, 4, 7), [Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 4, 7, 18, 14))]),

(date(2020, 1, 29), [Event('MOON_APOGEE', [ASTERS[1]], datetime(2020, 1, 29, 21, 32))])
(date(2020, 1, 29), [Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 29, 21, 32))])
)

@data_provider(expected_events_provider)


Зареждане…
Отказ
Запис