From c956e52ce88d614ac6828054085cc196dfb085ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Deuchnord?= Date: Sat, 20 Mar 2021 16:54:07 +0100 Subject: [PATCH] ci(black): fix the coding standards --- Makefile | 3 + kosmorrolib/__version__.py | 16 ++-- kosmorrolib/core.py | 39 +++++---- kosmorrolib/data.py | 147 +++++++++++++++++++------------- kosmorrolib/dateutil.py | 4 +- kosmorrolib/enum.py | 2 + kosmorrolib/ephemerides.py | 90 ++++++++++++++------ kosmorrolib/events.py | 130 ++++++++++++++++++++--------- kosmorrolib/exceptions.py | 6 +- tests/core.py | 33 +++++--- tests/data.py | 10 ++- tests/dateutil.py | 10 ++- tests/ephemerides.py | 39 ++++----- tests/events.py | 167 ++++++++++++++++++++++++++----------- tests/testutils.py | 7 +- 15 files changed, 467 insertions(+), 236 deletions(-) diff --git a/Makefile b/Makefile index 8438cb2..8654a81 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +black: + pipenv run black kosmorrolib tests + .PHONY: test tests: legacy-tests python3 tests.py diff --git a/kosmorrolib/__version__.py b/kosmorrolib/__version__.py index fff308b..7718736 100644 --- a/kosmorrolib/__version__.py +++ b/kosmorrolib/__version__.py @@ -1,9 +1,9 @@ -__title__ = 'kosmorrolib' -__description__ = 'A library that computes your ephemerides' -__url__ = 'http://kosmorro.space' -__version__ = '0.9.0' +__title__ = "kosmorrolib" +__description__ = "A library that computes your ephemerides" +__url__ = "http://kosmorro.space" +__version__ = "0.9.0" __build__ = 0x000900 -__author__ = 'Jérôme Deuchnord' -__author_email__ = 'jerome@deuchnord.fr' -__license__ = 'AGPL' -__copyright__ = 'Copyright 2021 Jérôme Deuchnord' +__author__ = "Jérôme Deuchnord" +__author_email__ = "jerome@deuchnord.fr" +__license__ = "AGPL" +__copyright__ = "Copyright 2021 Jérôme Deuchnord" diff --git a/kosmorrolib/core.py b/kosmorrolib/core.py index 6a949a5..fa0bf7c 100644 --- a/kosmorrolib/core.py +++ b/kosmorrolib/core.py @@ -28,7 +28,8 @@ from skyfield.api import Loader from skyfield.timelib import Time from skyfield.nutationlib import iau2000b -CACHE_FOLDER = str(Path.home()) + '/.kosmorro-cache' +CACHE_FOLDER = str(Path.home()) + "/.kosmorro-cache" + class Environment: def __init__(self): @@ -46,18 +47,20 @@ class Environment: def __len__(self): return len(self._vars) + def get_env() -> Environment: environment = Environment() for var in os.environ: - if not re.search('^KOSMORRO_', var): + if not re.search("^KOSMORRO_", var): continue - [_, env] = var.split('_', 1) + [_, env] = var.split("_", 1) environment.__set__(env.lower(), os.getenv(var)) return environment + def get_loader(): return Loader(CACHE_FOLDER) @@ -67,7 +70,7 @@ def get_timescale(): def get_skf_objects(): - return get_loader()('de421.bsp') + return get_loader()("de421.bsp") def get_iau2000b(time: Time): @@ -92,26 +95,32 @@ def flatten_list(the_list: list): def get_date(date_arg: str) -> date: - if re.match(r'^\d{4}-\d{2}-\d{2}$', date_arg): + if re.match(r"^\d{4}-\d{2}-\d{2}$", date_arg): try: return date.fromisoformat(date_arg) except ValueError as error: - raise ValueError(_('The date {date} is not valid: {error}').format(date=date_arg, - error=error.args[0])) from error - elif re.match(r'^([+-])(([0-9]+)y)?[ ]?(([0-9]+)m)?[ ]?(([0-9]+)d)?$', date_arg): + raise ValueError( + _("The date {date} is not valid: {error}").format( + date=date_arg, error=error.args[0] + ) + ) from error + elif re.match(r"^([+-])(([0-9]+)y)?[ ]?(([0-9]+)m)?[ ]?(([0-9]+)d)?$", date_arg): + def get_offset(date_arg: str, signifier: str): - if re.search(r'([0-9]+)' + signifier, date_arg): - return abs(int(re.search(r'[+-]?([0-9]+)' + signifier, date_arg).group(0)[:-1])) + if re.search(r"([0-9]+)" + signifier, date_arg): + return abs( + int(re.search(r"[+-]?([0-9]+)" + signifier, date_arg).group(0)[:-1]) + ) return 0 - days = get_offset(date_arg, 'd') - months = get_offset(date_arg, 'm') - years = get_offset(date_arg, 'y') + days = get_offset(date_arg, "d") + months = get_offset(date_arg, "m") + years = get_offset(date_arg, "y") - if date_arg[0] == '+': + if date_arg[0] == "+": return date.today() + relativedelta(days=days, months=months, years=years) return date.today() - relativedelta(days=days, months=months, years=years) else: - error_msg = 'The date {date} does not match the required YYYY-MM-DD format or the offset format.' + error_msg = "The date {date} does not match the required YYYY-MM-DD format or the offset format." raise ValueError(error_msg.format(date=date_arg)) diff --git a/kosmorrolib/data.py b/kosmorrolib/data.py index 91e9d7b..933f7a9 100644 --- a/kosmorrolib/data.py +++ b/kosmorrolib/data.py @@ -36,7 +36,12 @@ class Serializable(ABC): class MoonPhase(Serializable): - def __init__(self, phase_type: MoonPhaseType, time: datetime = None, next_phase_date: datetime = None): + 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 @@ -44,7 +49,10 @@ class MoonPhase(Serializable): def get_next_phase(self): 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]: + 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 @@ -53,12 +61,12 @@ class MoonPhase(Serializable): def serialize(self) -> dict: return { - 'phase': self.phase_type.name, - 'time': self.time.isoformat() if self.time is not None else None, - 'next': { - 'phase': self.get_next_phase().name, - 'time': self.next_phase_date.isoformat() - } + "phase": self.phase_type.name, + "time": self.time.isoformat() if self.time is not None else None, + "next": { + "phase": self.get_next_phase().name, + "time": self.next_phase_date.isoformat(), + }, } @@ -67,10 +75,7 @@ class Object(Serializable): An astronomical object. """ - def __init__(self, - name: str, - skyfield_name: str, - radius: float = None): + def __init__(self, name: str, skyfield_name: str, radius: float = None): """ Initialize an astronomical object @@ -84,7 +89,7 @@ class Object(Serializable): self.radius = radius def __repr__(self): - return '' % (self.get_type(), self.name) + return "" % (self.get_type(), self.name) def get_skyfield_object(self) -> SkfPlanet: return get_skf_objects()[self.skyfield_name] @@ -101,41 +106,54 @@ class Object(Serializable): :return: """ if self.radius is None: - raise ValueError('Missing radius for %s object' % self.name) + raise ValueError("Missing radius for %s object" % self.name) - return 360 / pi * arcsin(self.radius / from_place.at(time).observe(self.get_skyfield_object()).distance().km) + return ( + 360 + / pi + * arcsin( + self.radius + / from_place.at(time).observe(self.get_skyfield_object()).distance().km + ) + ) def serialize(self) -> dict: return { - 'name': self.name, - 'type': self.get_type(), - 'radius': self.radius, + "name": self.name, + "type": self.get_type(), + "radius": self.radius, } class Star(Object): def get_type(self) -> str: - return 'star' + return "star" class Planet(Object): def get_type(self) -> str: - return 'planet' + return "planet" class DwarfPlanet(Planet): def get_type(self) -> str: - return 'dwarf_planet' + return "dwarf_planet" class Satellite(Object): def get_type(self) -> str: - return 'satellite' + return "satellite" class Event(Serializable): - def __init__(self, event_type: EventType, objects: [Object], start_time: datetime, - end_time: Union[datetime, None] = None, details: str = None): + def __init__( + self, + event_type: EventType, + objects: [Object], + start_time: datetime, + end_time: Union[datetime, None] = None, + details: str = None, + ): self.event_type = event_type self.objects = objects self.start_time = start_time @@ -143,16 +161,18 @@ class Event(Serializable): self.details = details def __repr__(self): - return '' % (self.event_type.name, - self.objects, - self.start_time, - self.end_time, - self.details) + return "" % ( + self.event_type.name, + self.objects, + self.start_time, + self.end_time, + self.details, + ) def get_description(self, show_details: bool = True) -> str: description = self.event_type.value % self._get_objects_name() if show_details and self.details is not None: - description += ' ({:s})'.format(self.details) + description += " ({:s})".format(self.details) return description def _get_objects_name(self): @@ -163,20 +183,22 @@ class Event(Serializable): def serialize(self) -> dict: return { - 'objects': [object.serialize() for object in self.objects], - 'EventType': 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 + "objects": [object.serialize() for object in self.objects], + "EventType": 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, } class AsterEphemerides(Serializable): - def __init__(self, - rise_time: Union[datetime, None], - culmination_time: Union[datetime, None], - set_time: Union[datetime, None], - aster: Object): + def __init__( + self, + rise_time: Union[datetime, None], + culmination_time: Union[datetime, None], + set_time: Union[datetime, None], + aster: Object, + ): self.rise_time = rise_time self.culmination_time = culmination_time self.set_time = set_time @@ -184,25 +206,33 @@ class AsterEphemerides(Serializable): def serialize(self) -> dict: return { - 'object': self.object.serialize(), - 'rise_time': self.rise_time.isoformat() if self.rise_time is not None else None, - 'culmination_time': self.culmination_time.isoformat() if self.culmination_time is not None else None, - 'set_time': self.set_time.isoformat() if self.set_time is not None else None + "object": self.object.serialize(), + "rise_time": self.rise_time.isoformat() + if self.rise_time is not None + else None, + "culmination_time": self.culmination_time.isoformat() + if self.culmination_time is not None + else None, + "set_time": self.set_time.isoformat() + if self.set_time is not None + else None, } -EARTH = Planet('Earth', 'EARTH') +EARTH = Planet("Earth", "EARTH") -ASTERS = [Star('Sun', 'SUN', radius=696342), - Satellite('Moon', 'MOON', radius=1737.4), - Planet('Mercury', 'MERCURY', radius=2439.7), - Planet('Venus', 'VENUS', radius=6051.8), - Planet('Mars', 'MARS', radius=3396.2), - Planet('Jupiter', 'JUPITER BARYCENTER', radius=71492), - Planet('Saturn', 'SATURN BARYCENTER', radius=60268), - Planet('Uranus', 'URANUS BARYCENTER', radius=25559), - Planet('Neptune', 'NEPTUNE BARYCENTER', radius=24764), - Planet('Pluto', 'PLUTO BARYCENTER', radius=1185)] +ASTERS = [ + Star("Sun", "SUN", radius=696342), + Satellite("Moon", "MOON", radius=1737.4), + Planet("Mercury", "MERCURY", radius=2439.7), + Planet("Venus", "VENUS", radius=6051.8), + Planet("Mars", "MARS", radius=3396.2), + Planet("Jupiter", "JUPITER BARYCENTER", radius=71492), + Planet("Saturn", "SATURN BARYCENTER", radius=60268), + Planet("Uranus", "URANUS BARYCENTER", radius=25559), + Planet("Neptune", "NEPTUNE BARYCENTER", radius=24764), + Planet("Pluto", "PLUTO BARYCENTER", radius=1185), +] class Position: @@ -214,10 +244,11 @@ class Position: def get_planet_topos(self) -> Topos: if self.aster is None: - raise TypeError('Observation planet must be set.') + raise TypeError("Observation planet must be set.") if self._topos is None: - self._topos = self.aster.get_skyfield_object() + Topos(latitude_degrees=self.latitude, - longitude_degrees=self.longitude) + self._topos = self.aster.get_skyfield_object() + Topos( + latitude_degrees=self.latitude, longitude_degrees=self.longitude + ) return self._topos diff --git a/kosmorrolib/dateutil.py b/kosmorrolib/dateutil.py index 6ff64d4..84dccd5 100644 --- a/kosmorrolib/dateutil.py +++ b/kosmorrolib/dateutil.py @@ -25,4 +25,6 @@ def translate_to_timezone(date: datetime, to_tz: int, from_tz: int = None): else: source_tz = timezone.utc - return date.replace(tzinfo=source_tz).astimezone(tz=timezone(timedelta(hours=to_tz))) + return date.replace(tzinfo=source_tz).astimezone( + tz=timezone(timedelta(hours=to_tz)) + ) diff --git a/kosmorrolib/enum.py b/kosmorrolib/enum.py index 29b185f..699103a 100644 --- a/kosmorrolib/enum.py +++ b/kosmorrolib/enum.py @@ -21,6 +21,7 @@ from enum import Enum, auto class MoonPhaseType(Enum): """An enumeration of moon phases.""" + NEW_MOON = 1 WAXING_CRESCENT = 2 FIRST_QUARTER = 3 @@ -33,6 +34,7 @@ class MoonPhaseType(Enum): class EventType(Enum): """An enumeration for the supported event types.""" + OPPOSITION = 1 CONJUNCTION = 2 OCCULTATION = 3 diff --git a/kosmorrolib/ephemerides.py b/kosmorrolib/ephemerides.py index 2ddc578..7ef3867 100644 --- a/kosmorrolib/ephemerides.py +++ b/kosmorrolib/ephemerides.py @@ -33,8 +33,12 @@ 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) +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 @@ -65,28 +69,34 @@ def _get_skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[ 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) + 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'] - sun = get_skf_objects()['sun'] + earth = get_skf_objects()["earth"] + moon = get_skf_objects()["moon"] + sun = get_skf_objects()["sun"] def moon_phase_at(time: Time): time._nutation_angles = get_iau2000b(time) current_earth = earth.at(time) - _, mlon, _ = current_earth.observe(moon).apparent().ecliptic_latlon('date') - _, slon, _ = current_earth.observe(sun).apparent().ecliptic_latlon('date') + _, mlon, _ = current_earth.observe(moon).apparent().ecliptic_latlon("date") + _, slon, _ = current_earth.observe(sun).apparent().ecliptic_latlon("date") return (((mlon.radians - slon.radians) // (tau / 8)) % 8).astype(int) moon_phase_at.rough_period = 7.0 # one lunar phase per week today = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day) - time1 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day - 10) - time2 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day + 10) + time1 = get_timescale().utc( + compute_date.year, compute_date.month, compute_date.day - 10 + ) + time2 = get_timescale().utc( + compute_date.year, compute_date.month, compute_date.day + 10 + ) try: times, phase = find_discrete(time1, time2, moon_phase_at) @@ -94,7 +104,9 @@ def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase: start = translate_to_timezone(error.start_time.utc_datetime(), timezone) end = translate_to_timezone(error.end_time.utc_datetime(), timezone) - start = datetime.date(start.year, start.month, start.day) + datetime.timedelta(days=12) + start = datetime.date(start.year, start.month, start.day) + datetime.timedelta( + days=12 + ) end = datetime.date(end.year, end.month, end.day) - datetime.timedelta(days=12) raise OutOfRangeDateError(start, end) from error @@ -102,31 +114,51 @@ def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase: return _get_skyfield_to_moon_phase(times, phase, today) -def get_ephemerides(date: datetime.date, position: Position, timezone: int = 0) -> [AsterEphemerides]: +def get_ephemerides( + date: datetime.date, position: Position, timezone: int = 0 +) -> [AsterEphemerides]: ephemerides = [] def get_angle(for_aster: Object): def fun(time: Time) -> float: - return position.get_planet_topos().at(time).observe(for_aster.get_skyfield_object()).apparent().altaz()[0]\ - .degrees + return ( + position.get_planet_topos() + .at(time) + .observe(for_aster.get_skyfield_object()) + .apparent() + .altaz()[0] + .degrees + ) + fun.rough_period = 1.0 return fun def is_risen(for_aster: Object): def fun(time: Time) -> bool: return get_angle(for_aster)(time) > RISEN_ANGLE + fun.rough_period = 0.5 return fun start_time = get_timescale().utc(date.year, date.month, date.day, -timezone) - end_time = get_timescale().utc(date.year, date.month, date.day, 23 - timezone, 59, 59) + end_time = get_timescale().utc( + date.year, date.month, date.day, 23 - timezone, 59, 59 + ) try: for aster in ASTERS: rise_times, arr = find_discrete(start_time, end_time, is_risen(aster)) try: - culmination_time, _ = find_maxima(start_time, end_time, f=get_angle(aster), epsilon=1./3600/24, num=12) - culmination_time = culmination_time[0] if len(culmination_time) > 0 else None + culmination_time, _ = find_maxima( + start_time, + end_time, + f=get_angle(aster), + epsilon=1.0 / 3600 / 24, + num=12, + ) + culmination_time = ( + culmination_time[0] if len(culmination_time) > 0 else None + ) except ValueError: culmination_time = None @@ -139,18 +171,24 @@ def get_ephemerides(date: datetime.date, position: Position, timezone: int = 0) # Convert the Time instances to Python datetime objects if rise_time is not None: - rise_time = translate_to_timezone(rise_time.utc_datetime().replace(microsecond=0), - to_tz=timezone) + rise_time = translate_to_timezone( + rise_time.utc_datetime().replace(microsecond=0), to_tz=timezone + ) if culmination_time is not None: - culmination_time = translate_to_timezone(culmination_time.utc_datetime().replace(microsecond=0), - to_tz=timezone) + culmination_time = translate_to_timezone( + culmination_time.utc_datetime().replace(microsecond=0), + to_tz=timezone, + ) if set_time is not None: - set_time = translate_to_timezone(set_time.utc_datetime().replace(microsecond=0), - to_tz=timezone) + set_time = translate_to_timezone( + set_time.utc_datetime().replace(microsecond=0), to_tz=timezone + ) - ephemerides.append(AsterEphemerides(rise_time, culmination_time, set_time, aster=aster)) + ephemerides.append( + AsterEphemerides(rise_time, culmination_time, set_time, aster=aster) + ) except EphemerisRangeError as error: start = translate_to_timezone(error.start_time.utc_datetime(), timezone) end = translate_to_timezone(error.end_time.utc_datetime(), timezone) diff --git a/kosmorrolib/events.py b/kosmorrolib/events.py index 5d4081a..1298399 100644 --- a/kosmorrolib/events.py +++ b/kosmorrolib/events.py @@ -31,16 +31,22 @@ from .core import get_timescale, get_skf_objects, flatten_list def _search_conjunction(start_time: Time, end_time: Time, timezone: int) -> [Event]: - earth = get_skf_objects()['earth'] + earth = get_skf_objects()["earth"] aster1 = None aster2 = None def is_in_conjunction(time: Time): earth_pos = earth.at(time) - _, aster1_lon, _ = earth_pos.observe(aster1.get_skyfield_object()).apparent().ecliptic_latlon() - _, aster2_lon, _ = earth_pos.observe(aster2.get_skyfield_object()).apparent().ecliptic_latlon() + _, aster1_lon, _ = ( + earth_pos.observe(aster1.get_skyfield_object()).apparent().ecliptic_latlon() + ) + _, aster2_lon, _ = ( + earth_pos.observe(aster2.get_skyfield_object()).apparent().ecliptic_latlon() + ) - return ((aster1_lon.radians - aster2_lon.radians) / pi % 2.0).astype('int8') == 0 + return ((aster1_lon.radians - aster2_lon.radians) / pi % 2.0).astype( + "int8" + ) == 0 is_in_conjunction.rough_period = 60.0 @@ -64,16 +70,30 @@ def _search_conjunction(start_time: Time, end_time: Time, timezone: int) -> [Eve aster2_pos = (aster2.get_skyfield_object() - earth).at(time) distance = aster1_pos.separation_from(aster2_pos).degrees - if distance - aster2.get_apparent_radius(time, earth) < aster1.get_apparent_radius(time, earth): - occulting_aster = [aster1, - aster2] if aster1_pos.distance().km < aster2_pos.distance().km else [aster2, - aster1] - - conjunctions.append(Event(EventType.OCCULTATION, occulting_aster, - translate_to_timezone(time.utc_datetime(), timezone))) + if distance - aster2.get_apparent_radius( + time, earth + ) < aster1.get_apparent_radius(time, earth): + occulting_aster = ( + [aster1, aster2] + if aster1_pos.distance().km < aster2_pos.distance().km + else [aster2, aster1] + ) + + conjunctions.append( + Event( + EventType.OCCULTATION, + occulting_aster, + translate_to_timezone(time.utc_datetime(), timezone), + ) + ) else: - conjunctions.append(Event(EventType.CONJUNCTION, [aster1, aster2], - translate_to_timezone(time.utc_datetime(), timezone))) + conjunctions.append( + Event( + EventType.CONJUNCTION, + [aster1, aster2], + translate_to_timezone(time.utc_datetime(), timezone), + ) + ) computed.append(aster1) @@ -81,13 +101,15 @@ def _search_conjunction(start_time: Time, end_time: Time, timezone: int) -> [Eve def _search_oppositions(start_time: Time, end_time: Time, timezone: int) -> [Event]: - earth = get_skf_objects()['earth'] - sun = get_skf_objects()['sun'] + earth = get_skf_objects()["earth"] + sun = get_skf_objects()["sun"] aster = None def is_oppositing(time: Time) -> [bool]: earth_pos = earth.at(time) - sun_pos = earth_pos.observe(sun).apparent() # Never do this without eyes protection! + sun_pos = earth_pos.observe( + sun + ).apparent() # Never do this without eyes protection! aster_pos = earth_pos.observe(get_skf_objects()[aster.skyfield_name]).apparent() _, lon1, _ = sun_pos.ecliptic_latlon() _, lon2, _ = aster_pos.ecliptic_latlon() @@ -97,19 +119,27 @@ def _search_oppositions(start_time: Time, end_time: Time, timezone: int) -> [Eve events = [] for aster in ASTERS: - if not isinstance(aster, Planet) or aster.skyfield_name in ['MERCURY', 'VENUS']: + if not isinstance(aster, Planet) or aster.skyfield_name in ["MERCURY", "VENUS"]: continue times, _ = find_discrete(start_time, end_time, is_oppositing) for time in times: - events.append(Event(EventType.OPPOSITION, [aster], translate_to_timezone(time.utc_datetime(), timezone))) + events.append( + Event( + EventType.OPPOSITION, + [aster], + translate_to_timezone(time.utc_datetime(), timezone), + ) + ) return events -def _search_maximal_elongations(start_time: Time, end_time: Time, timezone: int) -> [Event]: - earth = get_skf_objects()['earth'] - sun = get_skf_objects()['sun'] +def _search_maximal_elongations( + start_time: Time, end_time: Time, timezone: int +) -> [Event]: + earth = get_skf_objects()["earth"] + sun = get_skf_objects()["sun"] aster = None def get_elongation(time: Time): @@ -123,24 +153,30 @@ def _search_maximal_elongations(start_time: Time, end_time: Time, timezone: int) events = [] for aster in ASTERS: - if aster.skyfield_name not in ['MERCURY', 'VENUS']: + if aster.skyfield_name not in ["MERCURY", "VENUS"]: continue - times, elongations = find_maxima(start_time, end_time, f=get_elongation, epsilon=1./24/3600, num=12) + times, elongations = find_maxima( + start_time, end_time, f=get_elongation, epsilon=1.0 / 24 / 3600, num=12 + ) for i, time in enumerate(times): elongation = elongations[i] - events.append(Event(EventType.MAXIMAL_ELONGATION, - [aster], - translate_to_timezone(time.utc_datetime(), timezone), - details='{:.3n}°'.format(elongation))) + events.append( + Event( + EventType.MAXIMAL_ELONGATION, + [aster], + translate_to_timezone(time.utc_datetime(), timezone), + details="{:.3n}°".format(elongation), + ) + ) return events def _get_moon_distance(): - earth = get_skf_objects()['earth'] - moon = get_skf_objects()['moon'] + earth = get_skf_objects()["earth"] + moon = get_skf_objects()["moon"] def get_distance(time: Time): earth_pos = earth.at(time) @@ -157,10 +193,18 @@ def _search_moon_apogee(start_time: Time, end_time: Time, timezone: int) -> [Eve moon = ASTERS[1] events = [] - times, _ = find_maxima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60) + times, _ = find_maxima( + start_time, end_time, f=_get_moon_distance(), epsilon=1.0 / 24 / 60 + ) for time in times: - events.append(Event(EventType.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,10 +213,18 @@ def _search_moon_perigee(start_time: Time, end_time: Time, timezone: int) -> [Ev moon = ASTERS[1] events = [] - times, _ = find_minima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60) + times, _ = find_minima( + start_time, end_time, f=_get_moon_distance(), epsilon=1.0 / 24 / 60 + ) for time in times: - events.append(Event(EventType.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 @@ -206,11 +258,13 @@ def get_events(date: date_type, timezone: int = 0) -> [Event]: try: found_events = [] - for fun in [_search_oppositions, - _search_conjunction, - _search_maximal_elongations, - _search_moon_apogee, - _search_moon_perigee]: + for fun in [ + _search_oppositions, + _search_conjunction, + _search_maximal_elongations, + _search_moon_apogee, + _search_moon_perigee, + ]: found_events.append(fun(start_time, end_time, timezone)) return sorted(flatten_list(found_events), key=lambda event: event.start_time) diff --git a/kosmorrolib/exceptions.py b/kosmorrolib/exceptions.py index e6effdd..6a69046 100644 --- a/kosmorrolib/exceptions.py +++ b/kosmorrolib/exceptions.py @@ -30,8 +30,10 @@ class OutOfRangeDateError(RuntimeError): super().__init__() self.min_date = min_date self.max_date = max_date - self.msg = 'The date must be between %s and %s' % (min_date.strftime('%Y-%m-%d'), - max_date.strftime('%Y-%m-%d')) + self.msg = "The date must be between %s and %s" % ( + min_date.strftime("%Y-%m-%d"), + max_date.strftime("%Y-%m-%d"), + ) class CompileError(RuntimeError): diff --git a/tests/core.py b/tests/core.py index 34cf8e5..76802ba 100644 --- a/tests/core.py +++ b/tests/core.py @@ -9,31 +9,42 @@ from dateutil.relativedelta import relativedelta class CoreTestCase(unittest.TestCase): def test_flatten_list(self): - self.assertEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], core.flatten_list([0, 1, 2, [3, 4, [5, 6], 7], 8, [9]])) + self.assertEqual( + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + core.flatten_list([0, 1, 2, [3, 4, [5, 6], 7], 8, [9]]), + ) def test_get_env(self): self.assertEqual(0, len(core.get_env())) - os.environ['SOME_RANDOM_VAR'] = 'an awesome value' + os.environ["SOME_RANDOM_VAR"] = "an awesome value" self.assertEqual(0, len(core.get_env())) - os.environ['KOSMORRO_GREAT_VARIABLE'] = 'value' + os.environ["KOSMORRO_GREAT_VARIABLE"] = "value" env = core.get_env() self.assertEqual(1, len(env)) - self.assertEqual('value', env.great_variable) + self.assertEqual("value", env.great_variable) - os.environ['KOSMORRO_ANOTHER_VARIABLE'] = 'another value' + os.environ["KOSMORRO_ANOTHER_VARIABLE"] = "another value" env = core.get_env() self.assertEqual(2, len(env)) - self.assertEqual('value', env.great_variable) - self.assertEqual('another value', env.another_variable) + self.assertEqual("value", env.great_variable) + self.assertEqual("another value", env.another_variable) - self.assertEqual("{'great_variable': 'value', 'another_variable': 'another value'}", str(env)) + self.assertEqual( + "{'great_variable': 'value', 'another_variable': 'another value'}", str(env) + ) def test_date_arg_parsing(self): - self.assertEqual(core.get_date("+1y 2m3d"), date.today() + relativedelta(years=1, months=2, days=3)) - self.assertEqual(core.get_date("-1y2d"), date.today() - relativedelta(years=1, days=2)) + self.assertEqual( + core.get_date("+1y 2m3d"), + date.today() + relativedelta(years=1, months=2, days=3), + ) + self.assertEqual( + core.get_date("-1y2d"), date.today() - relativedelta(years=1, days=2) + ) self.assertEqual(core.get_date("1111-11-13"), date(1111, 11, 13)) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/data.py b/tests/data.py index d1e2103..0b68fb0 100644 --- a/tests/data.py +++ b/tests/data.py @@ -5,13 +5,15 @@ from kosmorrolib import data, core class DataTestCase(unittest.TestCase): def test_object_radius_must_be_set_to_get_apparent_radius(self): - o = data.Planet('Saturn', 'SATURN') + o = data.Planet("Saturn", "SATURN") with self.assertRaises(ValueError) as context: - o.get_apparent_radius(core.get_timescale().now(), core.get_skf_objects()['earth']) + o.get_apparent_radius( + core.get_timescale().now(), core.get_skf_objects()["earth"] + ) - self.assertEqual(('Missing radius for Saturn object',), context.exception.args) + self.assertEqual(("Missing radius for Saturn object",), context.exception.args) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/dateutil.py b/tests/dateutil.py index 984add3..82cf29d 100644 --- a/tests/dateutil.py +++ b/tests/dateutil.py @@ -12,13 +12,17 @@ class DateUtilTestCase(unittest.TestCase): date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 0), to_tz=2) self.assertEqual(2, date.hour) - date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 8), to_tz=2, from_tz=6) + date = dateutil.translate_to_timezone( + datetime(2020, 6, 9, 8), to_tz=2, from_tz=6 + ) self.assertEqual(4, date.hour) - date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 1), to_tz=0, from_tz=2) + date = dateutil.translate_to_timezone( + datetime(2020, 6, 9, 1), to_tz=0, from_tz=2 + ) self.assertEqual(8, date.day) self.assertEqual(23, date.hour) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/ephemerides.py b/tests/ephemerides.py index 713a115..c3d876d 100644 --- a/tests/ephemerides.py +++ b/tests/ephemerides.py @@ -12,16 +12,17 @@ from kosmorrolib.exceptions import OutOfRangeDateError class EphemeridesTestCase(unittest.TestCase): def test_get_ephemerides_for_aster_returns_correct_hours(self): position = Position(0, 0, EARTH) - eph = ephemerides.get_ephemerides(date=date(2019, 11, 18), - position=position) + eph = ephemerides.get_ephemerides(date=date(2019, 11, 18), position=position) @expect_assertions(self.assertRegex, num=3) def do_assertions(assert_regex): for ephemeris in eph: - if ephemeris.object.skyfield_name == 'SUN': - assert_regex(ephemeris.rise_time.isoformat(), '^2019-11-18T05:41:') - assert_regex(ephemeris.culmination_time.isoformat(), '^2019-11-18T11:45:') - assert_regex(ephemeris.set_time.isoformat(), '^2019-11-18T17:48:') + if ephemeris.object.skyfield_name == "SUN": + assert_regex(ephemeris.rise_time.isoformat(), "^2019-11-18T05:41:") + assert_regex( + ephemeris.culmination_time.isoformat(), "^2019-11-18T11:45:" + ) + assert_regex(ephemeris.set_time.isoformat(), "^2019-11-18T17:48:") break do_assertions() @@ -34,61 +35,61 @@ class EphemeridesTestCase(unittest.TestCase): phase = ephemerides.get_moon_phase(date(2019, 11, 25)) self.assertEqual(MoonPhaseType.WANING_CRESCENT, phase.phase_type) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), "^2019-11-26T") phase = ephemerides.get_moon_phase(date(2019, 11, 26)) self.assertEqual(MoonPhaseType.NEW_MOON, phase.phase_type) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), "^2019-12-04T") phase = ephemerides.get_moon_phase(date(2019, 11, 27)) self.assertEqual(MoonPhaseType.WAXING_CRESCENT, phase.phase_type) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') + 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(MoonPhaseType.WAXING_CRESCENT, phase.phase_type) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-04T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), "^2019-11-04T") phase = ephemerides.get_moon_phase(date(2019, 11, 4)) self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.phase_type) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), "^2019-11-12T") phase = ephemerides.get_moon_phase(date(2019, 11, 5)) self.assertEqual(MoonPhaseType.WAXING_GIBBOUS, phase.phase_type) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') + 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(MoonPhaseType.WAXING_GIBBOUS, phase.phase_type) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), "^2019-11-12T") phase = ephemerides.get_moon_phase(date(2019, 11, 12)) self.assertEqual(MoonPhaseType.FULL_MOON, phase.phase_type) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), "^2019-11-19T") phase = ephemerides.get_moon_phase(date(2019, 11, 13)) self.assertEqual(MoonPhaseType.WANING_GIBBOUS, phase.phase_type) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') + 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(MoonPhaseType.WANING_GIBBOUS, phase.phase_type) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), "^2019-11-19T") phase = ephemerides.get_moon_phase(date(2019, 11, 19)) self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.phase_type) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), "^2019-11-26T") phase = ephemerides.get_moon_phase(date(2019, 11, 20)) self.assertEqual(MoonPhaseType.WANING_CRESCENT, phase.phase_type) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), "^2019-11-26T") def test_moon_phase_prediction(self): phase = MoonPhase(MoonPhaseType.NEW_MOON, None, None) @@ -120,5 +121,5 @@ class EphemeridesTestCase(unittest.TestCase): ephemerides.get_moon_phase(date(1789, 5, 5)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/events.py b/tests/events.py index dd92611..da6485c 100644 --- a/tests/events.py +++ b/tests/events.py @@ -10,43 +10,111 @@ from kosmorrolib.exceptions import OutOfRangeDateError EXPECTED_EVENTS = [ (date(2020, 2, 7), []), - - (date(2020, 10, 13), [Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2020, 10, 13, 23, 25))]), - - (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(EventType.OPPOSITION, [ASTERS[4]], datetime(2025, 1, 16, 2, 38))]), - - (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(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(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(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(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(EventType.OCCULTATION, [ASTERS[1], ASTERS[5]], datetime(2005, 6, 16, 6, 31))]), - - (date(2020, 4, 7), [Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 4, 7, 18, 14))]), - - (date(2020, 1, 29), [Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 29, 21, 32))]) + ( + date(2020, 10, 13), + [Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2020, 10, 13, 23, 25))], + ), + ( + 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(EventType.OPPOSITION, [ASTERS[4]], datetime(2025, 1, 16, 2, 38))], + ), + ( + 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(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( + 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( + 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( + 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( + EventType.OCCULTATION, + [ASTERS[1], ASTERS[5]], + datetime(2005, 6, 16, 6, 31), + ) + ], + ), + ( + date(2020, 4, 7), + [Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 4, 7, 18, 14))], + ), + ( + date(2020, 1, 29), + [Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 29, 21, 32))], + ), ] @@ -57,20 +125,23 @@ class EventTestCase(unittest.TestCase): @parameterized.expand(EXPECTED_EVENTS) def test_search_events(self, d: date, expected_events: [Event]): actual_events = events.get_events(d) - self.assertEqual(len(expected_events), len(actual_events), - 'Expected %d elements, got %d for date %s.\n%s' % (len(expected_events), - len(actual_events), - d.isoformat(), - actual_events)) + self.assertEqual( + len(expected_events), + len(actual_events), + "Expected %d elements, got %d for date %s.\n%s" + % (len(expected_events), len(actual_events), d.isoformat(), actual_events), + ) for i, expected_event in enumerate(expected_events): actual_event = actual_events[i] # Remove unnecessary precision (seconds and microseconds) - actual_event.start_time = datetime(actual_event.start_time.year, - actual_event.start_time.month, - actual_event.start_time.day, - actual_event.start_time.hour, - actual_event.start_time.minute) + actual_event.start_time = datetime( + actual_event.start_time.year, + actual_event.start_time.month, + actual_event.start_time.day, + actual_event.start_time.hour, + actual_event.start_time.minute, + ) self.assertEqual(expected_event.__dict__, actual_event.__dict__) @@ -79,5 +150,5 @@ class EventTestCase(unittest.TestCase): events.get_events(date(1789, 5, 5)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/testutils.py b/tests/testutils.py index 964a3e1..475aeaa 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -39,9 +39,10 @@ def expect_assertions(assert_fun, num=1): count = assert_fun_mock.call_count if count != num: - raise AssertionError('Expected %d call(s) to function %s but called %d time(s).' % (num, - assert_fun.__name__, - count)) + raise AssertionError( + "Expected %d call(s) to function %s but called %d time(s)." + % (num, assert_fun.__name__, count) + ) return sniff_function