From fabf95b9ff3f4f2ef022da8351e10a1fb7d172d0 Mon Sep 17 00:00:00 2001 From: Thomas Vandal Date: Sat, 14 Sep 2024 07:12:07 -0300 Subject: [PATCH 01/11] Add type hints to scheduling and target modules --- astroplan/scheduling.py | 99 ++++++++++++++++++++++------------------- astroplan/target.py | 23 +++++----- 2 files changed, 67 insertions(+), 55 deletions(-) diff --git a/astroplan/scheduling.py b/astroplan/scheduling.py index 32f49273..af46e913 100644 --- a/astroplan/scheduling.py +++ b/astroplan/scheduling.py @@ -3,21 +3,22 @@ Tools for scheduling observations. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) +from __future__ import absolute_import, division, print_function, unicode_literals import copy from abc import ABCMeta, abstractmethod +from typing import Optional, Sequence, Type, Union import numpy as np - from astropy import units as u -from astropy.time import Time from astropy.table import Table +from astropy.time import Time, TimeDelta +from astropy.units import Quantity -from .utils import time_grid_from_range, stride_array -from .constraints import AltitudeConstraint -from .target import get_skycoord +from .constraints import AltitudeConstraint, Constraint +from .observer import Observer +from .target import FixedTarget, get_skycoord +from .utils import stride_array, time_grid_from_range __all__ = ['ObservingBlock', 'TransitionBlock', 'Schedule', 'Slot', 'Scheduler', 'SequentialScheduler', 'PriorityScheduler', @@ -30,7 +31,9 @@ class ObservingBlock(object): constraints on observations. """ @u.quantity_input(duration=u.second) - def __init__(self, target, duration, priority, configuration={}, constraints=None, name=None): + def __init__(self, target: FixedTarget, duration: Quantity["time"], priority: Union[int, float], # noqa: F821 + configuration: dict = {}, constraints: Optional[list[Constraint]] =None, + name: Optional[Union[str, int]] = None): """ Parameters ---------- @@ -65,7 +68,7 @@ def __init__(self, target, duration, priority, configuration={}, constraints=Non self.start_time = self.end_time = None self.observer = None - def __repr__(self): + def __repr__(self) -> str: orig_repr = object.__repr__(self) if self.start_time is None or self.end_time is None: return orig_repr.replace('object at', @@ -77,7 +80,7 @@ def __repr__(self): return orig_repr.replace('object at', s) @property - def constraints_scores(self): + def constraints_scores(self) -> Optional[dict[Constraint, np.ndarray]]: if not (self.start_time and self.duration): return None # TODO: setup a way of caching or defining it as an attribute during scheduling @@ -87,9 +90,10 @@ def constraints_scores(self): for constraint in self.constraints} @classmethod - def from_exposures(cls, target, priority, time_per_exposure, - number_exposures, readout_time=0 * u.second, - configuration={}, constraints=None): + def from_exposures(cls, target: FixedTarget, priority: Union[int, float], + time_per_exposure: Quantity["time"], number_exposures: int, # noqa: F821 + readout_time: Quantity["time"] = 0 * u.second, # noqa: F821 + configuration: dict = {}, constraints: Optional[list[Constraint]] = None) -> "ObservingBlock": duration = number_exposures * (time_per_exposure + readout_time) ob = cls(target, duration, priority, configuration, constraints) ob.time_per_exposure = time_per_exposure @@ -104,7 +108,8 @@ class Scorer(object): observing blocks """ - def __init__(self, blocks, observer, schedule, global_constraints=[]): + def __init__(self, blocks: list[ObservingBlock], observer: Observer, schedule: "Schedule", + global_constraints: list[Constraint] = []): """ Parameters ---------- @@ -123,7 +128,7 @@ def __init__(self, blocks, observer, schedule, global_constraints=[]): self.global_constraints = global_constraints self.targets = get_skycoord([block.target for block in self.blocks]) - def create_score_array(self, time_resolution=1*u.minute): + def create_score_array(self, time_resolution: Quantity["time"] = 1*u.minute) -> np.ndarray: # noqa: F821 """ this makes a score array over the entire schedule for all of the blocks and each `~astroplan.Constraint` in the .constraints of @@ -156,7 +161,7 @@ def create_score_array(self, time_resolution=1*u.minute): return score_array @classmethod - def from_start_end(cls, blocks, observer, start_time, end_time, + def from_start_end(cls, blocks: ObservingBlock, observer: Observer, start_time: Time, end_time: Time, global_constraints=[]): """ for if you don't have a schedule/ aren't inside a scheduler @@ -172,7 +177,7 @@ class TransitionBlock(object): telescope is slewing, instrument is reconfiguring, etc. """ - def __init__(self, components, start_time=None): + def __init__(self, components: dict[str, Quantity["time"]], start_time: Optional[Time] =None): # noqa: F821 """ Parameters ---------- @@ -188,7 +193,7 @@ def __init__(self, components, start_time=None): self.start_time = start_time self.components = components - def __repr__(self): + def __repr__(self) -> str: orig_repr = object.__repr__(self) comp_info = ', '.join(['{0}: {1}'.format(c, t) for c, t in self.components.items()]) @@ -199,15 +204,15 @@ def __repr__(self): return orig_repr.replace('object at', s) @property - def end_time(self): + def end_time(self) -> Time: return self.start_time + self.duration @property - def components(self): + def components(self) -> dict[str, Quantity["time"]]: # noqa: F821 return self._components @components.setter - def components(self, val): + def components(self, val: dict[str, Quantity["time"]]) -> None: # noqa: F821 duration = 0*u.second for t in val.values(): duration += t @@ -217,7 +222,7 @@ def components(self, val): @classmethod @u.quantity_input(duration=u.second) - def from_duration(cls, duration): + def from_duration(cls, duration: Quantity["time"]) -> "TransitionBlock": # noqa: F821 # for testing how to put transitions between observations during # scheduling without considering the complexities of duration tb = TransitionBlock({'duration': duration}) @@ -232,7 +237,8 @@ class Schedule(object): # as currently written, there should be no consecutive unoccupied slots # this should change to allow for more flexibility (e.g. dark slots, grey slots) - def __init__(self, start_time, end_time, constraints=None): + # TODO: Remove unused constraints arg? + def __init__(self, start_time: Time, end_time: Time, constraints: Optional[Sequence[Constraint]] = None): """ Parameters ---------- @@ -250,24 +256,24 @@ def __init__(self, start_time, end_time, constraints=None): self.slots = [Slot(start_time, end_time)] self.observer = None - def __repr__(self): + def __repr__(self) -> str: return ('Schedule containing ' + str(len(self.observing_blocks)) + ' observing blocks between ' + str(self.slots[0].start.iso) + ' and ' + str(self.slots[-1].end.iso)) @property - def observing_blocks(self): + def observing_blocks(self) -> list[ObservingBlock]: return [slot.block for slot in self.slots if isinstance(slot.block, ObservingBlock)] @property - def scheduled_blocks(self): + def scheduled_blocks(self) -> list[ObservingBlock]: return [slot.block for slot in self.slots if slot.block] @property - def open_slots(self): + def open_slots(self) -> list["Slot"]: return [slot for slot in self.slots if not slot.occupied] - def to_table(self, show_transitions=True, show_unused=False): + def to_table(self, show_transitions: bool = True, show_unused: bool = False) -> Table: # TODO: allow different coordinate types target_names = [] start_times = [] @@ -308,7 +314,7 @@ def to_table(self, show_transitions=True, show_unused=False): names=('target', 'start time (UTC)', 'end time (UTC)', 'duration (minutes)', 'ra', 'dec', 'configuration')) - def new_slots(self, slot_index, start_time, end_time): + def new_slots(self, slot_index: int, start_time: Time, end_time: Time) -> list["Slot"]: """ Create new slots by splitting a current slot. @@ -332,7 +338,7 @@ def new_slots(self, slot_index, start_time, end_time): new_slots = self.slots[slot_index].split_slot(start_time, end_time) return new_slots - def insert_slot(self, start_time, block): + def insert_slot(self, start_time: Time, block: ObservingBlock) -> list["Slot"]: """ Insert a slot into schedule and associate a block to the new slot. @@ -391,7 +397,7 @@ def insert_slot(self, start_time, block): self.slots = earlier_slots + new_slots + later_slots return earlier_slots + new_slots + later_slots - def change_slot_block(self, slot_index, new_block=None): + def change_slot_block(self, slot_index: int, new_block: Optional[TransitionBlock] = None) -> int: """ Change the block associated with a slot. @@ -428,7 +434,7 @@ class Slot(object): A time slot consisting of a start and end time """ - def __init__(self, start_time, end_time): + def __init__(self, start_time: Time, end_time: Time): """ Parameters ---------- @@ -447,7 +453,7 @@ def __init__(self, start_time, end_time): def duration(self): return self.end - self.start - def split_slot(self, early_time, later_time): + def split_slot(self, early_time: Time, later_time: Time) -> list["Slot"]: """ Split this slot and insert a new one. @@ -489,8 +495,9 @@ class Scheduler(object): __metaclass__ = ABCMeta @u.quantity_input(gap_time=u.second, time_resolution=u.second) - def __init__(self, constraints, observer, transitioner=None, - gap_time=5*u.min, time_resolution=20*u.second): + def __init__(self, constraints: Sequence[Constraint], observer: Observer, + transitioner: Optional["Transitioner"] = None, gap_time: Quantity["time"] = 5*u.min, # noqa: F821 + time_resolution: Quantity["time"] = 20*u.second): # noqa: F821 """ Parameters ---------- @@ -519,7 +526,7 @@ def __init__(self, constraints, observer, transitioner=None, self.gap_time = gap_time self.time_resolution = time_resolution - def __call__(self, blocks, schedule): + def __call__(self, blocks: list[ObservingBlock], schedule: Schedule) -> Schedule: """ Schedule a set of `~astroplan.scheduling.ObservingBlock` objects. @@ -550,7 +557,7 @@ def __call__(self, blocks, schedule): return schedule @abstractmethod - def _make_schedule(self, blocks): + def _make_schedule(self, blocks: list[ObservingBlock]): """ Does the actual business of scheduling. The ``blocks`` passed in should have their ``start_time` and `end_time`` modified to reflect the @@ -577,7 +584,7 @@ def _make_schedule(self, blocks): @classmethod @u.quantity_input(duration=u.second) - def from_timespan(cls, center_time, duration, **kwargs): + def from_timespan(cls, center_time: Time, duration: Union[Quantity["time"], TimeDelta], **kwargs) -> "Scheduler": # noqa: F821 """ Create a new instance of this class given a center time and duration. @@ -604,7 +611,7 @@ class SequentialScheduler(Scheduler): def __init__(self, *args, **kwargs): super(SequentialScheduler, self).__init__(*args, **kwargs) - def _make_schedule(self, blocks): + def _make_schedule(self, blocks: list[ObservingBlock]) -> Schedule: pre_filled = np.array([[block.start_time, block.end_time] for block in self.schedule.scheduled_blocks]) if len(pre_filled) == 0: @@ -703,7 +710,7 @@ def __init__(self, *args, **kwargs): """ super(PriorityScheduler, self).__init__(*args, **kwargs) - def _get_filled_indices(self, times): + def _get_filled_indices(self, times: Time) -> np.ndarray[bool]: is_open_time = np.ones(len(times), bool) # close times that are already filled pre_filled = np.array([[block.start_time, block.end_time] for @@ -716,7 +723,7 @@ def _get_filled_indices(self, times): is_open_time[min(filled[0]) - 1] = False return is_open_time - def _make_schedule(self, blocks): + def _make_schedule(self, blocks: list[ObservingBlock]) -> Schedule: # Combine individual constraints with global constraints, and # retrieve priorities from each block to define scheduling order @@ -808,7 +815,7 @@ def _make_schedule(self, blocks): return self.schedule - def attempt_insert_block(self, b, new_start_time, start_time_idx): + def attempt_insert_block(self, b: ObservingBlock, new_start_time: Time, start_time_idx: int) -> bool: # set duration to be exact multiple of time resolution duration_indices = int(np.floor( float(b.duration / self.time_resolution))) @@ -955,7 +962,8 @@ class Transitioner(object): """ u.quantity_input(slew_rate=u.deg/u.second) - def __init__(self, slew_rate=None, instrument_reconfig_times=None): + def __init__(self, slew_rate: Optional[Quantity["angular frequency"]] = None, # noqa: F722 + instrument_reconfig_times: Optional[dict[str, dict[tuple[str, str], Quantity["time"]]]] = None): # noqa: F722 """ Parameters ---------- @@ -971,7 +979,7 @@ def __init__(self, slew_rate=None, instrument_reconfig_times=None): self.slew_rate = slew_rate self.instrument_reconfig_times = instrument_reconfig_times - def __call__(self, oldblock, newblock, start_time, observer): + def __call__(self, oldblock: ObservingBlock, newblock: ObservingBlock, start_time: Time, observer: Observer) -> Optional[TransitionBlock]: """ Determines the amount of time needed to transition from one observing block to another. This uses the parameters defined in @@ -1015,7 +1023,8 @@ def __call__(self, oldblock, newblock, start_time, observer): else: return None - def compute_instrument_transitions(self, oldblock, newblock): + def compute_instrument_transitions(self, oldblock: ObservingBlock, + newblock: ObservingBlock) -> dict[str, Quantity["time"]]: # noqa: F821 components = {} for conf_name, old_conf in oldblock.configuration.items(): if conf_name in newblock.configuration: diff --git a/astroplan/target.py b/astroplan/target.py index 8a05dfca..66cdd2ad 100644 --- a/astroplan/target.py +++ b/astroplan/target.py @@ -1,12 +1,14 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst from __future__ import (absolute_import, division, print_function, unicode_literals) +from typing import Any, Optional # Standard library from abc import ABCMeta # Third-party import astropy.units as u +from astropy.units import Quantity from astropy.coordinates import SkyCoord, ICRS, UnitSphericalRepresentation __all__ = ["Target", "FixedTarget", "NonFixedTarget"] @@ -29,7 +31,8 @@ class Target(object): """ __metaclass__ = ABCMeta - def __init__(self, name=None, ra=None, dec=None, marker=None): + def __init__(self, name: Optional[str] = None, ra: Optional[Quantity["angle"]] = None, # noqa: F821 + dec: Optional[Quantity["angle"]] = None, marker: Optional[str] = None): # noqa: F821 """ Defines a single observation target. @@ -37,9 +40,9 @@ def __init__(self, name=None, ra=None, dec=None, marker=None): ---------- name : str, optional - ra : WHAT TYPE IS ra ? + ra : Right ascension, optional - dec : WHAT TYPE IS dec ? + dec : Declination, optional marker : str, optional User-defined markers to differentiate between different types @@ -48,7 +51,7 @@ def __init__(self, name=None, ra=None, dec=None, marker=None): raise NotImplementedError() @property - def ra(self): + def ra(self) -> Quantity["angle"]: # noqa: F821 """ Right ascension. """ @@ -57,7 +60,7 @@ def ra(self): raise NotImplementedError() @property - def dec(self): + def dec(self) -> Quantity["angle"]: # noqa: F821 """ Declination. """ @@ -88,7 +91,7 @@ class FixedTarget(Target): >>> sirius = FixedTarget.from_name("Sirius") """ - def __init__(self, coord, name=None, **kwargs): + def __init__(self, coord: SkyCoord, name: Optional[str] = None, **kwargs): """ Parameters ---------- @@ -107,7 +110,7 @@ def __init__(self, coord, name=None, **kwargs): self.coord = coord @classmethod - def from_name(cls, query_name, name=None, **kwargs): + def from_name(cls, query_name: str, name: Optional[str] = None, **kwargs) -> "FixedTarget": """ Initialize a `FixedTarget` by querying for a name from the CDS name resolver, using the machinery in @@ -138,7 +141,7 @@ def from_name(cls, query_name, name=None, **kwargs): name = query_name return cls(SkyCoord.from_name(query_name), name=name, **kwargs) - def __repr__(self): + def __repr__(self) -> str: """ String representation of `~astroplan.FixedTarget`. @@ -158,7 +161,7 @@ def __repr__(self): return '<{} "{}" at {}>'.format(class_name, self.name, fmt_coord) @classmethod - def _from_name_mock(cls, query_name, name=None): + def _from_name_mock(cls, query_name: str, name: Optional[str] = None) -> "FixedTarget": """ Mock method to replace `FixedTarget.from_name` in tests without internet connection. @@ -190,7 +193,7 @@ class NonFixedTarget(Target): """ -def get_skycoord(targets): +def get_skycoord(targets: list[FixedTarget]) -> SkyCoord: """ Return an `~astropy.coordinates.SkyCoord` object. From c1e1449528cdb172c5f55493aa245e09460a2ccd Mon Sep 17 00:00:00 2001 From: Thomas Vandal Date: Sat, 14 Sep 2024 22:45:04 -0300 Subject: [PATCH 02/11] Type hints in constraints --- astroplan/constraints.py | 116 +++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index f9857fe9..ac64e110 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -9,23 +9,28 @@ # Standard library from abc import ABCMeta, abstractmethod +from typing import Optional, Sequence, Union import datetime import time import warnings # Third-party from astropy.time import Time +from astropy.units import Quantity import astropy.units as u -from astropy.coordinates import get_body, get_sun, Galactic, SkyCoord +from astropy.coordinates import get_body, get_sun, Galactic, SkyCoord, AltAz from astropy import table import numpy as np from numpy.lib.stride_tricks import as_strided +from numpy.typing import ArrayLike # Package from .moon import moon_illumination +from .periodic import EclipsingSystem, PeriodicEvent from .utils import time_grid_from_range -from .target import get_skycoord +from .observer import Observer +from .target import get_skycoord, FixedTarget from .exceptions import MissingConstraintWarning __all__ = ["AltitudeConstraint", "AirmassConstraint", "AtNightConstraint", @@ -44,7 +49,7 @@ ) -def _make_cache_key(times, targets): +def _make_cache_key(times: Time, targets: Union[SkyCoord, list[FixedTarget]]) -> tuple: """ Make a unique key to reference this combination of ``times`` and ``targets``. @@ -85,7 +90,8 @@ def _make_cache_key(times, targets): return timekey + targkey -def _get_altaz(times, observer, targets, force_zero_pressure=False): +def _get_altaz(times: Time, observer: Observer, targets: Union[list[FixedTarget], SkyCoord], + force_zero_pressure: bool = False) -> dict[str, Union[Time, AltAz]]: """ Calculate alt/az for ``target`` at times linearly spaced between the two times in ``time_range`` with grid spacing ``time_resolution`` @@ -133,7 +139,7 @@ def _get_altaz(times, observer, targets, force_zero_pressure=False): return observer._altaz_cache[aakey] -def _get_moon_data(times, observer, force_zero_pressure=False): +def _get_moon_data(times: Time, observer: Observer, force_zero_pressure: bool = False) -> dict[str, Union[Time, AltAz, np.ndarray]]: """ Calculate moon altitude az and illumination for an array of times for ``observer``. @@ -181,7 +187,7 @@ def _get_moon_data(times, observer, force_zero_pressure=False): return observer._moon_cache[aakey] -def _get_meridian_transit_times(times, observer, targets): +def _get_meridian_transit_times(times: Time, observer: Observer, targets: Union[list[FixedTarget], SkyCoord]) -> dict[str, Time]: """ Calculate next meridian transit for an array of times for ``targets`` and ``observer``. @@ -223,9 +229,9 @@ class Constraint(object): """ __metaclass__ = ABCMeta - def __call__(self, observer, targets, times=None, - time_range=None, time_grid_resolution=0.5*u.hour, - grid_times_targets=False): + def __call__(self, observer: Observer, targets: Sequence[FixedTarget], times: Optional[Time] = None, + time_range: Optional[Time] = None, time_grid_resolution: Quantity["time"] = 0.5*u.hour, + grid_times_targets: bool = False) -> np.ndarray[Union[float, bool]]: """ Compute the constraint for this class @@ -289,7 +295,7 @@ def __call__(self, observer, targets, times=None, return result @abstractmethod - def compute_constraint(self, times, observer, targets): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[Union[float, bool]]: """ Actually do the real work of computing the constraint. Subclasses override this. @@ -334,7 +340,7 @@ class AltitudeConstraint(Constraint): float on [0, 1], where 0 is the min altitude and 1 is the max. """ - def __init__(self, min=None, max=None, boolean_constraint=True): + def __init__(self, min: Optional[Quantity["angle"]] = None, max: Optional[Quantity["angle"]] = None, boolean_constraint: bool = True): # noqa: F821 if min is None: self.min = -90*u.deg else: @@ -346,7 +352,7 @@ def __init__(self, min=None, max=None, boolean_constraint=True): self.boolean_constraint = boolean_constraint - def compute_constraint(self, times, observer, targets): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[Union[float, bool]]: cached_altaz = _get_altaz(times, observer, targets) alt = cached_altaz['altaz'].alt if self.boolean_constraint: @@ -386,12 +392,12 @@ class AirmassConstraint(AltitudeConstraint): AirmassConstraint(2) """ - def __init__(self, max=None, min=1, boolean_constraint=True): + def __init__(self, max: Optional[float] = None, min: Optional[float] = 1.0, boolean_constraint: bool = True): self.min = min self.max = max self.boolean_constraint = boolean_constraint - def compute_constraint(self, times, observer, targets): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[Union[float, bool]]: cached_altaz = _get_altaz(times, observer, targets) secz = cached_altaz['altaz'].secz.value if self.boolean_constraint: @@ -421,7 +427,7 @@ class AtNightConstraint(Constraint): Constrain the Sun to be below ``horizon``. """ @u.quantity_input(horizon=u.deg) - def __init__(self, max_solar_altitude=0*u.deg, force_pressure_zero=True): + def __init__(self, max_solar_altitude: Quantity["angle"] = 0*u.deg, force_pressure_zero: bool = True): # noqa: F821 """ Parameters ---------- @@ -438,27 +444,27 @@ def __init__(self, max_solar_altitude=0*u.deg, force_pressure_zero=True): self.force_pressure_zero = force_pressure_zero @classmethod - def twilight_civil(cls, **kwargs): + def twilight_civil(cls, **kwargs) -> "AtNightConstraint": """ Consider nighttime as time between civil twilights (-6 degrees). """ return cls(max_solar_altitude=-6*u.deg, **kwargs) @classmethod - def twilight_nautical(cls, **kwargs): + def twilight_nautical(cls, **kwargs) -> "AtNightConstraint": """ Consider nighttime as time between nautical twilights (-12 degrees). """ return cls(max_solar_altitude=-12*u.deg, **kwargs) @classmethod - def twilight_astronomical(cls, **kwargs): + def twilight_astronomical(cls, **kwargs) -> "AtNightConstraint": """ Consider nighttime as time between astronomical twilights (-18 degrees). """ return cls(max_solar_altitude=-18*u.deg, **kwargs) - def _get_solar_altitudes(self, times, observer, targets): + def _get_solar_altitudes(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> Quantity["angle"]: # noqa: F821 if not hasattr(observer, '_altaz_cache'): observer._altaz_cache = {} @@ -484,7 +490,7 @@ def _get_solar_altitudes(self, times, observer, targets): return altitude - def compute_constraint(self, times, observer, targets): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: solar_altitude = self._get_solar_altitudes(times, observer, targets) mask = solar_altitude <= self.max_solar_altitude return mask @@ -495,7 +501,7 @@ class GalacticLatitudeConstraint(Constraint): Constrain the distance between the Galactic plane and some targets. """ - def __init__(self, min=None, max=None): + def __init__(self, min: Optional[Quantity["angle"]] = None, max: Optional[Quantity["angle"]] = None): # noqa: F821 """ Parameters ---------- @@ -509,7 +515,7 @@ def __init__(self, min=None, max=None): self.min = min self.max = max - def compute_constraint(self, times, observer, targets): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: separation = abs(targets.transform_to(Galactic).b) if self.min is None and self.max is not None: @@ -529,7 +535,7 @@ class SunSeparationConstraint(Constraint): Constrain the distance between the Sun and some targets. """ - def __init__(self, min=None, max=None): + def __init__(self, min: Optional[Quantity["angle"]] = None, max: Optional[Quantity["angle"]] = None): # noqa: F821 """ Parameters ---------- @@ -543,7 +549,7 @@ def __init__(self, min=None, max=None): self.min = min self.max = max - def compute_constraint(self, times, observer, targets): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: # use get_body rather than get sun here, since # it returns the Sun's coordinates in an observer # centred frame, so the separation is as-seen @@ -571,7 +577,7 @@ class MoonSeparationConstraint(Constraint): Constrain the distance between the Earth's moon and some targets. """ - def __init__(self, min=None, max=None, ephemeris=None): + def __init__(self, min: Optional[Quantity["angle"]] = None, max: Optional[Quantity["angle"]] = None, ephemeris: Optional[str] = None): # noqa: F821 """ Parameters ---------- @@ -590,7 +596,7 @@ def __init__(self, min=None, max=None, ephemeris=None): self.max = max self.ephemeris = ephemeris - def compute_constraint(self, times, observer, targets): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: moon = get_body("moon", times, location=observer.location, ephemeris=self.ephemeris) # note to future editors - the order matters here # moon.separation(targets) is NOT the same as targets.separation(moon) @@ -619,7 +625,7 @@ class MoonIlluminationConstraint(Constraint): Constraint is also satisfied if the Moon has set. """ - def __init__(self, min=None, max=None, ephemeris=None): + def __init__(self, min: Optional[float] = None, max: Optional[float] = None, ephemeris: Optional[str] = None): """ Parameters ---------- @@ -639,7 +645,7 @@ def __init__(self, min=None, max=None, ephemeris=None): self.ephemeris = ephemeris @classmethod - def dark(cls, min=None, max=0.25, **kwargs): + def dark(cls, min: Optional[float] = None, max: Optional[float] = 0.25, **kwargs) -> "MoonIlluminationConstraint": """ initialize a `~astroplan.constraints.MoonIlluminationConstraint` with defaults of no minimum and a maximum of 0.25 @@ -656,7 +662,7 @@ def dark(cls, min=None, max=0.25, **kwargs): return cls(min, max, **kwargs) @classmethod - def grey(cls, min=0.25, max=0.65, **kwargs): + def grey(cls, min: Optional[float] = 0.25, max: Optional[float] = 0.65, **kwargs) -> "MoonIlluminationConstraint": """ initialize a `~astroplan.constraints.MoonIlluminationConstraint` with defaults of a minimum of 0.25 and a maximum of 0.65 @@ -673,7 +679,7 @@ def grey(cls, min=0.25, max=0.65, **kwargs): return cls(min, max, **kwargs) @classmethod - def bright(cls, min=0.65, max=None, **kwargs): + def bright(cls, min: Optional[float] = 0.65, max: Optional[float] = None, **kwargs) -> "MoonIlluminationConstraint": """ initialize a `~astroplan.constraints.MoonIlluminationConstraint` with defaults of a minimum of 0.65 and no maximum @@ -689,7 +695,7 @@ def bright(cls, min=0.65, max=None, **kwargs): """ return cls(min, max, **kwargs) - def compute_constraint(self, times, observer, targets): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: # first is the moon up? cached_moon = _get_moon_data(times, observer) moon_alt = cached_moon['altaz'].alt @@ -716,7 +722,7 @@ class LocalTimeConstraint(Constraint): Constrain the observable hours. """ - def __init__(self, min=None, max=None): + def __init__(self, min: Optional[datetime.time] = None, max: Optional[datetime.time] = None): """ Parameters ---------- @@ -753,7 +759,7 @@ def __init__(self, min=None, max=None): if not isinstance(self.max, datetime.time): raise TypeError("Time limits must be specified as datetime.time objects.") - def compute_constraint(self, times, observer, targets): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: timezone = None @@ -804,7 +810,7 @@ class TimeConstraint(Constraint): to `is_observable` or `is_always_observable`. """ - def __init__(self, min=None, max=None): + def __init__(self, min: Optional[Time] = None, max: Optional[Time] = None): """ Parameters ---------- @@ -843,7 +849,7 @@ def __init__(self, min=None, max=None): raise TypeError("Time limits must be specified as " "astropy.time.Time objects.") - def compute_constraint(self, times, observer, targets): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: with warnings.catch_warnings(): warnings.simplefilter('ignore') min_time = Time("1950-01-01T00:00:00") if self.min is None else self.min @@ -857,7 +863,7 @@ class PrimaryEclipseConstraint(Constraint): Constrain observations to times during primary eclipse. """ - def __init__(self, eclipsing_system): + def __init__(self, eclipsing_system: EclipsingSystem): """ Parameters ---------- @@ -876,7 +882,7 @@ class SecondaryEclipseConstraint(Constraint): Constrain observations to times during secondary eclipse. """ - def __init__(self, eclipsing_system): + def __init__(self, eclipsing_system: EclipsingSystem): """ Parameters ---------- @@ -885,7 +891,7 @@ def __init__(self, eclipsing_system): """ self.eclipsing_system = eclipsing_system - def compute_constraint(self, times, observer=None, targets=None): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: mask = self.eclipsing_system.in_secondary_eclipse(times) return mask @@ -896,7 +902,7 @@ class PhaseConstraint(Constraint): (e.g.~transiting exoplanets, eclipsing binaries). """ - def __init__(self, periodic_event, min=None, max=None): + def __init__(self, periodic_event: PeriodicEvent, min: Optional[float] = None, max: Optional[float] = None): """ Parameters ---------- @@ -929,7 +935,7 @@ def __init__(self, periodic_event, min=None, max=None): self.min = min if min is not None else 0.0 self.max = max if max is not None else 1.0 - def compute_constraint(self, times, observer=None, targets=None): + def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: phase = self.periodic_event.phase(times) mask = np.where(self.max > self.min, @@ -938,8 +944,9 @@ def compute_constraint(self, times, observer=None, targets=None): return mask -def is_always_observable(constraints, observer, targets, times=None, - time_range=None, time_grid_resolution=0.5*u.hour): +def is_always_observable(constraints: Union[list[Constraint], Constraint], observer: Observer, + targets: Union[list[FixedTarget], SkyCoord], times: Optional[Time] = None, + time_range: Optional[Time] = None, time_grid_resolution: Quantity["time"] = 0.5*u.hour) -> list[bool]: """ A function to determine whether ``targets`` are always observable throughout ``time_range`` given constraints in the ``constraints_list`` for a @@ -988,8 +995,9 @@ def is_always_observable(constraints, observer, targets, times=None, return np.all(constraint_arr, axis=1) -def is_observable(constraints, observer, targets, times=None, - time_range=None, time_grid_resolution=0.5*u.hour): +def is_observable(constraints: Union[list[Constraint], Constraint], observer: Observer, + targets: Union[list[FixedTarget], SkyCoord], times: Optional[Time] = None, + time_range: Optional[Time] = None, time_grid_resolution: Quantity["time"] = 0.5*u.hour) -> list[bool]: """ Determines if the ``targets`` are observable during ``time_range`` given constraints in ``constraints_list`` for a particular ``observer``. @@ -1037,8 +1045,9 @@ def is_observable(constraints, observer, targets, times=None, return np.any(constraint_arr, axis=1) -def is_event_observable(constraints, observer, target, times=None, - times_ingress_egress=None): +def is_event_observable(constraints: Union[list[Constraint], Constraint], + observer: Observer, target: FixedTarget, times: Optional[Time] = None, + times_ingress_egress: Optional[Time] = None) -> np.ndarray[bool]: """ Determines if the ``target`` is observable at each time in ``times``, given constraints in ``constraints`` for a particular ``observer``. @@ -1091,9 +1100,9 @@ def is_event_observable(constraints, observer, target, times=None, return constraint_arr -def months_observable(constraints, observer, targets, - time_range=_current_year_time_range, - time_grid_resolution=0.5*u.hour): +def months_observable(constraints: Union[list[Constraint], Constraint], observer: Observer, + targets: Union[list[FixedTarget], SkyCoord], time_range: Time = _current_year_time_range, + time_grid_resolution: Quantity["time"] = 0.5*u.hour) -> list[set[int]]: """ Determines which month the specified ``targets`` are observable for a specific ``observer``, given the supplied ``constraints``. @@ -1160,8 +1169,9 @@ def months_observable(constraints, observer, targets, return months_observable -def observability_table(constraints, observer, targets, times=None, - time_range=None, time_grid_resolution=0.5*u.hour): +def observability_table(constraints: Union[list[Constraint], Constraint], observer: Observer, + targets: Union[list[FixedTarget], SkyCoord], times: Optional[Time] = None, + time_range: Optional[Time] = None, time_grid_resolution: Quantity["time"] = 0.5*u.hour) -> table.Table: """ Creates a table with information about observability for all the ``targets`` over the requested ``time_range``, given the constraints in @@ -1246,7 +1256,7 @@ def observability_table(constraints, observer, targets, times=None, return tab -def min_best_rescale(vals, min_val, max_val, less_than_min=1): +def min_best_rescale(vals: ArrayLike, min_val: float, max_val: float, less_than_min: int = 1): """ rescales an input array ``vals`` to be a score (between zero and one), where the ``min_val`` goes to one, and the ``max_val`` goes to zero. @@ -1290,7 +1300,7 @@ def min_best_rescale(vals, min_val, max_val, less_than_min=1): return rescaled -def max_best_rescale(vals, min_val, max_val, greater_than_max=1): +def max_best_rescale(vals: ArrayLike, min_val: float, max_val: float, greater_than_max: int = 1) -> ArrayLike: """ rescales an input array ``vals`` to be a score (between zero and one), where the ``max_val`` goes to one, and the ``min_val`` goes to zero. From 81841e14b72809bae16d044e81b15fc48b69809e Mon Sep 17 00:00:00 2001 From: Thomas Vandal Date: Sat, 14 Sep 2024 23:47:27 -0300 Subject: [PATCH 03/11] Type hint smaller modules --- astroplan/moon.py | 10 ++++++++-- astroplan/periodic.py | 29 ++++++++++++++++++----------- astroplan/target.py | 2 +- astroplan/utils.py | 19 ++++++++++++------- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/astroplan/moon.py b/astroplan/moon.py index 10aef507..d32fd80b 100644 --- a/astroplan/moon.py +++ b/astroplan/moon.py @@ -6,14 +6,20 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) +# Standard library +from typing import Optional + # Third-party import numpy as np from astropy.coordinates import get_sun, get_body +from astropy.time import Time +import astropy.units as u +from astropy.units import Quantity __all__ = ["moon_phase_angle", "moon_illumination"] -def moon_phase_angle(time, ephemeris=None): +def moon_phase_angle(time: Time, ephemeris: Optional[str] = None) -> Quantity[u.rad]: """ Calculate lunar orbital phase in radians. @@ -41,7 +47,7 @@ def moon_phase_angle(time, ephemeris=None): moon.distance - sun.distance*np.cos(elongation)) -def moon_illumination(time, ephemeris=None): +def moon_illumination(time: Time, ephemeris: Optional[str] = None) -> Quantity[u.rad]: """ Calculate fraction of the moon illuminated. diff --git a/astroplan/periodic.py b/astroplan/periodic.py index 44038b97..49809314 100644 --- a/astroplan/periodic.py +++ b/astroplan/periodic.py @@ -1,9 +1,14 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst from __future__ import (absolute_import, division, print_function, unicode_literals) +# Standard library +from typing import Optional, Union + +# Third party import numpy as np import astropy.units as u from astropy.time import Time +from astropy.units import Quantity __all__ = ['PeriodicEvent', 'EclipsingSystem'] @@ -13,7 +18,8 @@ class PeriodicEvent(object): A periodic event defined by an epoch and period. """ @u.quantity_input(period=u.day, duration=u.day) - def __init__(self, epoch, period, duration=None, name=None): + def __init__(self, epoch: Time, period: Quantity["time"], duration: Optional[Quantity["time"]] = None, # noqa: F821 + name: Optional[str] = None): """ Parameters @@ -32,7 +38,7 @@ def __init__(self, epoch, period, duration=None, name=None): self.name = name self.duration = duration - def phase(self, time): + def phase(self, time: Time) -> np.ndarray[float]: """ Phase of periodic event, on interval [0, 1). For example, the phase could be an orbital phase for an eclipsing binary system. @@ -66,8 +72,9 @@ class EclipsingSystem(PeriodicEvent): barycentric correction error (<=16 minutes). """ @u.quantity_input(period=u.day, duration=u.day) - def __init__(self, primary_eclipse_time, orbital_period, duration=None, - name=None, eccentricity=None, argument_of_periapsis=None): + def __init__(self, primary_eclipse_time: Time, orbital_period: Quantity["time"], # noqa: F821 + duration: Optional[Quantity["time"]] = None, name: Optional[str] = None, # noqa: F821 + eccentricity: Optional[float] = None, argument_of_periapsis: Optional[float] = None): """ Parameters ---------- @@ -99,7 +106,7 @@ def __init__(self, primary_eclipse_time, orbital_period, duration=None, argument_of_periapsis = np.pi/2 self.argument_of_periapsis = argument_of_periapsis - def in_primary_eclipse(self, time): + def in_primary_eclipse(self, time: Time) -> Union[np.ndarray[bool], bool]: """ Returns `True` when ``time`` is during a primary eclipse. @@ -120,7 +127,7 @@ def in_primary_eclipse(self, time): return ((phases < float(self.duration/self.period)/2) | (phases > 1 - float(self.duration/self.period)/2)) - def in_secondary_eclipse(self, time): + def in_secondary_eclipse(self, time: Time) -> Union[np.ndarray[bool], bool]: r""" Returns `True` when ``time`` is during a secondary eclipse @@ -161,7 +168,7 @@ def in_secondary_eclipse(self, time): return ((phases < secondary_eclipse_phase + float(self.duration/self.period)/2) & (phases > secondary_eclipse_phase - float(self.duration/self.period)/2)) - def out_of_eclipse(self, time): + def out_of_eclipse(self, time: Time) -> Union[np.ndarray[bool], bool]: """ Returns `True` when ``time`` is not during primary or secondary eclipse. @@ -181,7 +188,7 @@ def out_of_eclipse(self, time): return np.logical_not(np.logical_or(self.in_primary_eclipse(time), self.in_secondary_eclipse(time))) - def next_primary_eclipse_time(self, time, n_eclipses=1): + def next_primary_eclipse_time(self, time: Time, n_eclipses: int = 1) -> Time: """ Time of the next primary eclipse after ``time``. @@ -205,7 +212,7 @@ def next_primary_eclipse_time(self, time, n_eclipses=1): np.arange(n_eclipses) * self.period) return eclipse_times - def next_secondary_eclipse_time(self, time, n_eclipses=1): + def next_secondary_eclipse_time(self, time: Time, n_eclipses: int = 1) -> Time: """ Time of the next secondary eclipse after ``time``. @@ -234,7 +241,7 @@ def next_secondary_eclipse_time(self, time, n_eclipses=1): np.arange(n_eclipses) * self.period) return eclipse_times - def next_primary_ingress_egress_time(self, time, n_eclipses=1): + def next_primary_ingress_egress_time(self, time: Time, n_eclipses: int = 1) -> Time: """ Calculate the times of ingress and egress for the next ``n_eclipses`` primary eclipses after ``time`` @@ -264,7 +271,7 @@ def next_primary_ingress_egress_time(self, time, n_eclipses=1): return Time(ing_egr, format='jd', scale='utc') - def next_secondary_ingress_egress_time(self, time, n_eclipses=1): + def next_secondary_ingress_egress_time(self, time: Time, n_eclipses: int = 1) -> Time: """ Calculate the times of ingress and egress for the next ``n_eclipses`` secondary eclipses after ``time`` diff --git a/astroplan/target.py b/astroplan/target.py index 66cdd2ad..de8f8831 100644 --- a/astroplan/target.py +++ b/astroplan/target.py @@ -1,7 +1,7 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst from __future__ import (absolute_import, division, print_function, unicode_literals) -from typing import Any, Optional +from typing import Optional # Standard library from abc import ABCMeta diff --git a/astroplan/utils.py b/astroplan/utils.py index 0db34468..3bb15320 100644 --- a/astroplan/utils.py +++ b/astroplan/utils.py @@ -4,6 +4,8 @@ # Standard library import warnings +from typing import Union +import os # Third-party import numpy as np @@ -11,6 +13,8 @@ from astropy.time import Time import astropy.units as u from astropy.coordinates import EarthLocation +from astropy.units import Quantity +from numpy.typing import ArrayLike # Package from .exceptions import OldEarthOrientationDataWarning @@ -30,7 +34,7 @@ BACKUP_Time_get_delta_ut1_utc = Time._get_delta_ut1_utc -def _low_precision_utc_to_ut1(self, jd1, jd2): +def _low_precision_utc_to_ut1(self, jd1: np.ndarray[float], jd2: np.ndarray[float]) -> np.ndarray[float]: """ When no IERS Bulletin A is available (no internet connection), use low precision time conversion by assuming UT1-UTC=0 always. @@ -46,7 +50,7 @@ def _low_precision_utc_to_ut1(self, jd1, jd2): return np.zeros(self.shape) -def download_IERS_A(show_progress=True): +def download_IERS_A(show_progres: bool = True) -> None: """ Download and cache the IERS Bulletin A table. @@ -76,7 +80,7 @@ def download_IERS_A(show_progress=True): @u.quantity_input(time_resolution=u.hour) -def time_grid_from_range(time_range, time_resolution=0.5*u.hour): +def time_grid_from_range(time_range: Time, time_resolution: Quantity["time"] = 0.5*u.hour) -> Time: # noqa: F821 """ Get linearly-spaced sequence of times. @@ -151,7 +155,7 @@ def _unmock_remote_data(): # otherwise assume it's already correct -def _set_mpl_style_sheet(style_sheet): +def _set_mpl_style_sheet(style_sheet: dict) -> None: """ Import matplotlib, set the style sheet to ``style_sheet`` using the most backward compatible import pattern. @@ -161,7 +165,7 @@ def _set_mpl_style_sheet(style_sheet): matplotlib.rcParams.update(style_sheet) -def stride_array(arr, window_width): +def stride_array(arr: ArrayLike, window_width: int) -> np.ndarray: """ Computes all possible sequential subarrays of arr with length = window_width @@ -194,7 +198,7 @@ class EarthLocation_mock(EarthLocation): """ @classmethod - def of_site_mock(cls, string): + def of_site_mock(cls, string: str) -> EarthLocation: subaru = EarthLocation.from_geodetic(-155.4761111111111*u.deg, 19.825555555555564*u.deg, 4139*u.m) @@ -233,7 +237,8 @@ def of_site_mock(cls, string): return observatories[string.lower()] -def _open_shelve(shelffn, withclosing=False): +# NOTE: using dict as alias to avoid importing shelve above since they have similar properties +def _open_shelve(shelffn: Union[str, os.PathLike], withclosing: bool = False) -> dict: """ Opens a shelf file. If ``withclosing`` is True, it will be opened with closing, allowing use like: From 4661fe95eeca630ed324b5f17ddc5899c4425ddb Mon Sep 17 00:00:00 2001 From: Thomas Vandal Date: Sun, 15 Sep 2024 04:49:42 -0300 Subject: [PATCH 04/11] Add type hints to observer module --- astroplan/observer.py | 152 ++++++++++++++++++++++++------------------ astroplan/target.py | 4 +- 2 files changed, 88 insertions(+), 68 deletions(-) diff --git a/astroplan/observer.py b/astroplan/observer.py index 03f148dc..228755c7 100644 --- a/astroplan/observer.py +++ b/astroplan/observer.py @@ -5,13 +5,16 @@ # Standard library import sys +from typing import Any, Callable, Optional, Sequence, Union import datetime import warnings + # Third-party from astropy.coordinates import (EarthLocation, SkyCoord, AltAz, get_sun, get_body, Angle, Longitude) import astropy.units as u from astropy.time import Time +from astropy.units import Quantity from astropy.utils.exceptions import AstropyDeprecationWarning import numpy as np import pytz @@ -19,21 +22,22 @@ # Package from .exceptions import TargetNeverUpWarning, TargetAlwaysUpWarning from .moon import moon_illumination, moon_phase_angle -from .target import get_skycoord, SunFlag, MoonFlag +from .target import get_skycoord, SunFlag, MoonFlag, FixedTarget __all__ = ["Observer"] MAGIC_TIME = Time(-999, format='jd') +TargetType = Union[FixedTarget, SkyCoord, list[FixedTarget]] # Handle deprecated MAGIC_TIME variable -def deprecation_wrap_module(mod, deprecated): +def deprecation_wrap_module(mod: str, deprecated: Sequence[str]) -> Any: # noqa: F821 """Return a wrapped object that warns about deprecated accesses""" deprecated = set(deprecated) class DeprecateWrapper(object): - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> Any: if attr in deprecated: warnmsg = ("`MAGIC_TIME` will be deprecated in future versions " "of astroplan. Use masked Time objects instead.") @@ -47,7 +51,7 @@ def __getattr__(self, attr): deprecated=['MAGIC_TIME']) -def _process_nans_in_jds(jds): +def _process_nans_in_jds(jds: Union[float, int, np.ndarray, Quantity["time"], Time]) -> np.ma.MaskedArray: # noqa: F821 """ Some functions calculate times for events that won't happen, yielding nans. This wrapper manages vectors of (potentially) invalid JDs that must be passed to the astropy.time.Time @@ -61,7 +65,7 @@ def _process_nans_in_jds(jds): return masked_jds -def _generate_24hr_grid(t0, start, end, n_grid_points, for_deriv=False): +def _generate_24hr_grid(t0: Time, start: float, end: float, n_grid_points: int, for_deriv: bool = False) -> Time: """ Generate a nearly linearly spaced grid of time durations. @@ -80,7 +84,7 @@ def _generate_24hr_grid(t0, start, end, n_grid_points, for_deriv=False): end : float Number of days before/after ``t0`` to end the grid. - n_grid_points : int (optional) + n_grid_points : int Number of grid points to generate for_deriv : bool @@ -148,9 +152,11 @@ class Observer(object): """ @u.quantity_input(elevation=u.m) - def __init__(self, location=None, timezone='UTC', name=None, latitude=None, - longitude=None, elevation=0*u.m, pressure=None, - relative_humidity=None, temperature=None, description=None): + def __init__(self, location: Optional[EarthLocation] = None, timezone: Union[str, datetime.tzinfo] = 'UTC', + name: Optional[str] = None, latitude: Optional[Union[float, str, Quantity["angle"]]] = None, # noqa: F821 + longitude: Optional[Union[float, str, Quantity["angle"]]] = None, elevation: Quantity["length"] = 0*u.m, # noqa: F821 + pressure: Optional[Quantity["pressure"]] =None, relative_humidity: Optional[float] = None, # noqa: F821 + temperature: Quantity["temperature"] = None, description: Optional[str] = None): # noqa: F821 """ Parameters ---------- @@ -223,21 +229,21 @@ def __init__(self, location=None, timezone='UTC', name=None, latitude=None, 'instance of datetime.tzinfo') @property - def longitude(self): + def longitude(self) -> Quantity["angle"]: # noqa: F821 """The longitude of the observing location, derived from the location.""" return self.location.lon @property - def latitude(self): + def latitude(self) -> Quantity["angle"]: # noqa: F821 """The latitude of the observing location, derived from the location.""" return self.location.lat @property - def elevation(self): + def elevation(self) -> Quantity["length"]: # noqa: F821 """The elevation of the observing location with respect to sea level.""" return self.location.height - def __repr__(self): + def __repr__(self) -> str: """ String representation of the `~astroplan.Observer` object. @@ -273,7 +279,7 @@ def __repr__(self): attributes_strings.append("{}={}".format(name, value)) return "<{}: {}>".format(class_name, ",\n ".join(attributes_strings)) - def _key(self): + def _key(self) -> tuple: """ Generate a tuple of the attributes that determine uniqueness of `~astroplan.Observer` objects. @@ -302,7 +308,7 @@ def _key(self): self.elevation, self.timezone,) - def __hash__(self): + def __hash__(self) -> int: """ Hash the `~astroplan.Observer` object. @@ -317,7 +323,7 @@ def __hash__(self): return hash(self._key()) - def __eq__(self, other): + def __eq__(self, other: "Observer") -> bool: """ Equality check for `~astroplan.Observer` objects. @@ -336,7 +342,7 @@ def __eq__(self, other): else: return NotImplemented - def __ne__(self, other): + def __ne__(self, other: "Observer") -> bool: """ Inequality check for `~astroplan.Observer` objects. @@ -353,7 +359,7 @@ def __ne__(self, other): return not self.__eq__(other) @classmethod - def at_site(cls, site_name, **kwargs): + def at_site(cls, site_name: str, **kwargs) -> "Observer": """ Initialize an `~astroplan.observer.Observer` object with a site name. @@ -387,7 +393,7 @@ def at_site(cls, site_name, **kwargs): "initializing an Observer with Observer.at_site()") return cls(location=EarthLocation.of_site(site_name), name=name, **kwargs) - def astropy_time_to_datetime(self, astropy_time): + def astropy_time_to_datetime(self, astropy_time: Time) -> datetime.datetime: """ Convert the `~astropy.time.Time` object ``astropy_time`` to a localized `~datetime.datetime` object. @@ -429,7 +435,7 @@ def astropy_time_to_datetime(self, astropy_time): # Convert UTC to local timezone return self.timezone.normalize(utc_datetime) - def datetime_to_astropy_time(self, date_time): + def datetime_to_astropy_time(self, date_time: datetime.datetime) -> Time: """ Convert the `~datetime.datetime` object ``date_time`` to a `~astropy.time.Time` object. @@ -474,7 +480,7 @@ def datetime_to_astropy_time(self, date_time): return Time(date_time, location=self.location) - def _is_broadcastable(self, shp1, shp2): + def _is_broadcastable(self, shp1: tuple[int], shp2: tuple[int]) -> bool: """Test if two shape tuples are broadcastable""" if shp1 == shp2: return True @@ -485,7 +491,7 @@ def _is_broadcastable(self, shp1, shp2): return False return True - def _preprocess_inputs(self, time, target=None, grid_times_targets=False): + def _preprocess_inputs(self, time: Any, target: Optional[TargetType] = None, grid_times_targets: bool = False) -> tuple[Time, SkyCoord]: """ Preprocess time and target inputs @@ -531,7 +537,8 @@ def _preprocess_inputs(self, time, target=None, grid_times_targets=False): .format(time.shape, target.shape)) return time, target - def altaz(self, time, target=None, obswl=None, grid_times_targets=False): + def altaz(self, time: Time, target: Optional[TargetType] = None, + obswl: Optional[Quantity["length"]] = None, grid_times_targets: bool = False) -> Union[AltAz, SkyCoord]: # noqa: F821 """ Get an `~astropy.coordinates.AltAz` frame or coordinate. @@ -608,8 +615,8 @@ def altaz(self, time, target=None, obswl=None, grid_times_targets=False): else: return target.transform_to(altaz_frame) - def parallactic_angle(self, time, target, grid_times_targets=False, - kind='mean', model=None): + def parallactic_angle(self, time: Time, target: Union[TargetType], + grid_times_targets: bool = False, kind: str = 'mean', model: Optional[str] = None) -> Angle: """ Calculate the parallactic angle. @@ -664,7 +671,9 @@ def parallactic_angle(self, time, target, grid_times_targets=False, # Sun-related methods. @u.quantity_input(horizon=u.deg) - def _horiz_cross(self, t, alt, rise_set, horizon=0*u.degree): + def _horiz_cross(self, t: Time, alt: Quantity["angle"], rise_set: str, # noqa: F821 + horizon: Quantity["angle"] = 0*u.degree # noqa: F821 + ) -> tuple[Union[Quantity["angle"], np.ndarray[float]]]: # noqa: F821 """ Find time ``t`` when values in array ``a`` go from negative to positive or positive to negative (exclude endpoints) @@ -771,8 +780,9 @@ def _horiz_cross(self, t, alt, rise_set, horizon=0*u.degree): return alt_lims1, alt_lims2, jd_lims1, jd_lims2 @u.quantity_input(horizon=u.deg) - def _two_point_interp(self, jd_before, jd_after, - alt_before, alt_after, horizon=0*u.deg): + def _two_point_interp(self, jd_before: float, jd_after: float, + alt_before: Quantity["angle"], alt_after: Quantity["angle"], # noqa: F821 + horizon: Quantity["angle"] = 0*u.deg) -> Time: # noqa: F821 """ Do linear interpolation between two ``altitudes`` at two ``times`` to determine the time where the altitude @@ -812,7 +822,8 @@ def _two_point_interp(self, jd_before, jd_after, return np.squeeze(times) - def _altitude_trig(self, LST, target, grid_times_targets=False): + def _altitude_trig(self, LST: Time, target: Union[SkyCoord, FixedTarget], + grid_times_targets: bool = False) -> Quantity["angle"]: # noqa: F821 """ Calculate the altitude of ``target`` at local sidereal times ``LST``. @@ -846,8 +857,9 @@ def _altitude_trig(self, LST, target, grid_times_targets=False): np.cos(LST.radian - target.ra.radian)) return alt - def _calc_riseset(self, time, target, prev_next, rise_set, horizon, - n_grid_points=150, grid_times_targets=False): + def _calc_riseset(self, time: Any, target: SkyCoord, prev_next: str, rise_set: str, + horizon: Quantity["angle"], n_grid_points: int = 150, # noqa: F821 + grid_times_targets: bool = False) -> Time: """ Time at next rise/set of ``target``. @@ -913,8 +925,9 @@ def _calc_riseset(self, time, target, prev_next, rise_set, horizon, return self._two_point_interp(jd1, jd2, al1, al2, horizon=horizon) - def _calc_transit(self, time, target, prev_next, antitransit=False, - n_grid_points=150, grid_times_targets=False): + def _calc_transit(self, time: Any, target: SkyCoord, prev_next: str, + antitransit: bool = False, n_grid_points: int = 150, + grid_times_targets: bool = False) -> Time: """ Time at next transit of the meridian of `target`. @@ -989,7 +1002,7 @@ def _calc_transit(self, time, target, prev_next, antitransit=False, return self._two_point_interp(jd1, jd2, al1, al2, horizon=horizon) - def _determine_which_event(self, function, args_dict): + def _determine_which_event(self, function: Callable, args_dict: dict[str, Any]) -> Time: """ Run through the next/previous/nearest permutations of the solutions to `function(time, ...)`, and return the previous/next/nearest one @@ -1006,12 +1019,12 @@ def _determine_which_event(self, function, args_dict): # Assemble arguments for function, depending on the function. if function == self._calc_riseset: - def event_function(w): + def event_function(w: str) -> Time: return function(time, target, w, rise_set, horizon, grid_times_targets=grid_times_targets, n_grid_points=n_grid_points) elif function == self._calc_transit: - def event_function(w): + def event_function(w: str) -> Time: return function(time, target, w, antitransit=antitransit, grid_times_targets=grid_times_targets, n_grid_points=n_grid_points) @@ -1045,8 +1058,9 @@ def event_function(w): '"nearest".') @u.quantity_input(horizon=u.deg) - def target_rise_time(self, time, target, which='nearest', - horizon=0*u.degree, grid_times_targets=False, n_grid_points=150): + def target_rise_time(self, time: Time, target: TargetType, + which: str = 'nearest', horizon: Quantity["angle"] = 0*u.degree, # noqa: F821 + grid_times_targets: bool = False, n_grid_points: int = 150) -> Time: """ Calculate rise time. @@ -1112,8 +1126,9 @@ def target_rise_time(self, time, target, which='nearest', grid_times_targets=grid_times_targets)) @u.quantity_input(horizon=u.deg) - def target_set_time(self, time, target, which='nearest', horizon=0*u.degree, - grid_times_targets=False, n_grid_points=150): + def target_set_time(self, time: Time, target: SkyCoord, which: str = 'nearest', + horizon: Quantity["angle"] = 0*u.degree, grid_times_targets: bool = False, # noqa: F821 + n_grid_points: int = 150) -> Time: """ Calculate set time. @@ -1178,8 +1193,8 @@ def target_set_time(self, time, target, which='nearest', horizon=0*u.degree, n_grid_points=n_grid_points, grid_times_targets=grid_times_targets)) - def target_meridian_transit_time(self, time, target, which='nearest', - grid_times_targets=False, n_grid_points=150): + def target_meridian_transit_time(self, time: Time, target: TargetType, which: str = 'nearest', + grid_times_targets: bool = False, n_grid_points: int = 150) -> Time: """ Calculate time at the transit of the meridian. @@ -1238,8 +1253,8 @@ def target_meridian_transit_time(self, time, target, which='nearest', rise_set='setting', grid_times_targets=grid_times_targets)) - def target_meridian_antitransit_time(self, time, target, which='nearest', - grid_times_targets=False, n_grid_points=150): + def target_meridian_antitransit_time(self, time: Time, target: TargetType, which: str = 'nearest', + grid_times_targets: bool = False, n_grid_points: int = 150) -> Time: """ Calculate time at the antitransit of the meridian. @@ -1300,7 +1315,8 @@ def target_meridian_antitransit_time(self, time, target, which='nearest', grid_times_targets=grid_times_targets)) @u.quantity_input(horizon=u.deg) - def sun_rise_time(self, time, which='nearest', horizon=0*u.degree, n_grid_points=150): + def sun_rise_time(self, time: Time, which: str = 'nearest', horizon: Quantity["angle"] = 0*u.degree, # noqa: F821 + n_grid_points: int = 150) -> Time: """ Time of sunrise. @@ -1351,7 +1367,8 @@ def sun_rise_time(self, time, which='nearest', horizon=0*u.degree, n_grid_points n_grid_points=n_grid_points) @u.quantity_input(horizon=u.deg) - def sun_set_time(self, time, which='nearest', horizon=0*u.degree, n_grid_points=150): + def sun_set_time(self, time: Time, which: str = 'nearest', horizon: Quantity["angle"] = 0*u.degree, # noqa: F821 + n_grid_points: int = 150) -> Time: """ Time of sunset. @@ -1401,7 +1418,7 @@ def sun_set_time(self, time, which='nearest', horizon=0*u.degree, n_grid_points= return self.target_set_time(time, get_sun(time), which, horizon, n_grid_points=n_grid_points) - def noon(self, time, which='nearest', n_grid_points=150): + def noon(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) -> Time: """ Time at solar noon. @@ -1430,7 +1447,7 @@ def noon(self, time, which='nearest', n_grid_points=150): return self.target_meridian_transit_time(time, get_sun(time), which, n_grid_points=n_grid_points) - def midnight(self, time, which='nearest', n_grid_points=150): + def midnight(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) -> Time: """ Time at solar midnight. @@ -1461,7 +1478,7 @@ def midnight(self, time, which='nearest', n_grid_points=150): # Twilight convenience functions - def twilight_evening_astronomical(self, time, which='nearest', n_grid_points=150): + def twilight_evening_astronomical(self, time: Time, which: str = 'nearest', n_grid_points: int =150) -> Time: """ Time at evening astronomical (-18 degree) twilight. @@ -1490,7 +1507,7 @@ def twilight_evening_astronomical(self, time, which='nearest', n_grid_points=150 return self.sun_set_time(time, which, horizon=-18*u.degree, n_grid_points=n_grid_points) - def twilight_evening_nautical(self, time, which='nearest', n_grid_points=150): + def twilight_evening_nautical(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) -> Time: """ Time at evening nautical (-12 degree) twilight. @@ -1519,7 +1536,7 @@ def twilight_evening_nautical(self, time, which='nearest', n_grid_points=150): return self.sun_set_time(time, which, horizon=-12*u.degree, n_grid_points=n_grid_points) - def twilight_evening_civil(self, time, which='nearest', n_grid_points=150): + def twilight_evening_civil(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) -> Time: """ Time at evening civil (-6 degree) twilight. @@ -1548,7 +1565,7 @@ def twilight_evening_civil(self, time, which='nearest', n_grid_points=150): return self.sun_set_time(time, which, horizon=-6*u.degree, n_grid_points=n_grid_points) - def twilight_morning_astronomical(self, time, which='nearest', n_grid_points=150): + def twilight_morning_astronomical(self, time: Time, which: str = 'nearest', n_grid_points: int =150) -> Time: """ Time at morning astronomical (-18 degree) twilight. @@ -1577,7 +1594,7 @@ def twilight_morning_astronomical(self, time, which='nearest', n_grid_points=150 return self.sun_rise_time(time, which, horizon=-18*u.degree, n_grid_points=n_grid_points) - def twilight_morning_nautical(self, time, which='nearest', n_grid_points=150): + def twilight_morning_nautical(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) -> Time: """ Time at morning nautical (-12 degree) twilight. @@ -1606,7 +1623,7 @@ def twilight_morning_nautical(self, time, which='nearest', n_grid_points=150): return self.sun_rise_time(time, which, horizon=-12*u.degree, n_grid_points=n_grid_points) - def twilight_morning_civil(self, time, which='nearest', n_grid_points=150): + def twilight_morning_civil(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) -> Time: """ Time at morning civil (-6 degree) twilight. @@ -1637,7 +1654,8 @@ def twilight_morning_civil(self, time, which='nearest', n_grid_points=150): # Moon-related methods. - def moon_rise_time(self, time, which='nearest', horizon=0*u.deg, n_grid_points=150): + def moon_rise_time(self, time: Time, which: str = 'nearest', horizon: Quantity["angle"] = 0*u.deg, # noqa: F821 + n_grid_points: int = 150) -> Time: """ Returns the local moon rise time. @@ -1670,7 +1688,8 @@ def moon_rise_time(self, time, which='nearest', horizon=0*u.deg, n_grid_points=1 return self.target_rise_time(time, MoonFlag, which, horizon, n_grid_points=n_grid_points) - def moon_set_time(self, time, which='nearest', horizon=0*u.deg, n_grid_points=150): + def moon_set_time(self, time: Time, which: str = 'nearest', horizon: Quantity["angle"] = 0*u.deg, # noqa: F821 + n_grid_points: int = 150) -> Time: """ Returns the local moon set time. @@ -1703,7 +1722,7 @@ def moon_set_time(self, time, which='nearest', horizon=0*u.deg, n_grid_points=15 return self.target_set_time(time, MoonFlag, which, horizon, n_grid_points=n_grid_points) - def moon_illumination(self, time): + def moon_illumination(self, time: Any): """ Calculate the illuminated fraction of the moon. @@ -1737,7 +1756,7 @@ def moon_illumination(self, time): return moon_illumination(time) - def moon_phase(self, time=None): + def moon_phase(self, time: Optional[Any] = None) -> Quantity["angle"]: # noqa: F821 """ Calculate lunar orbital phase. @@ -1774,7 +1793,7 @@ def moon_phase(self, time=None): return moon_phase_angle(time) - def moon_altaz(self, time, ephemeris=None): + def moon_altaz(self, time: Any, ephemeris: Optional[str] = None) -> SkyCoord: """ Returns the position of the moon in alt/az. @@ -1816,7 +1835,7 @@ def moon_altaz(self, time, ephemeris=None): moon = get_body("moon", time, location=self.location, ephemeris=ephemeris) return self.altaz(time, moon) - def sun_altaz(self, time): + def sun_altaz(self, time: Any) -> SkyCoord: """ Returns the position of the Sun in alt/az. @@ -1846,8 +1865,8 @@ def sun_altaz(self, time): return self.altaz(time, sun) @u.quantity_input(horizon=u.deg) - def target_is_up(self, time, target, horizon=0*u.degree, - return_altaz=False, grid_times_targets=False): + def target_is_up(self, time: Any, target: TargetType, horizon: Quantity["angle"] = 0*u.degree, # noqa: F821 + return_altaz: bool = False, grid_times_targets: bool = False) -> Union[bool, np.ndarray[bool]]: """ Is ``target`` above ``horizon`` at this ``time``? @@ -1912,7 +1931,8 @@ def target_is_up(self, time, target, horizon=0*u.degree, return observable, altaz @u.quantity_input(horizon=u.deg) - def is_night(self, time, horizon=0*u.deg, obswl=None): + def is_night(self, time: Any, horizon: Quantity["angle"] = 0*u.deg, # noqa: F821 + obswl: Optional[Quantity["length"]] = None) -> Union[bool, np.ndarray[bool]]: # noqa: F821 """ Is the Sun below ``horizon`` at ``time``? @@ -1959,7 +1979,7 @@ def is_night(self, time, horizon=0*u.deg, obswl=None): else: return solar_altitude < horizon - def local_sidereal_time(self, time, kind='apparent', model=None): + def local_sidereal_time(self, time: Any, kind: str = 'apparent', model: Optional[str] = None): """ Convert ``time`` to local sidereal time for observer. @@ -1993,7 +2013,7 @@ def local_sidereal_time(self, time, kind='apparent', model=None): return time.sidereal_time(kind, longitude=self.location.lon, model=model) - def target_hour_angle(self, time, target, grid_times_targets=False): + def target_hour_angle(self, time: Any, target: TargetType, grid_times_targets: bool = False) -> Angle: """ Calculate the local hour angle of ``target`` at ``time``. @@ -2023,7 +2043,7 @@ def target_hour_angle(self, time, target, grid_times_targets=False): return Longitude(self.local_sidereal_time(time) - target.ra) @u.quantity_input(horizon=u.degree) - def tonight(self, time=None, horizon=0 * u.degree, obswl=None): + def tonight(self, time: Optional[Time] = None, horizon: Quantity["angle"] = 0 * u.degree, obswl: Optional[Quantity["length"]] =None): # noqa: F821 """ Return a time range corresponding to the nearest night diff --git a/astroplan/target.py b/astroplan/target.py index de8f8831..31ff709a 100644 --- a/astroplan/target.py +++ b/astroplan/target.py @@ -1,10 +1,10 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst from __future__ import (absolute_import, division, print_function, unicode_literals) -from typing import Optional # Standard library from abc import ABCMeta +from typing import Optional, Union # Third-party import astropy.units as u @@ -193,7 +193,7 @@ class NonFixedTarget(Target): """ -def get_skycoord(targets: list[FixedTarget]) -> SkyCoord: +def get_skycoord(targets: Union[list[FixedTarget], FixedTarget, SkyCoord]) -> SkyCoord: """ Return an `~astropy.coordinates.SkyCoord` object. From 88fe72b818d33e65f5b8985bce4c385c7c0e97af Mon Sep 17 00:00:00 2001 From: Thomas Vandal Date: Sun, 15 Sep 2024 22:24:57 -0300 Subject: [PATCH 05/11] Remove physical type annotations from Quantity type hints Does not play nicely with `quantity_input` and Union type hints. Not necessary to provide basic type hint (LSP and static checks). See https://github.com/astropy/astropy/issues/17017 for more info --- astroplan/constraints.py | 22 ++++++++-------- astroplan/observer.py | 56 ++++++++++++++++++++-------------------- astroplan/periodic.py | 6 ++--- astroplan/scheduling.py | 28 ++++++++++---------- astroplan/target.py | 8 +++--- astroplan/utils.py | 2 +- 6 files changed, 61 insertions(+), 61 deletions(-) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index ac64e110..6ab07f5d 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -230,7 +230,7 @@ class Constraint(object): __metaclass__ = ABCMeta def __call__(self, observer: Observer, targets: Sequence[FixedTarget], times: Optional[Time] = None, - time_range: Optional[Time] = None, time_grid_resolution: Quantity["time"] = 0.5*u.hour, + time_range: Optional[Time] = None, time_grid_resolution: Quantity = 0.5*u.hour, grid_times_targets: bool = False) -> np.ndarray[Union[float, bool]]: """ Compute the constraint for this class @@ -340,7 +340,7 @@ class AltitudeConstraint(Constraint): float on [0, 1], where 0 is the min altitude and 1 is the max. """ - def __init__(self, min: Optional[Quantity["angle"]] = None, max: Optional[Quantity["angle"]] = None, boolean_constraint: bool = True): # noqa: F821 + def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = None, boolean_constraint: bool = True): if min is None: self.min = -90*u.deg else: @@ -427,7 +427,7 @@ class AtNightConstraint(Constraint): Constrain the Sun to be below ``horizon``. """ @u.quantity_input(horizon=u.deg) - def __init__(self, max_solar_altitude: Quantity["angle"] = 0*u.deg, force_pressure_zero: bool = True): # noqa: F821 + def __init__(self, max_solar_altitude: Quantity = 0*u.deg, force_pressure_zero: bool = True): """ Parameters ---------- @@ -464,7 +464,7 @@ def twilight_astronomical(cls, **kwargs) -> "AtNightConstraint": """ return cls(max_solar_altitude=-18*u.deg, **kwargs) - def _get_solar_altitudes(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> Quantity["angle"]: # noqa: F821 + def _get_solar_altitudes(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> Quantity: if not hasattr(observer, '_altaz_cache'): observer._altaz_cache = {} @@ -501,7 +501,7 @@ class GalacticLatitudeConstraint(Constraint): Constrain the distance between the Galactic plane and some targets. """ - def __init__(self, min: Optional[Quantity["angle"]] = None, max: Optional[Quantity["angle"]] = None): # noqa: F821 + def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = None): """ Parameters ---------- @@ -535,7 +535,7 @@ class SunSeparationConstraint(Constraint): Constrain the distance between the Sun and some targets. """ - def __init__(self, min: Optional[Quantity["angle"]] = None, max: Optional[Quantity["angle"]] = None): # noqa: F821 + def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = None): """ Parameters ---------- @@ -577,7 +577,7 @@ class MoonSeparationConstraint(Constraint): Constrain the distance between the Earth's moon and some targets. """ - def __init__(self, min: Optional[Quantity["angle"]] = None, max: Optional[Quantity["angle"]] = None, ephemeris: Optional[str] = None): # noqa: F821 + def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = None, ephemeris: Optional[str] = None): """ Parameters ---------- @@ -946,7 +946,7 @@ def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[ def is_always_observable(constraints: Union[list[Constraint], Constraint], observer: Observer, targets: Union[list[FixedTarget], SkyCoord], times: Optional[Time] = None, - time_range: Optional[Time] = None, time_grid_resolution: Quantity["time"] = 0.5*u.hour) -> list[bool]: + time_range: Optional[Time] = None, time_grid_resolution: Quantity = 0.5*u.hour) -> list[bool]: """ A function to determine whether ``targets`` are always observable throughout ``time_range`` given constraints in the ``constraints_list`` for a @@ -997,7 +997,7 @@ def is_always_observable(constraints: Union[list[Constraint], Constraint], obser def is_observable(constraints: Union[list[Constraint], Constraint], observer: Observer, targets: Union[list[FixedTarget], SkyCoord], times: Optional[Time] = None, - time_range: Optional[Time] = None, time_grid_resolution: Quantity["time"] = 0.5*u.hour) -> list[bool]: + time_range: Optional[Time] = None, time_grid_resolution: Quantity = 0.5*u.hour) -> list[bool]: """ Determines if the ``targets`` are observable during ``time_range`` given constraints in ``constraints_list`` for a particular ``observer``. @@ -1102,7 +1102,7 @@ def is_event_observable(constraints: Union[list[Constraint], Constraint], def months_observable(constraints: Union[list[Constraint], Constraint], observer: Observer, targets: Union[list[FixedTarget], SkyCoord], time_range: Time = _current_year_time_range, - time_grid_resolution: Quantity["time"] = 0.5*u.hour) -> list[set[int]]: + time_grid_resolution: Quantity = 0.5*u.hour) -> list[set[int]]: """ Determines which month the specified ``targets`` are observable for a specific ``observer``, given the supplied ``constraints``. @@ -1171,7 +1171,7 @@ def months_observable(constraints: Union[list[Constraint], Constraint], observer def observability_table(constraints: Union[list[Constraint], Constraint], observer: Observer, targets: Union[list[FixedTarget], SkyCoord], times: Optional[Time] = None, - time_range: Optional[Time] = None, time_grid_resolution: Quantity["time"] = 0.5*u.hour) -> table.Table: + time_range: Optional[Time] = None, time_grid_resolution: Quantity = 0.5*u.hour) -> table.Table: """ Creates a table with information about observability for all the ``targets`` over the requested ``time_range``, given the constraints in diff --git a/astroplan/observer.py b/astroplan/observer.py index 228755c7..0e2ee3ff 100644 --- a/astroplan/observer.py +++ b/astroplan/observer.py @@ -32,7 +32,7 @@ TargetType = Union[FixedTarget, SkyCoord, list[FixedTarget]] # Handle deprecated MAGIC_TIME variable -def deprecation_wrap_module(mod: str, deprecated: Sequence[str]) -> Any: # noqa: F821 +def deprecation_wrap_module(mod: str, deprecated: Sequence[str]) -> Any: """Return a wrapped object that warns about deprecated accesses""" deprecated = set(deprecated) @@ -51,7 +51,7 @@ def __getattr__(self, attr: str) -> Any: deprecated=['MAGIC_TIME']) -def _process_nans_in_jds(jds: Union[float, int, np.ndarray, Quantity["time"], Time]) -> np.ma.MaskedArray: # noqa: F821 +def _process_nans_in_jds(jds: Union[float, int, np.ndarray, Quantity, Time]) -> np.ma.MaskedArray: """ Some functions calculate times for events that won't happen, yielding nans. This wrapper manages vectors of (potentially) invalid JDs that must be passed to the astropy.time.Time @@ -153,10 +153,10 @@ class Observer(object): """ @u.quantity_input(elevation=u.m) def __init__(self, location: Optional[EarthLocation] = None, timezone: Union[str, datetime.tzinfo] = 'UTC', - name: Optional[str] = None, latitude: Optional[Union[float, str, Quantity["angle"]]] = None, # noqa: F821 - longitude: Optional[Union[float, str, Quantity["angle"]]] = None, elevation: Quantity["length"] = 0*u.m, # noqa: F821 - pressure: Optional[Quantity["pressure"]] =None, relative_humidity: Optional[float] = None, # noqa: F821 - temperature: Quantity["temperature"] = None, description: Optional[str] = None): # noqa: F821 + name: Optional[str] = None, latitude: Optional[Union[float, str, Quantity]] = None, + longitude: Optional[Union[float, str, Quantity]] = None, elevation: Quantity = 0*u.m, + pressure: Optional[Quantity] =None, relative_humidity: Optional[float] = None, + temperature: Quantity = None, description: Optional[str] = None): """ Parameters ---------- @@ -229,17 +229,17 @@ def __init__(self, location: Optional[EarthLocation] = None, timezone: Union[str 'instance of datetime.tzinfo') @property - def longitude(self) -> Quantity["angle"]: # noqa: F821 + def longitude(self) -> Quantity: """The longitude of the observing location, derived from the location.""" return self.location.lon @property - def latitude(self) -> Quantity["angle"]: # noqa: F821 + def latitude(self) -> Quantity: """The latitude of the observing location, derived from the location.""" return self.location.lat @property - def elevation(self) -> Quantity["length"]: # noqa: F821 + def elevation(self) -> Quantity: """The elevation of the observing location with respect to sea level.""" return self.location.height @@ -538,7 +538,7 @@ def _preprocess_inputs(self, time: Any, target: Optional[TargetType] = None, gri return time, target def altaz(self, time: Time, target: Optional[TargetType] = None, - obswl: Optional[Quantity["length"]] = None, grid_times_targets: bool = False) -> Union[AltAz, SkyCoord]: # noqa: F821 + obswl: Optional[Quantity] = None, grid_times_targets: bool = False) -> Union[AltAz, SkyCoord]: """ Get an `~astropy.coordinates.AltAz` frame or coordinate. @@ -671,9 +671,9 @@ def parallactic_angle(self, time: Time, target: Union[TargetType], # Sun-related methods. @u.quantity_input(horizon=u.deg) - def _horiz_cross(self, t: Time, alt: Quantity["angle"], rise_set: str, # noqa: F821 - horizon: Quantity["angle"] = 0*u.degree # noqa: F821 - ) -> tuple[Union[Quantity["angle"], np.ndarray[float]]]: # noqa: F821 + def _horiz_cross(self, t: Time, alt: Quantity, rise_set: str, + horizon: Quantity = 0*u.degree + ) -> tuple[Union[Quantity, np.ndarray[float]]]: """ Find time ``t`` when values in array ``a`` go from negative to positive or positive to negative (exclude endpoints) @@ -781,8 +781,8 @@ def _horiz_cross(self, t: Time, alt: Quantity["angle"], rise_set: str, # noqa: @u.quantity_input(horizon=u.deg) def _two_point_interp(self, jd_before: float, jd_after: float, - alt_before: Quantity["angle"], alt_after: Quantity["angle"], # noqa: F821 - horizon: Quantity["angle"] = 0*u.deg) -> Time: # noqa: F821 + alt_before: Quantity, alt_after: Quantity, + horizon: Quantity = 0*u.deg) -> Time: """ Do linear interpolation between two ``altitudes`` at two ``times`` to determine the time where the altitude @@ -823,7 +823,7 @@ def _two_point_interp(self, jd_before: float, jd_after: float, return np.squeeze(times) def _altitude_trig(self, LST: Time, target: Union[SkyCoord, FixedTarget], - grid_times_targets: bool = False) -> Quantity["angle"]: # noqa: F821 + grid_times_targets: bool = False) -> Quantity: """ Calculate the altitude of ``target`` at local sidereal times ``LST``. @@ -858,7 +858,7 @@ def _altitude_trig(self, LST: Time, target: Union[SkyCoord, FixedTarget], return alt def _calc_riseset(self, time: Any, target: SkyCoord, prev_next: str, rise_set: str, - horizon: Quantity["angle"], n_grid_points: int = 150, # noqa: F821 + horizon: Quantity, n_grid_points: int = 150, grid_times_targets: bool = False) -> Time: """ Time at next rise/set of ``target``. @@ -1059,7 +1059,7 @@ def event_function(w: str) -> Time: @u.quantity_input(horizon=u.deg) def target_rise_time(self, time: Time, target: TargetType, - which: str = 'nearest', horizon: Quantity["angle"] = 0*u.degree, # noqa: F821 + which: str = 'nearest', horizon: Quantity = 0*u.degree, grid_times_targets: bool = False, n_grid_points: int = 150) -> Time: """ Calculate rise time. @@ -1127,7 +1127,7 @@ def target_rise_time(self, time: Time, target: TargetType, @u.quantity_input(horizon=u.deg) def target_set_time(self, time: Time, target: SkyCoord, which: str = 'nearest', - horizon: Quantity["angle"] = 0*u.degree, grid_times_targets: bool = False, # noqa: F821 + horizon: Quantity = 0*u.degree, grid_times_targets: bool = False, n_grid_points: int = 150) -> Time: """ Calculate set time. @@ -1315,7 +1315,7 @@ def target_meridian_antitransit_time(self, time: Time, target: TargetType, which grid_times_targets=grid_times_targets)) @u.quantity_input(horizon=u.deg) - def sun_rise_time(self, time: Time, which: str = 'nearest', horizon: Quantity["angle"] = 0*u.degree, # noqa: F821 + def sun_rise_time(self, time: Time, which: str = 'nearest', horizon: Quantity = 0*u.degree, n_grid_points: int = 150) -> Time: """ Time of sunrise. @@ -1367,7 +1367,7 @@ def sun_rise_time(self, time: Time, which: str = 'nearest', horizon: Quantity["a n_grid_points=n_grid_points) @u.quantity_input(horizon=u.deg) - def sun_set_time(self, time: Time, which: str = 'nearest', horizon: Quantity["angle"] = 0*u.degree, # noqa: F821 + def sun_set_time(self, time: Time, which: str = 'nearest', horizon: Quantity = 0*u.degree, n_grid_points: int = 150) -> Time: """ Time of sunset. @@ -1654,7 +1654,7 @@ def twilight_morning_civil(self, time: Time, which: str = 'nearest', n_grid_poin # Moon-related methods. - def moon_rise_time(self, time: Time, which: str = 'nearest', horizon: Quantity["angle"] = 0*u.deg, # noqa: F821 + def moon_rise_time(self, time: Time, which: str = 'nearest', horizon: Quantity = 0*u.deg, n_grid_points: int = 150) -> Time: """ Returns the local moon rise time. @@ -1688,7 +1688,7 @@ def moon_rise_time(self, time: Time, which: str = 'nearest', horizon: Quantity[" return self.target_rise_time(time, MoonFlag, which, horizon, n_grid_points=n_grid_points) - def moon_set_time(self, time: Time, which: str = 'nearest', horizon: Quantity["angle"] = 0*u.deg, # noqa: F821 + def moon_set_time(self, time: Time, which: str = 'nearest', horizon: Quantity = 0*u.deg, n_grid_points: int = 150) -> Time: """ Returns the local moon set time. @@ -1756,7 +1756,7 @@ def moon_illumination(self, time: Any): return moon_illumination(time) - def moon_phase(self, time: Optional[Any] = None) -> Quantity["angle"]: # noqa: F821 + def moon_phase(self, time: Optional[Any] = None) -> Quantity: """ Calculate lunar orbital phase. @@ -1865,7 +1865,7 @@ def sun_altaz(self, time: Any) -> SkyCoord: return self.altaz(time, sun) @u.quantity_input(horizon=u.deg) - def target_is_up(self, time: Any, target: TargetType, horizon: Quantity["angle"] = 0*u.degree, # noqa: F821 + def target_is_up(self, time: Any, target: TargetType, horizon: Quantity = 0*u.degree, return_altaz: bool = False, grid_times_targets: bool = False) -> Union[bool, np.ndarray[bool]]: """ Is ``target`` above ``horizon`` at this ``time``? @@ -1931,8 +1931,8 @@ def target_is_up(self, time: Any, target: TargetType, horizon: Quantity["angle"] return observable, altaz @u.quantity_input(horizon=u.deg) - def is_night(self, time: Any, horizon: Quantity["angle"] = 0*u.deg, # noqa: F821 - obswl: Optional[Quantity["length"]] = None) -> Union[bool, np.ndarray[bool]]: # noqa: F821 + def is_night(self, time: Any, horizon: Quantity = 0*u.deg, + obswl: Optional[Quantity] = None) -> Union[bool, np.ndarray[bool]]: """ Is the Sun below ``horizon`` at ``time``? @@ -2043,7 +2043,7 @@ def target_hour_angle(self, time: Any, target: TargetType, grid_times_targets: b return Longitude(self.local_sidereal_time(time) - target.ra) @u.quantity_input(horizon=u.degree) - def tonight(self, time: Optional[Time] = None, horizon: Quantity["angle"] = 0 * u.degree, obswl: Optional[Quantity["length"]] =None): # noqa: F821 + def tonight(self, time: Optional[Time] = None, horizon: Quantity = 0 * u.degree, obswl: Optional[Quantity] =None): """ Return a time range corresponding to the nearest night diff --git a/astroplan/periodic.py b/astroplan/periodic.py index 49809314..19807b2f 100644 --- a/astroplan/periodic.py +++ b/astroplan/periodic.py @@ -18,7 +18,7 @@ class PeriodicEvent(object): A periodic event defined by an epoch and period. """ @u.quantity_input(period=u.day, duration=u.day) - def __init__(self, epoch: Time, period: Quantity["time"], duration: Optional[Quantity["time"]] = None, # noqa: F821 + def __init__(self, epoch: Time, period: Quantity, duration: Optional[Quantity] = None, name: Optional[str] = None): """ @@ -72,8 +72,8 @@ class EclipsingSystem(PeriodicEvent): barycentric correction error (<=16 minutes). """ @u.quantity_input(period=u.day, duration=u.day) - def __init__(self, primary_eclipse_time: Time, orbital_period: Quantity["time"], # noqa: F821 - duration: Optional[Quantity["time"]] = None, name: Optional[str] = None, # noqa: F821 + def __init__(self, primary_eclipse_time: Time, orbital_period: Quantity, + duration: Optional[Quantity] = None, name: Optional[str] = None, eccentricity: Optional[float] = None, argument_of_periapsis: Optional[float] = None): """ Parameters diff --git a/astroplan/scheduling.py b/astroplan/scheduling.py index af46e913..ed2ab60c 100644 --- a/astroplan/scheduling.py +++ b/astroplan/scheduling.py @@ -7,7 +7,7 @@ import copy from abc import ABCMeta, abstractmethod -from typing import Optional, Sequence, Type, Union +from typing import Optional, Sequence, Union import numpy as np from astropy import units as u @@ -31,7 +31,7 @@ class ObservingBlock(object): constraints on observations. """ @u.quantity_input(duration=u.second) - def __init__(self, target: FixedTarget, duration: Quantity["time"], priority: Union[int, float], # noqa: F821 + def __init__(self, target: FixedTarget, duration: Quantity, priority: Union[int, float], configuration: dict = {}, constraints: Optional[list[Constraint]] =None, name: Optional[Union[str, int]] = None): """ @@ -91,8 +91,8 @@ def constraints_scores(self) -> Optional[dict[Constraint, np.ndarray]]: @classmethod def from_exposures(cls, target: FixedTarget, priority: Union[int, float], - time_per_exposure: Quantity["time"], number_exposures: int, # noqa: F821 - readout_time: Quantity["time"] = 0 * u.second, # noqa: F821 + time_per_exposure: Quantity, number_exposures: int, + readout_time: Quantity = 0 * u.second, configuration: dict = {}, constraints: Optional[list[Constraint]] = None) -> "ObservingBlock": duration = number_exposures * (time_per_exposure + readout_time) ob = cls(target, duration, priority, configuration, constraints) @@ -128,7 +128,7 @@ def __init__(self, blocks: list[ObservingBlock], observer: Observer, schedule: " self.global_constraints = global_constraints self.targets = get_skycoord([block.target for block in self.blocks]) - def create_score_array(self, time_resolution: Quantity["time"] = 1*u.minute) -> np.ndarray: # noqa: F821 + def create_score_array(self, time_resolution: Quantity = 1*u.minute) -> np.ndarray: """ this makes a score array over the entire schedule for all of the blocks and each `~astroplan.Constraint` in the .constraints of @@ -177,7 +177,7 @@ class TransitionBlock(object): telescope is slewing, instrument is reconfiguring, etc. """ - def __init__(self, components: dict[str, Quantity["time"]], start_time: Optional[Time] =None): # noqa: F821 + def __init__(self, components: dict[str, Quantity], start_time: Optional[Time] =None): """ Parameters ---------- @@ -208,11 +208,11 @@ def end_time(self) -> Time: return self.start_time + self.duration @property - def components(self) -> dict[str, Quantity["time"]]: # noqa: F821 + def components(self) -> dict[str, Quantity]: return self._components @components.setter - def components(self, val: dict[str, Quantity["time"]]) -> None: # noqa: F821 + def components(self, val: dict[str, Quantity]) -> None: duration = 0*u.second for t in val.values(): duration += t @@ -222,7 +222,7 @@ def components(self, val: dict[str, Quantity["time"]]) -> None: # noqa: F821 @classmethod @u.quantity_input(duration=u.second) - def from_duration(cls, duration: Quantity["time"]) -> "TransitionBlock": # noqa: F821 + def from_duration(cls, duration: Quantity) -> "TransitionBlock": # for testing how to put transitions between observations during # scheduling without considering the complexities of duration tb = TransitionBlock({'duration': duration}) @@ -496,8 +496,8 @@ class Scheduler(object): @u.quantity_input(gap_time=u.second, time_resolution=u.second) def __init__(self, constraints: Sequence[Constraint], observer: Observer, - transitioner: Optional["Transitioner"] = None, gap_time: Quantity["time"] = 5*u.min, # noqa: F821 - time_resolution: Quantity["time"] = 20*u.second): # noqa: F821 + transitioner: Optional["Transitioner"] = None, gap_time: Quantity = 5*u.min, + time_resolution: Quantity = 20*u.second): """ Parameters ---------- @@ -584,7 +584,7 @@ def _make_schedule(self, blocks: list[ObservingBlock]): @classmethod @u.quantity_input(duration=u.second) - def from_timespan(cls, center_time: Time, duration: Union[Quantity["time"], TimeDelta], **kwargs) -> "Scheduler": # noqa: F821 + def from_timespan(cls, center_time: Time, duration: Union[Quantity, TimeDelta], **kwargs) -> "Scheduler": """ Create a new instance of this class given a center time and duration. @@ -963,7 +963,7 @@ class Transitioner(object): u.quantity_input(slew_rate=u.deg/u.second) def __init__(self, slew_rate: Optional[Quantity["angular frequency"]] = None, # noqa: F722 - instrument_reconfig_times: Optional[dict[str, dict[tuple[str, str], Quantity["time"]]]] = None): # noqa: F722 + instrument_reconfig_times: Optional[dict[str, dict[tuple[str, str], Quantity]]] = None): # noqa: F722 """ Parameters ---------- @@ -1024,7 +1024,7 @@ def __call__(self, oldblock: ObservingBlock, newblock: ObservingBlock, start_tim return None def compute_instrument_transitions(self, oldblock: ObservingBlock, - newblock: ObservingBlock) -> dict[str, Quantity["time"]]: # noqa: F821 + newblock: ObservingBlock) -> dict[str, Quantity]: components = {} for conf_name, old_conf in oldblock.configuration.items(): if conf_name in newblock.configuration: diff --git a/astroplan/target.py b/astroplan/target.py index 31ff709a..1719780d 100644 --- a/astroplan/target.py +++ b/astroplan/target.py @@ -31,8 +31,8 @@ class Target(object): """ __metaclass__ = ABCMeta - def __init__(self, name: Optional[str] = None, ra: Optional[Quantity["angle"]] = None, # noqa: F821 - dec: Optional[Quantity["angle"]] = None, marker: Optional[str] = None): # noqa: F821 + def __init__(self, name: Optional[str] = None, ra: Optional[Quantity] = None, + dec: Optional[Quantity] = None, marker: Optional[str] = None): """ Defines a single observation target. @@ -51,7 +51,7 @@ def __init__(self, name: Optional[str] = None, ra: Optional[Quantity["angle"]] = raise NotImplementedError() @property - def ra(self) -> Quantity["angle"]: # noqa: F821 + def ra(self) -> Quantity: """ Right ascension. """ @@ -60,7 +60,7 @@ def ra(self) -> Quantity["angle"]: # noqa: F821 raise NotImplementedError() @property - def dec(self) -> Quantity["angle"]: # noqa: F821 + def dec(self) -> Quantity: """ Declination. """ diff --git a/astroplan/utils.py b/astroplan/utils.py index 3bb15320..db57ebc4 100644 --- a/astroplan/utils.py +++ b/astroplan/utils.py @@ -80,7 +80,7 @@ def download_IERS_A(show_progres: bool = True) -> None: @u.quantity_input(time_resolution=u.hour) -def time_grid_from_range(time_range: Time, time_resolution: Quantity["time"] = 0.5*u.hour) -> Time: # noqa: F821 +def time_grid_from_range(time_range: Time, time_resolution: Quantity = 0.5*u.hour) -> Time: """ Get linearly-spaced sequence of times. From 63a7b13e783a8a3715ddfa336c7665f673d85e77 Mon Sep 17 00:00:00 2001 From: Thomas Vandal Date: Sun, 15 Sep 2024 22:29:54 -0300 Subject: [PATCH 06/11] Use `typing.Self` to annotate classmethods return types https://docs.python.org/3/library/typing.html#typing.Self --- astroplan/constraints.py | 14 +++++++------- astroplan/observer.py | 4 ++-- astroplan/scheduling.py | 8 ++++---- astroplan/target.py | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index 6ab07f5d..1339d967 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -9,7 +9,7 @@ # Standard library from abc import ABCMeta, abstractmethod -from typing import Optional, Sequence, Union +from typing import Optional, Self, Sequence, Union import datetime import time import warnings @@ -444,21 +444,21 @@ def __init__(self, max_solar_altitude: Quantity = 0*u.deg, force_pressure_zero: self.force_pressure_zero = force_pressure_zero @classmethod - def twilight_civil(cls, **kwargs) -> "AtNightConstraint": + def twilight_civil(cls, **kwargs) -> Self: """ Consider nighttime as time between civil twilights (-6 degrees). """ return cls(max_solar_altitude=-6*u.deg, **kwargs) @classmethod - def twilight_nautical(cls, **kwargs) -> "AtNightConstraint": + def twilight_nautical(cls, **kwargs) -> Self: """ Consider nighttime as time between nautical twilights (-12 degrees). """ return cls(max_solar_altitude=-12*u.deg, **kwargs) @classmethod - def twilight_astronomical(cls, **kwargs) -> "AtNightConstraint": + def twilight_astronomical(cls, **kwargs) -> Self: """ Consider nighttime as time between astronomical twilights (-18 degrees). """ @@ -645,7 +645,7 @@ def __init__(self, min: Optional[float] = None, max: Optional[float] = None, eph self.ephemeris = ephemeris @classmethod - def dark(cls, min: Optional[float] = None, max: Optional[float] = 0.25, **kwargs) -> "MoonIlluminationConstraint": + def dark(cls, min: Optional[float] = None, max: Optional[float] = 0.25, **kwargs) -> Self: """ initialize a `~astroplan.constraints.MoonIlluminationConstraint` with defaults of no minimum and a maximum of 0.25 @@ -662,7 +662,7 @@ def dark(cls, min: Optional[float] = None, max: Optional[float] = 0.25, **kwargs return cls(min, max, **kwargs) @classmethod - def grey(cls, min: Optional[float] = 0.25, max: Optional[float] = 0.65, **kwargs) -> "MoonIlluminationConstraint": + def grey(cls, min: Optional[float] = 0.25, max: Optional[float] = 0.65, **kwargs) -> Self: """ initialize a `~astroplan.constraints.MoonIlluminationConstraint` with defaults of a minimum of 0.25 and a maximum of 0.65 @@ -679,7 +679,7 @@ def grey(cls, min: Optional[float] = 0.25, max: Optional[float] = 0.65, **kwargs return cls(min, max, **kwargs) @classmethod - def bright(cls, min: Optional[float] = 0.65, max: Optional[float] = None, **kwargs) -> "MoonIlluminationConstraint": + def bright(cls, min: Optional[float] = 0.65, max: Optional[float] = None, **kwargs) -> Self: """ initialize a `~astroplan.constraints.MoonIlluminationConstraint` with defaults of a minimum of 0.65 and no maximum diff --git a/astroplan/observer.py b/astroplan/observer.py index 0e2ee3ff..77a2474c 100644 --- a/astroplan/observer.py +++ b/astroplan/observer.py @@ -5,7 +5,7 @@ # Standard library import sys -from typing import Any, Callable, Optional, Sequence, Union +from typing import Any, Callable, Optional, Self, Sequence, Union import datetime import warnings @@ -359,7 +359,7 @@ def __ne__(self, other: "Observer") -> bool: return not self.__eq__(other) @classmethod - def at_site(cls, site_name: str, **kwargs) -> "Observer": + def at_site(cls, site_name: str, **kwargs) -> Self: """ Initialize an `~astroplan.observer.Observer` object with a site name. diff --git a/astroplan/scheduling.py b/astroplan/scheduling.py index ed2ab60c..efd76c1a 100644 --- a/astroplan/scheduling.py +++ b/astroplan/scheduling.py @@ -7,7 +7,7 @@ import copy from abc import ABCMeta, abstractmethod -from typing import Optional, Sequence, Union +from typing import Optional, Self, Sequence, Union import numpy as np from astropy import units as u @@ -93,7 +93,7 @@ def constraints_scores(self) -> Optional[dict[Constraint, np.ndarray]]: def from_exposures(cls, target: FixedTarget, priority: Union[int, float], time_per_exposure: Quantity, number_exposures: int, readout_time: Quantity = 0 * u.second, - configuration: dict = {}, constraints: Optional[list[Constraint]] = None) -> "ObservingBlock": + configuration: dict = {}, constraints: Optional[list[Constraint]] = None) -> Self: duration = number_exposures * (time_per_exposure + readout_time) ob = cls(target, duration, priority, configuration, constraints) ob.time_per_exposure = time_per_exposure @@ -222,7 +222,7 @@ def components(self, val: dict[str, Quantity]) -> None: @classmethod @u.quantity_input(duration=u.second) - def from_duration(cls, duration: Quantity) -> "TransitionBlock": + def from_duration(cls, duration: Quantity) -> Self: # for testing how to put transitions between observations during # scheduling without considering the complexities of duration tb = TransitionBlock({'duration': duration}) @@ -584,7 +584,7 @@ def _make_schedule(self, blocks: list[ObservingBlock]): @classmethod @u.quantity_input(duration=u.second) - def from_timespan(cls, center_time: Time, duration: Union[Quantity, TimeDelta], **kwargs) -> "Scheduler": + def from_timespan(cls, center_time: Time, duration: Union[Quantity, TimeDelta], **kwargs) -> Self: """ Create a new instance of this class given a center time and duration. diff --git a/astroplan/target.py b/astroplan/target.py index 1719780d..3144b347 100644 --- a/astroplan/target.py +++ b/astroplan/target.py @@ -4,7 +4,7 @@ # Standard library from abc import ABCMeta -from typing import Optional, Union +from typing import Optional, Self, Union # Third-party import astropy.units as u @@ -110,7 +110,7 @@ def __init__(self, coord: SkyCoord, name: Optional[str] = None, **kwargs): self.coord = coord @classmethod - def from_name(cls, query_name: str, name: Optional[str] = None, **kwargs) -> "FixedTarget": + def from_name(cls, query_name: str, name: Optional[str] = None, **kwargs) -> Self: """ Initialize a `FixedTarget` by querying for a name from the CDS name resolver, using the machinery in @@ -161,7 +161,7 @@ def __repr__(self) -> str: return '<{} "{}" at {}>'.format(class_name, self.name, fmt_coord) @classmethod - def _from_name_mock(cls, query_name: str, name: Optional[str] = None) -> "FixedTarget": + def _from_name_mock(cls, query_name: str, name: Optional[str] = None) -> Self: """ Mock method to replace `FixedTarget.from_name` in tests without internet connection. From 0f3a7b49e9e0746126a44fc7b144809fed5b8409 Mon Sep 17 00:00:00 2001 From: Thomas Vandal Date: Mon, 16 Sep 2024 00:51:00 -0300 Subject: [PATCH 07/11] Fix line lengths --- astroplan/constraints.py | 84 ++++++++++++++++++++++++++-------------- astroplan/observer.py | 63 +++++++++++++++++++----------- astroplan/periodic.py | 3 +- astroplan/scheduling.py | 26 ++++++++----- astroplan/utils.py | 3 +- 5 files changed, 115 insertions(+), 64 deletions(-) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index 1339d967..0a6f9c31 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -139,7 +139,8 @@ def _get_altaz(times: Time, observer: Observer, targets: Union[list[FixedTarget] return observer._altaz_cache[aakey] -def _get_moon_data(times: Time, observer: Observer, force_zero_pressure: bool = False) -> dict[str, Union[Time, AltAz, np.ndarray]]: +def _get_moon_data(times: Time, observer: Observer, + force_zero_pressure: bool = False) -> dict[str, Union[Time, AltAz, np.ndarray]]: """ Calculate moon altitude az and illumination for an array of times for ``observer``. @@ -187,7 +188,8 @@ def _get_moon_data(times: Time, observer: Observer, force_zero_pressure: bool = return observer._moon_cache[aakey] -def _get_meridian_transit_times(times: Time, observer: Observer, targets: Union[list[FixedTarget], SkyCoord]) -> dict[str, Time]: +def _get_meridian_transit_times(times: Time, observer: Observer, targets: Union[list[FixedTarget], + SkyCoord]) -> dict[str, Time]: """ Calculate next meridian transit for an array of times for ``targets`` and ``observer``. @@ -229,8 +231,9 @@ class Constraint(object): """ __metaclass__ = ABCMeta - def __call__(self, observer: Observer, targets: Sequence[FixedTarget], times: Optional[Time] = None, - time_range: Optional[Time] = None, time_grid_resolution: Quantity = 0.5*u.hour, + def __call__(self, observer: Observer, targets: Sequence[FixedTarget], + times: Optional[Time] = None, time_range: Optional[Time] = None, + time_grid_resolution: Quantity = 0.5*u.hour, grid_times_targets: bool = False) -> np.ndarray[Union[float, bool]]: """ Compute the constraint for this class @@ -295,7 +298,8 @@ def __call__(self, observer: Observer, targets: Sequence[FixedTarget], times: Op return result @abstractmethod - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[Union[float, bool]]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[Union[float, bool]]: """ Actually do the real work of computing the constraint. Subclasses override this. @@ -340,7 +344,8 @@ class AltitudeConstraint(Constraint): float on [0, 1], where 0 is the min altitude and 1 is the max. """ - def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = None, boolean_constraint: bool = True): + def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = None, + boolean_constraint: bool = True): if min is None: self.min = -90*u.deg else: @@ -352,7 +357,8 @@ def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = Non self.boolean_constraint = boolean_constraint - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[Union[float, bool]]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[Union[float, bool]]: cached_altaz = _get_altaz(times, observer, targets) alt = cached_altaz['altaz'].alt if self.boolean_constraint: @@ -392,12 +398,14 @@ class AirmassConstraint(AltitudeConstraint): AirmassConstraint(2) """ - def __init__(self, max: Optional[float] = None, min: Optional[float] = 1.0, boolean_constraint: bool = True): + def __init__(self, max: Optional[float] = None, min: Optional[float] = 1.0, + boolean_constraint: bool = True): self.min = min self.max = max self.boolean_constraint = boolean_constraint - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[Union[float, bool]]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[Union[float, bool]]: cached_altaz = _get_altaz(times, observer, targets) secz = cached_altaz['altaz'].secz.value if self.boolean_constraint: @@ -464,7 +472,8 @@ def twilight_astronomical(cls, **kwargs) -> Self: """ return cls(max_solar_altitude=-18*u.deg, **kwargs) - def _get_solar_altitudes(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> Quantity: + def _get_solar_altitudes(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> Quantity: if not hasattr(observer, '_altaz_cache'): observer._altaz_cache = {} @@ -490,7 +499,8 @@ def _get_solar_altitudes(self, times: Time, observer: Observer, targets: Sequenc return altitude - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[bool]: solar_altitude = self._get_solar_altitudes(times, observer, targets) mask = solar_altitude <= self.max_solar_altitude return mask @@ -515,7 +525,8 @@ def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = Non self.min = min self.max = max - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[bool]: separation = abs(targets.transform_to(Galactic).b) if self.min is None and self.max is not None: @@ -549,7 +560,8 @@ def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = Non self.min = min self.max = max - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[bool]: # use get_body rather than get sun here, since # it returns the Sun's coordinates in an observer # centred frame, so the separation is as-seen @@ -577,7 +589,8 @@ class MoonSeparationConstraint(Constraint): Constrain the distance between the Earth's moon and some targets. """ - def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = None, ephemeris: Optional[str] = None): + def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = None, + ephemeris: Optional[str] = None): """ Parameters ---------- @@ -596,7 +609,8 @@ def __init__(self, min: Optional[Quantity] = None, max: Optional[Quantity] = Non self.max = max self.ephemeris = ephemeris - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[bool]: moon = get_body("moon", times, location=observer.location, ephemeris=self.ephemeris) # note to future editors - the order matters here # moon.separation(targets) is NOT the same as targets.separation(moon) @@ -625,7 +639,8 @@ class MoonIlluminationConstraint(Constraint): Constraint is also satisfied if the Moon has set. """ - def __init__(self, min: Optional[float] = None, max: Optional[float] = None, ephemeris: Optional[str] = None): + def __init__(self, min: Optional[float] = None, max: Optional[float] = None, + ephemeris: Optional[str] = None): """ Parameters ---------- @@ -695,7 +710,8 @@ def bright(cls, min: Optional[float] = 0.65, max: Optional[float] = None, **kwar """ return cls(min, max, **kwargs) - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[bool]: # first is the moon up? cached_moon = _get_moon_data(times, observer) moon_alt = cached_moon['altaz'].alt @@ -759,7 +775,8 @@ def __init__(self, min: Optional[datetime.time] = None, max: Optional[datetime.t if not isinstance(self.max, datetime.time): raise TypeError("Time limits must be specified as datetime.time objects.") - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[bool]: timezone = None @@ -849,7 +866,8 @@ def __init__(self, min: Optional[Time] = None, max: Optional[Time] = None): raise TypeError("Time limits must be specified as " "astropy.time.Time objects.") - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[bool]: with warnings.catch_warnings(): warnings.simplefilter('ignore') min_time = Time("1950-01-01T00:00:00") if self.min is None else self.min @@ -891,7 +909,8 @@ def __init__(self, eclipsing_system: EclipsingSystem): """ self.eclipsing_system = eclipsing_system - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[bool]: mask = self.eclipsing_system.in_secondary_eclipse(times) return mask @@ -902,7 +921,8 @@ class PhaseConstraint(Constraint): (e.g.~transiting exoplanets, eclipsing binaries). """ - def __init__(self, periodic_event: PeriodicEvent, min: Optional[float] = None, max: Optional[float] = None): + def __init__(self, periodic_event: PeriodicEvent, min: Optional[float] = None, + max: Optional[float] = None): """ Parameters ---------- @@ -935,7 +955,8 @@ def __init__(self, periodic_event: PeriodicEvent, min: Optional[float] = None, m self.min = min if min is not None else 0.0 self.max = max if max is not None else 1.0 - def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[FixedTarget]) -> np.ndarray[bool]: + def compute_constraint(self, times: Time, observer: Observer, + targets: Sequence[FixedTarget]) -> np.ndarray[bool]: phase = self.periodic_event.phase(times) mask = np.where(self.max > self.min, @@ -946,7 +967,8 @@ def compute_constraint(self, times: Time, observer: Observer, targets: Sequence[ def is_always_observable(constraints: Union[list[Constraint], Constraint], observer: Observer, targets: Union[list[FixedTarget], SkyCoord], times: Optional[Time] = None, - time_range: Optional[Time] = None, time_grid_resolution: Quantity = 0.5*u.hour) -> list[bool]: + time_range: Optional[Time] = None, + time_grid_resolution: Quantity = 0.5*u.hour) -> list[bool]: """ A function to determine whether ``targets`` are always observable throughout ``time_range`` given constraints in the ``constraints_list`` for a @@ -997,7 +1019,8 @@ def is_always_observable(constraints: Union[list[Constraint], Constraint], obser def is_observable(constraints: Union[list[Constraint], Constraint], observer: Observer, targets: Union[list[FixedTarget], SkyCoord], times: Optional[Time] = None, - time_range: Optional[Time] = None, time_grid_resolution: Quantity = 0.5*u.hour) -> list[bool]: + time_range: Optional[Time] = None, + time_grid_resolution: Quantity = 0.5*u.hour) -> list[bool]: """ Determines if the ``targets`` are observable during ``time_range`` given constraints in ``constraints_list`` for a particular ``observer``. @@ -1101,8 +1124,9 @@ def is_event_observable(constraints: Union[list[Constraint], Constraint], def months_observable(constraints: Union[list[Constraint], Constraint], observer: Observer, - targets: Union[list[FixedTarget], SkyCoord], time_range: Time = _current_year_time_range, - time_grid_resolution: Quantity = 0.5*u.hour) -> list[set[int]]: + targets: Union[list[FixedTarget], SkyCoord], + time_range: Time = _current_year_time_range, + time_grid_resolution: Quantity = 0.5*u.hour) -> list[set[int]]: """ Determines which month the specified ``targets`` are observable for a specific ``observer``, given the supplied ``constraints``. @@ -1170,8 +1194,9 @@ def months_observable(constraints: Union[list[Constraint], Constraint], observer def observability_table(constraints: Union[list[Constraint], Constraint], observer: Observer, - targets: Union[list[FixedTarget], SkyCoord], times: Optional[Time] = None, - time_range: Optional[Time] = None, time_grid_resolution: Quantity = 0.5*u.hour) -> table.Table: + targets: Union[list[FixedTarget], SkyCoord], times: Optional[Time] = None, + time_range: Optional[Time] = None, + time_grid_resolution: Quantity = 0.5*u.hour) -> table.Table: """ Creates a table with information about observability for all the ``targets`` over the requested ``time_range``, given the constraints in @@ -1300,7 +1325,8 @@ def min_best_rescale(vals: ArrayLike, min_val: float, max_val: float, less_than_ return rescaled -def max_best_rescale(vals: ArrayLike, min_val: float, max_val: float, greater_than_max: int = 1) -> ArrayLike: +def max_best_rescale(vals: ArrayLike, min_val: float, max_val: float, + greater_than_max: int = 1) -> ArrayLike: """ rescales an input array ``vals`` to be a score (between zero and one), where the ``max_val`` goes to one, and the ``min_val`` goes to zero. diff --git a/astroplan/observer.py b/astroplan/observer.py index 77a2474c..347d4d7c 100644 --- a/astroplan/observer.py +++ b/astroplan/observer.py @@ -65,7 +65,8 @@ def _process_nans_in_jds(jds: Union[float, int, np.ndarray, Quantity, Time]) -> return masked_jds -def _generate_24hr_grid(t0: Time, start: float, end: float, n_grid_points: int, for_deriv: bool = False) -> Time: +def _generate_24hr_grid(t0: Time, start: float, end: float, n_grid_points: int, + for_deriv: bool = False) -> Time: """ Generate a nearly linearly spaced grid of time durations. @@ -152,11 +153,13 @@ class Observer(object): """ @u.quantity_input(elevation=u.m) - def __init__(self, location: Optional[EarthLocation] = None, timezone: Union[str, datetime.tzinfo] = 'UTC', - name: Optional[str] = None, latitude: Optional[Union[float, str, Quantity]] = None, - longitude: Optional[Union[float, str, Quantity]] = None, elevation: Quantity = 0*u.m, - pressure: Optional[Quantity] =None, relative_humidity: Optional[float] = None, - temperature: Quantity = None, description: Optional[str] = None): + def __init__(self, location: Optional[EarthLocation] = None, + timezone: Union[str, datetime.tzinfo] = 'UTC', name: Optional[str] = None, + latitude: Optional[Union[float, str, Quantity]] = None, + longitude: Optional[Union[float, str, Quantity]] = None, + elevation: Quantity = 0*u.m, pressure: Optional[Quantity] =None, + relative_humidity: Optional[float] = None, temperature: Quantity = None, + description: Optional[str] = None): """ Parameters ---------- @@ -323,7 +326,7 @@ def __hash__(self) -> int: return hash(self._key()) - def __eq__(self, other: "Observer") -> bool: + def __eq__(self, other: Self) -> bool: """ Equality check for `~astroplan.Observer` objects. @@ -342,7 +345,7 @@ def __eq__(self, other: "Observer") -> bool: else: return NotImplemented - def __ne__(self, other: "Observer") -> bool: + def __ne__(self, other: Self) -> bool: """ Inequality check for `~astroplan.Observer` objects. @@ -491,7 +494,8 @@ def _is_broadcastable(self, shp1: tuple[int], shp2: tuple[int]) -> bool: return False return True - def _preprocess_inputs(self, time: Any, target: Optional[TargetType] = None, grid_times_targets: bool = False) -> tuple[Time, SkyCoord]: + def _preprocess_inputs(self, time: Any, target: Optional[TargetType] = None, + grid_times_targets: bool = False) -> tuple[Time, SkyCoord]: """ Preprocess time and target inputs @@ -538,7 +542,8 @@ def _preprocess_inputs(self, time: Any, target: Optional[TargetType] = None, gri return time, target def altaz(self, time: Time, target: Optional[TargetType] = None, - obswl: Optional[Quantity] = None, grid_times_targets: bool = False) -> Union[AltAz, SkyCoord]: + obswl: Optional[Quantity] = None, + grid_times_targets: bool = False) -> Union[AltAz, SkyCoord]: """ Get an `~astropy.coordinates.AltAz` frame or coordinate. @@ -616,7 +621,8 @@ def altaz(self, time: Time, target: Optional[TargetType] = None, return target.transform_to(altaz_frame) def parallactic_angle(self, time: Time, target: Union[TargetType], - grid_times_targets: bool = False, kind: str = 'mean', model: Optional[str] = None) -> Angle: + grid_times_targets: bool = False, kind: str = 'mean', + model: Optional[str] = None) -> Angle: """ Calculate the parallactic angle. @@ -1194,7 +1200,8 @@ def target_set_time(self, time: Time, target: SkyCoord, which: str = 'nearest', grid_times_targets=grid_times_targets)) def target_meridian_transit_time(self, time: Time, target: TargetType, which: str = 'nearest', - grid_times_targets: bool = False, n_grid_points: int = 150) -> Time: + grid_times_targets: bool = False, + n_grid_points: int = 150) -> Time: """ Calculate time at the transit of the meridian. @@ -1253,8 +1260,9 @@ def target_meridian_transit_time(self, time: Time, target: TargetType, which: st rise_set='setting', grid_times_targets=grid_times_targets)) - def target_meridian_antitransit_time(self, time: Time, target: TargetType, which: str = 'nearest', - grid_times_targets: bool = False, n_grid_points: int = 150) -> Time: + def target_meridian_antitransit_time(self, time: Time, target: TargetType, + which: str = 'nearest', grid_times_targets: bool = False, + n_grid_points: int = 150) -> Time: """ Calculate time at the antitransit of the meridian. @@ -1478,7 +1486,8 @@ def midnight(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) # Twilight convenience functions - def twilight_evening_astronomical(self, time: Time, which: str = 'nearest', n_grid_points: int =150) -> Time: + def twilight_evening_astronomical(self, time: Time, which: str = 'nearest', + n_grid_points: int =150) -> Time: """ Time at evening astronomical (-18 degree) twilight. @@ -1507,7 +1516,8 @@ def twilight_evening_astronomical(self, time: Time, which: str = 'nearest', n_gr return self.sun_set_time(time, which, horizon=-18*u.degree, n_grid_points=n_grid_points) - def twilight_evening_nautical(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) -> Time: + def twilight_evening_nautical(self, time: Time, which: str = 'nearest', + n_grid_points: int = 150) -> Time: """ Time at evening nautical (-12 degree) twilight. @@ -1536,7 +1546,8 @@ def twilight_evening_nautical(self, time: Time, which: str = 'nearest', n_grid_p return self.sun_set_time(time, which, horizon=-12*u.degree, n_grid_points=n_grid_points) - def twilight_evening_civil(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) -> Time: + def twilight_evening_civil(self, time: Time, which: str = 'nearest', + n_grid_points: int = 150) -> Time: """ Time at evening civil (-6 degree) twilight. @@ -1565,7 +1576,8 @@ def twilight_evening_civil(self, time: Time, which: str = 'nearest', n_grid_poin return self.sun_set_time(time, which, horizon=-6*u.degree, n_grid_points=n_grid_points) - def twilight_morning_astronomical(self, time: Time, which: str = 'nearest', n_grid_points: int =150) -> Time: + def twilight_morning_astronomical(self, time: Time, which: str = 'nearest', + n_grid_points: int =150) -> Time: """ Time at morning astronomical (-18 degree) twilight. @@ -1594,7 +1606,8 @@ def twilight_morning_astronomical(self, time: Time, which: str = 'nearest', n_gr return self.sun_rise_time(time, which, horizon=-18*u.degree, n_grid_points=n_grid_points) - def twilight_morning_nautical(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) -> Time: + def twilight_morning_nautical(self, time: Time, which: str = 'nearest', + n_grid_points: int = 150) -> Time: """ Time at morning nautical (-12 degree) twilight. @@ -1623,7 +1636,8 @@ def twilight_morning_nautical(self, time: Time, which: str = 'nearest', n_grid_p return self.sun_rise_time(time, which, horizon=-12*u.degree, n_grid_points=n_grid_points) - def twilight_morning_civil(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) -> Time: + def twilight_morning_civil(self, time: Time, which: str = 'nearest', + n_grid_points: int = 150) -> Time: """ Time at morning civil (-6 degree) twilight. @@ -1866,7 +1880,8 @@ def sun_altaz(self, time: Any) -> SkyCoord: @u.quantity_input(horizon=u.deg) def target_is_up(self, time: Any, target: TargetType, horizon: Quantity = 0*u.degree, - return_altaz: bool = False, grid_times_targets: bool = False) -> Union[bool, np.ndarray[bool]]: + return_altaz: bool = False, + grid_times_targets: bool = False) -> Union[bool, np.ndarray[bool]]: """ Is ``target`` above ``horizon`` at this ``time``? @@ -2013,7 +2028,8 @@ def local_sidereal_time(self, time: Any, kind: str = 'apparent', model: Optional return time.sidereal_time(kind, longitude=self.location.lon, model=model) - def target_hour_angle(self, time: Any, target: TargetType, grid_times_targets: bool = False) -> Angle: + def target_hour_angle(self, time: Any, target: TargetType, + grid_times_targets: bool = False) -> Angle: """ Calculate the local hour angle of ``target`` at ``time``. @@ -2043,7 +2059,8 @@ def target_hour_angle(self, time: Any, target: TargetType, grid_times_targets: b return Longitude(self.local_sidereal_time(time) - target.ra) @u.quantity_input(horizon=u.degree) - def tonight(self, time: Optional[Time] = None, horizon: Quantity = 0 * u.degree, obswl: Optional[Quantity] =None): + def tonight(self, time: Optional[Time] = None, horizon: Quantity = 0 * u.degree, + obswl: Optional[Quantity] =None) -> Time: """ Return a time range corresponding to the nearest night diff --git a/astroplan/periodic.py b/astroplan/periodic.py index 19807b2f..6a537654 100644 --- a/astroplan/periodic.py +++ b/astroplan/periodic.py @@ -74,7 +74,8 @@ class EclipsingSystem(PeriodicEvent): @u.quantity_input(period=u.day, duration=u.day) def __init__(self, primary_eclipse_time: Time, orbital_period: Quantity, duration: Optional[Quantity] = None, name: Optional[str] = None, - eccentricity: Optional[float] = None, argument_of_periapsis: Optional[float] = None): + eccentricity: Optional[float] = None, + argument_of_periapsis: Optional[float] = None): """ Parameters ---------- diff --git a/astroplan/scheduling.py b/astroplan/scheduling.py index efd76c1a..06f75902 100644 --- a/astroplan/scheduling.py +++ b/astroplan/scheduling.py @@ -93,7 +93,8 @@ def constraints_scores(self) -> Optional[dict[Constraint, np.ndarray]]: def from_exposures(cls, target: FixedTarget, priority: Union[int, float], time_per_exposure: Quantity, number_exposures: int, readout_time: Quantity = 0 * u.second, - configuration: dict = {}, constraints: Optional[list[Constraint]] = None) -> Self: + configuration: dict = {}, + constraints: Optional[list[Constraint]] = None) -> Self: duration = number_exposures * (time_per_exposure + readout_time) ob = cls(target, duration, priority, configuration, constraints) ob.time_per_exposure = time_per_exposure @@ -161,8 +162,8 @@ def create_score_array(self, time_resolution: Quantity = 1*u.minute) -> np.ndarr return score_array @classmethod - def from_start_end(cls, blocks: ObservingBlock, observer: Observer, start_time: Time, end_time: Time, - global_constraints=[]): + def from_start_end(cls, blocks: ObservingBlock, observer: Observer, start_time: Time, + end_time: Time, global_constraints=[]) -> Self: """ for if you don't have a schedule/ aren't inside a scheduler """ @@ -238,7 +239,8 @@ class Schedule(object): # this should change to allow for more flexibility (e.g. dark slots, grey slots) # TODO: Remove unused constraints arg? - def __init__(self, start_time: Time, end_time: Time, constraints: Optional[Sequence[Constraint]] = None): + def __init__(self, start_time: Time, end_time: Time, + constraints: Optional[Sequence[Constraint]] = None): """ Parameters ---------- @@ -397,7 +399,8 @@ def insert_slot(self, start_time: Time, block: ObservingBlock) -> list["Slot"]: self.slots = earlier_slots + new_slots + later_slots return earlier_slots + new_slots + later_slots - def change_slot_block(self, slot_index: int, new_block: Optional[TransitionBlock] = None) -> int: + def change_slot_block(self, slot_index: int, + new_block: Optional[TransitionBlock] = None) -> int: """ Change the block associated with a slot. @@ -584,7 +587,8 @@ def _make_schedule(self, blocks: list[ObservingBlock]): @classmethod @u.quantity_input(duration=u.second) - def from_timespan(cls, center_time: Time, duration: Union[Quantity, TimeDelta], **kwargs) -> Self: + def from_timespan(cls, center_time: Time, duration: Union[Quantity, TimeDelta], + **kwargs) -> Self: """ Create a new instance of this class given a center time and duration. @@ -815,7 +819,8 @@ def _make_schedule(self, blocks: list[ObservingBlock]) -> Schedule: return self.schedule - def attempt_insert_block(self, b: ObservingBlock, new_start_time: Time, start_time_idx: int) -> bool: + def attempt_insert_block(self, b: ObservingBlock, new_start_time: Time, + start_time_idx: int) -> bool: # set duration to be exact multiple of time resolution duration_indices = int(np.floor( float(b.duration / self.time_resolution))) @@ -962,8 +967,8 @@ class Transitioner(object): """ u.quantity_input(slew_rate=u.deg/u.second) - def __init__(self, slew_rate: Optional[Quantity["angular frequency"]] = None, # noqa: F722 - instrument_reconfig_times: Optional[dict[str, dict[tuple[str, str], Quantity]]] = None): # noqa: F722 + def __init__(self, slew_rate: Optional[Quantity] = None, + instrument_reconfig_times: Optional[dict[str, dict[tuple[str, str], Quantity]]] = None): """ Parameters ---------- @@ -979,7 +984,8 @@ def __init__(self, slew_rate: Optional[Quantity["angular frequency"]] = None, # self.slew_rate = slew_rate self.instrument_reconfig_times = instrument_reconfig_times - def __call__(self, oldblock: ObservingBlock, newblock: ObservingBlock, start_time: Time, observer: Observer) -> Optional[TransitionBlock]: + def __call__(self, oldblock: ObservingBlock, newblock: ObservingBlock, start_time: Time, + observer: Observer) -> Optional[TransitionBlock]: """ Determines the amount of time needed to transition from one observing block to another. This uses the parameters defined in diff --git a/astroplan/utils.py b/astroplan/utils.py index db57ebc4..fe49f784 100644 --- a/astroplan/utils.py +++ b/astroplan/utils.py @@ -34,7 +34,8 @@ BACKUP_Time_get_delta_ut1_utc = Time._get_delta_ut1_utc -def _low_precision_utc_to_ut1(self, jd1: np.ndarray[float], jd2: np.ndarray[float]) -> np.ndarray[float]: +def _low_precision_utc_to_ut1(self, jd1: np.ndarray[float], + jd2: np.ndarray[float]) -> np.ndarray[float]: """ When no IERS Bulletin A is available (no internet connection), use low precision time conversion by assuming UT1-UTC=0 always. From 04b1804fa1e724de5926a7124670f45aa846f347 Mon Sep 17 00:00:00 2001 From: Thomas Vandal Date: Mon, 16 Sep 2024 02:17:58 -0300 Subject: [PATCH 08/11] Add type hints to plotting modules --- astroplan/plots/finder.py | 19 +++++++----- astroplan/plots/sky.py | 25 ++++++++++------ astroplan/plots/time_dependent.py | 48 ++++++++++++++++++------------- 3 files changed, 57 insertions(+), 35 deletions(-) diff --git a/astroplan/plots/finder.py b/astroplan/plots/finder.py index 14a86cb8..be099242 100644 --- a/astroplan/plots/finder.py +++ b/astroplan/plots/finder.py @@ -1,21 +1,26 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) +from __future__ import absolute_import, division, print_function, unicode_literals -import numpy as np +from typing import Optional, Union import astropy.units as u - +from matplotlib.axes import Axes +import numpy as np from astropy.coordinates import SkyCoord from astropy.wcs import WCS +from astropy.units import Quantity + +from ..target import FixedTarget __all__ = ['plot_finder_image'] @u.quantity_input(fov_radius=u.deg) -def plot_finder_image(target, survey='DSS', fov_radius=10*u.arcmin, - log=False, ax=None, grid=False, reticle=False, - style_kwargs=None, reticle_style_kwargs=None): +def plot_finder_image(target: Union[FixedTarget, SkyCoord], survey: str = 'DSS', + fov_radius: Quantity = 10*u.arcmin, log: bool = False, + ax: Optional[Axes] = None, grid: bool = False, reticle: bool = False, + style_kwargs: Optional[dict] = None, + reticle_style_kwargs: Optional[dict] = None) -> Axes: """ Plot survey image centered on ``target``. diff --git a/astroplan/plots/sky.py b/astroplan/plots/sky.py index 8c0d3fa3..8432fc34 100644 --- a/astroplan/plots/sky.py +++ b/astroplan/plots/sky.py @@ -1,19 +1,26 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -import numpy as np +import warnings +from typing import Optional + import astropy.units as u +import numpy as np from astropy.time import Time -import warnings +from astropy.units import Quantity +from matplotlib.axes import Axes from ..exceptions import PlotBelowHorizonWarning +from ..observer import Observer +from ..target import FixedTarget from ..utils import _set_mpl_style_sheet __all__ = ['plot_sky', 'plot_sky_24hr'] @u.quantity_input(az_label_offset=u.deg) -def plot_sky(target, observer, time, ax=None, style_kwargs=None, - north_to_east_ccw=True, grid=True, az_label_offset=0.0*u.deg, - warn_below_horizon=False, style_sheet=None): +def plot_sky(target: FixedTarget, observer: Observer, time: Time, ax: Optional[Axes] = None, + style_kwargs: Optional[dict] = None, north_to_east_ccw: bool = True, grid: bool = True, + az_label_offset: Quantity = 0.0*u.deg, + warn_below_horizon: bool = False, style_sheet: Optional[dict] = None) -> Axes: """ Plots target positions in the sky with respect to the observer's location. @@ -229,9 +236,11 @@ def plot_sky(target, observer, time, ax=None, style_kwargs=None, @u.quantity_input(delta=u.hour) -def plot_sky_24hr(target, observer, time, delta=1*u.hour, ax=None, - style_kwargs=None, north_to_east_ccw=True, grid=True, - az_label_offset=0.0*u.deg, center_time_style_kwargs=None): +def plot_sky_24hr(target: FixedTarget, observer: Observer, time: Time, delta: Quantity = 1*u.hour, + ax: Axes = None, style_kwargs: Optional[dict] = None, + north_to_east_ccw: bool = True, grid: bool = True, + az_label_offset: Quantity = 0.0*u.deg, + center_time_style_kwargs: Optional[dict] = None) -> Axes: """ Plots target positions in the sky with respect to the observer's location over a twenty-four hour period centered on ``time``. diff --git a/astroplan/plots/time_dependent.py b/astroplan/plots/time_dependent.py index aca3296e..76bf65a7 100644 --- a/astroplan/plots/time_dependent.py +++ b/astroplan/plots/time_dependent.py @@ -1,23 +1,29 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) +from __future__ import absolute_import, division, print_function, unicode_literals + import copy -import numpy as np import operator -import astropy.units as u -from astropy.time import Time -from collections.abc import Sequence import warnings +from collections.abc import Sequence +from typing import Optional + +import astropy.units as u +import numpy as np import pytz +from astropy.time import Time +from matplotlib.axes import Axes from ..exceptions import PlotWarning +from ..observer import Observer +from ..scheduling import Schedule +from ..target import FixedTarget from ..utils import _set_mpl_style_sheet __all__ = ['plot_airmass', 'plot_schedule_airmass', 'plot_parallactic', 'plot_altitude'] -def _secz_to_altitude(secant_z): +def _secz_to_altitude(secant_z: float) -> float: """ Convert airmass (approximated as the secant of the zenith angle) to an altitude (aka elevation) in degrees. @@ -35,7 +41,7 @@ def _secz_to_altitude(secant_z): return np.degrees(np.pi/2 - np.arccos(1./secant_z)) -def _has_twin(ax): +def _has_twin(ax: Axes) -> bool: """ Solution for detecting twin axes built on `ax`. Courtesy of Jake Vanderplas http://stackoverflow.com/a/36209590/1340208 @@ -48,10 +54,12 @@ def _has_twin(ax): return False -def plot_airmass(targets, observer, time, ax=None, style_kwargs=None, - style_sheet=None, brightness_shading=False, - altitude_yaxis=False, min_airmass=1.0, min_region=None, - max_airmass=3.0, max_region=None, use_local_tz=False): +def plot_airmass(targets: FixedTarget, observer: Observer, time: Time, ax: Optional[Axes] = None, + style_kwargs: Optional[dict] = None, style_sheet: Optional[dict] = None, + brightness_shading: bool = False, altitude_yaxis: bool = False, + min_airmass: float = 1.0, min_region: Optional[float] = None, + max_airmass: float = 3.0, max_region: Optional[float] = None, + use_local_tz: bool = False) -> Axes: r""" Plots airmass as a function of time for a given target. @@ -276,10 +284,11 @@ def plot_airmass(targets, observer, time, ax=None, style_kwargs=None, return ax -def plot_altitude(targets, observer, time, ax=None, style_kwargs=None, - style_sheet=None, brightness_shading=False, - airmass_yaxis=False, min_altitude=0, min_region=None, - max_altitude=90, max_region=None): +def plot_altitude(targets: FixedTarget, observer: Observer, time: Time, ax: Optional[Axes] = None, + style_kwargs: Optional[dict] = None, style_sheet: Optional[dict] = None, + brightness_shading: bool = False, airmass_yaxis: bool = False, + min_altitude: float = 0, min_region: Optional[float] = None, + max_altitude: float = 90, max_region: Optional[float] = None) -> Axes: r""" Plots altitude as a function of time for a given target. @@ -467,7 +476,7 @@ def plot_altitude(targets, observer, time, ax=None, style_kwargs=None, return ax -def plot_schedule_airmass(schedule, show_night=False): +def plot_schedule_airmass(schedule: Schedule, show_night: bool = False) -> Axes: """ Plots when observations of targets are scheduled to occur superimposed upon plots of the airmasses of the targets. @@ -526,8 +535,8 @@ def plot_schedule_airmass(schedule, show_night=False): # TODO: make this output a `axes` object -def plot_parallactic(target, observer, time, ax=None, style_kwargs=None, - style_sheet=None): +def plot_parallactic(target: FixedTarget, observer: Observer, time: Time, ax: Optional[Axes] = None, + style_kwargs: Optional[dict] = None, style_sheet: Optional[dict] = None) -> Axes: """ Plots parallactic angle as a function of time for a given target. @@ -586,7 +595,6 @@ def plot_parallactic(target, observer, time, ax=None, style_kwargs=None, _set_mpl_style_sheet(style_sheet) import matplotlib.pyplot as plt - from matplotlib import dates # Set up plot axes and style if needed. From fc60e631dda6b7776accde750c237437c4f60b61 Mon Sep 17 00:00:00 2001 From: Thomas Vandal Date: Mon, 16 Sep 2024 02:32:51 -0300 Subject: [PATCH 09/11] Formatting --- astroplan/constraints.py | 4 ++-- astroplan/observer.py | 9 +++++---- astroplan/plots/time_dependent.py | 3 ++- astroplan/scheduling.py | 9 +++++---- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index 0a6f9c31..252afda5 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -1069,8 +1069,8 @@ def is_observable(constraints: Union[list[Constraint], Constraint], observer: Ob def is_event_observable(constraints: Union[list[Constraint], Constraint], - observer: Observer, target: FixedTarget, times: Optional[Time] = None, - times_ingress_egress: Optional[Time] = None) -> np.ndarray[bool]: + observer: Observer, target: FixedTarget, times: Optional[Time] = None, + times_ingress_egress: Optional[Time] = None) -> np.ndarray[bool]: """ Determines if the ``target`` is observable at each time in ``times``, given constraints in ``constraints`` for a particular ``observer``. diff --git a/astroplan/observer.py b/astroplan/observer.py index 347d4d7c..9af0b3d7 100644 --- a/astroplan/observer.py +++ b/astroplan/observer.py @@ -31,6 +31,7 @@ TargetType = Union[FixedTarget, SkyCoord, list[FixedTarget]] + # Handle deprecated MAGIC_TIME variable def deprecation_wrap_module(mod: str, deprecated: Sequence[str]) -> Any: """Return a wrapped object that warns about deprecated accesses""" @@ -157,7 +158,7 @@ def __init__(self, location: Optional[EarthLocation] = None, timezone: Union[str, datetime.tzinfo] = 'UTC', name: Optional[str] = None, latitude: Optional[Union[float, str, Quantity]] = None, longitude: Optional[Union[float, str, Quantity]] = None, - elevation: Quantity = 0*u.m, pressure: Optional[Quantity] =None, + elevation: Quantity = 0*u.m, pressure: Optional[Quantity] = None, relative_humidity: Optional[float] = None, temperature: Quantity = None, description: Optional[str] = None): """ @@ -1487,7 +1488,7 @@ def midnight(self, time: Time, which: str = 'nearest', n_grid_points: int = 150) # Twilight convenience functions def twilight_evening_astronomical(self, time: Time, which: str = 'nearest', - n_grid_points: int =150) -> Time: + n_grid_points: int = 150) -> Time: """ Time at evening astronomical (-18 degree) twilight. @@ -1577,7 +1578,7 @@ def twilight_evening_civil(self, time: Time, which: str = 'nearest', n_grid_points=n_grid_points) def twilight_morning_astronomical(self, time: Time, which: str = 'nearest', - n_grid_points: int =150) -> Time: + n_grid_points: int = 150) -> Time: """ Time at morning astronomical (-18 degree) twilight. @@ -2060,7 +2061,7 @@ def target_hour_angle(self, time: Any, target: TargetType, @u.quantity_input(horizon=u.degree) def tonight(self, time: Optional[Time] = None, horizon: Quantity = 0 * u.degree, - obswl: Optional[Quantity] =None) -> Time: + obswl: Optional[Quantity] = None) -> Time: """ Return a time range corresponding to the nearest night diff --git a/astroplan/plots/time_dependent.py b/astroplan/plots/time_dependent.py index 76bf65a7..f61743b7 100644 --- a/astroplan/plots/time_dependent.py +++ b/astroplan/plots/time_dependent.py @@ -536,7 +536,8 @@ def plot_schedule_airmass(schedule: Schedule, show_night: bool = False) -> Axes: def plot_parallactic(target: FixedTarget, observer: Observer, time: Time, ax: Optional[Axes] = None, - style_kwargs: Optional[dict] = None, style_sheet: Optional[dict] = None) -> Axes: + style_kwargs: Optional[dict] = None, + style_sheet: Optional[dict] = None) -> Axes: """ Plots parallactic angle as a function of time for a given target. diff --git a/astroplan/scheduling.py b/astroplan/scheduling.py index 06f75902..4ae69875 100644 --- a/astroplan/scheduling.py +++ b/astroplan/scheduling.py @@ -32,7 +32,7 @@ class ObservingBlock(object): """ @u.quantity_input(duration=u.second) def __init__(self, target: FixedTarget, duration: Quantity, priority: Union[int, float], - configuration: dict = {}, constraints: Optional[list[Constraint]] =None, + configuration: dict = {}, constraints: Optional[list[Constraint]] = None, name: Optional[Union[str, int]] = None): """ Parameters @@ -178,7 +178,7 @@ class TransitionBlock(object): telescope is slewing, instrument is reconfiguring, etc. """ - def __init__(self, components: dict[str, Quantity], start_time: Optional[Time] =None): + def __init__(self, components: dict[str, Quantity], start_time: Optional[Time] = None): """ Parameters ---------- @@ -240,7 +240,7 @@ class Schedule(object): # TODO: Remove unused constraints arg? def __init__(self, start_time: Time, end_time: Time, - constraints: Optional[Sequence[Constraint]] = None): + constraints: Optional[Sequence[Constraint]] = None): """ Parameters ---------- @@ -968,7 +968,8 @@ class Transitioner(object): u.quantity_input(slew_rate=u.deg/u.second) def __init__(self, slew_rate: Optional[Quantity] = None, - instrument_reconfig_times: Optional[dict[str, dict[tuple[str, str], Quantity]]] = None): + instrument_reconfig_times: Optional[dict[str, dict[tuple[str, str], Quantity]]] = None, + ): """ Parameters ---------- From 0a31cba9970d524cf1e3242704554eddddee119e Mon Sep 17 00:00:00 2001 From: Thomas Vandal Date: Mon, 16 Sep 2024 02:50:27 -0300 Subject: [PATCH 10/11] More formatting --- astroplan/scheduling.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/astroplan/scheduling.py b/astroplan/scheduling.py index 4ae69875..b173a137 100644 --- a/astroplan/scheduling.py +++ b/astroplan/scheduling.py @@ -967,7 +967,8 @@ class Transitioner(object): """ u.quantity_input(slew_rate=u.deg/u.second) - def __init__(self, slew_rate: Optional[Quantity] = None, + def __init__( + self, slew_rate: Optional[Quantity] = None, instrument_reconfig_times: Optional[dict[str, dict[tuple[str, str], Quantity]]] = None, ): """ From 480412cc270faefc577b7cf4f8f8584269e491c7 Mon Sep 17 00:00:00 2001 From: Thomas Vandal Date: Mon, 16 Sep 2024 03:17:58 -0300 Subject: [PATCH 11/11] Update typing.Self import to be backward compatible typing.Self was introduced in 3.11. Use a custom TypeVar for older releases. --- astroplan/constraints.py | 6 ++++-- astroplan/observer.py | 5 ++++- astroplan/scheduling.py | 6 ++++-- astroplan/target.py | 11 +++++++---- astroplan/utils.py | 13 +++++++++++-- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index 252afda5..f2e69993 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -9,7 +9,7 @@ # Standard library from abc import ABCMeta, abstractmethod -from typing import Optional, Self, Sequence, Union +from typing import Optional, Sequence, TypeVar, Union, _SpecialForm import datetime import time import warnings @@ -28,11 +28,13 @@ # Package from .moon import moon_illumination from .periodic import EclipsingSystem, PeriodicEvent -from .utils import time_grid_from_range +from .utils import time_grid_from_range, _import_typing_self_compat from .observer import Observer from .target import get_skycoord, FixedTarget from .exceptions import MissingConstraintWarning +Self: Union[TypeVar, _SpecialForm] = _import_typing_self_compat() + __all__ = ["AltitudeConstraint", "AirmassConstraint", "AtNightConstraint", "is_observable", "is_always_observable", "time_grid_from_range", "GalacticLatitudeConstraint", "SunSeparationConstraint", diff --git a/astroplan/observer.py b/astroplan/observer.py index 9af0b3d7..aeaf1724 100644 --- a/astroplan/observer.py +++ b/astroplan/observer.py @@ -5,7 +5,7 @@ # Standard library import sys -from typing import Any, Callable, Optional, Self, Sequence, Union +from typing import Any, Callable, Optional, Sequence, Union import datetime import warnings @@ -23,6 +23,9 @@ from .exceptions import TargetNeverUpWarning, TargetAlwaysUpWarning from .moon import moon_illumination, moon_phase_angle from .target import get_skycoord, SunFlag, MoonFlag, FixedTarget +from .utils import _import_typing_self_compat + +Self = _import_typing_self_compat() __all__ = ["Observer"] diff --git a/astroplan/scheduling.py b/astroplan/scheduling.py index b173a137..9ae1cfe0 100644 --- a/astroplan/scheduling.py +++ b/astroplan/scheduling.py @@ -7,7 +7,7 @@ import copy from abc import ABCMeta, abstractmethod -from typing import Optional, Self, Sequence, Union +from typing import Optional, Sequence, Union import numpy as np from astropy import units as u @@ -18,7 +18,9 @@ from .constraints import AltitudeConstraint, Constraint from .observer import Observer from .target import FixedTarget, get_skycoord -from .utils import stride_array, time_grid_from_range +from .utils import stride_array, time_grid_from_range, _import_typing_self_compat + +Self = _import_typing_self_compat() __all__ = ['ObservingBlock', 'TransitionBlock', 'Schedule', 'Slot', 'Scheduler', 'SequentialScheduler', 'PriorityScheduler', diff --git a/astroplan/target.py b/astroplan/target.py index 3144b347..28ed1aeb 100644 --- a/astroplan/target.py +++ b/astroplan/target.py @@ -1,15 +1,18 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) +from __future__ import absolute_import, division, print_function, unicode_literals # Standard library from abc import ABCMeta -from typing import Optional, Self, Union +from typing import Optional, Union # Third-party import astropy.units as u +from astropy.coordinates import ICRS, SkyCoord, UnitSphericalRepresentation from astropy.units import Quantity -from astropy.coordinates import SkyCoord, ICRS, UnitSphericalRepresentation + +from .utils import _import_typing_self_compat + +Self = _import_typing_self_compat() __all__ = ["Target", "FixedTarget", "NonFixedTarget"] diff --git a/astroplan/utils.py b/astroplan/utils.py index fe49f784..4b74e20b 100644 --- a/astroplan/utils.py +++ b/astroplan/utils.py @@ -4,7 +4,7 @@ # Standard library import warnings -from typing import Union +from typing import Union, _SpecialForm, TypeVar import os # Third-party @@ -21,7 +21,7 @@ __all__ = ["download_IERS_A", "time_grid_from_range", "_set_mpl_style_sheet", - "stride_array"] + "stride_array", "_import_typing_self_compat"] IERS_A_WARNING = ("For best precision (on the order of arcseconds), you must " "download an up-to-date IERS Bulletin A table. To do so, run:" @@ -259,3 +259,12 @@ def _open_shelve(shelffn: Union[str, os.PathLike], withclosing: bool = False) -> return contextlib.closing(shelf) else: return shelf + + +def _import_typing_self_compat() -> Union[_SpecialForm, TypeVar]: + # TODO: Remove when no support for Python<3.11 + try: + from typing import Self + except ImportError: + Self = TypeVar("Self") + return Self