Bump apscheduler from 3.8.0 to 3.9.1 (#1675)

* Bump apscheduler from 3.8.0 to 3.9.1

Bumps [apscheduler](https://github.com/agronholm/apscheduler) from 3.8.0 to 3.9.1.
- [Release notes](https://github.com/agronholm/apscheduler/releases)
- [Changelog](https://github.com/agronholm/apscheduler/blob/3.9.1/docs/versionhistory.rst)
- [Commits](https://github.com/agronholm/apscheduler/compare/3.8.0...3.9.1)

---
updated-dependencies:
- dependency-name: apscheduler
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update apscheduler==3.9.1

* Update pytz==2022.1

* Add pytz-deprecation-shim==0.1.0.post0

* Update tzdata==2022.1

* Update tzlocal==4.2

* Update requirements.txt

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>

[skip ci]
This commit is contained in:
dependabot[bot] 2022-05-16 20:32:37 -07:00 committed by GitHub
commit 54c9214b03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 1029 additions and 223 deletions

View file

@ -0,0 +1,34 @@
__all__ = [
"AmbiguousTimeError",
"NonExistentTimeError",
"InvalidTimeError",
"UnknownTimeZoneError",
"PytzUsageWarning",
"FixedOffset",
"UTC",
"utc",
"build_tzinfo",
"timezone",
"fixed_offset_timezone",
"wrap_zone",
]
from . import helpers
from ._exceptions import (
AmbiguousTimeError,
InvalidTimeError,
NonExistentTimeError,
PytzUsageWarning,
UnknownTimeZoneError,
)
from ._impl import (
UTC,
build_tzinfo,
fixed_offset_timezone,
timezone,
wrap_zone,
)
# Compatibility aliases
utc = UTC
FixedOffset = fixed_offset_timezone

View file

@ -0,0 +1,13 @@
import sys
_PYTZ_IMPORTED = False
def pytz_imported():
"""Detects whether or not pytz has been imported without importing pytz."""
global _PYTZ_IMPORTED
if not _PYTZ_IMPORTED and "pytz" in sys.modules:
_PYTZ_IMPORTED = True
return _PYTZ_IMPORTED

View file

@ -0,0 +1,15 @@
import sys
if sys.version_info[0] == 2:
from . import _compat_py2 as _compat_impl
else:
from . import _compat_py3 as _compat_impl
UTC = _compat_impl.UTC
get_timezone = _compat_impl.get_timezone
get_timezone_file = _compat_impl.get_timezone_file
get_fixed_offset_zone = _compat_impl.get_fixed_offset_zone
is_ambiguous = _compat_impl.is_ambiguous
is_imaginary = _compat_impl.is_imaginary
enfold = _compat_impl.enfold
get_fold = _compat_impl.get_fold

View file

@ -0,0 +1,43 @@
from datetime import timedelta
from dateutil import tz
UTC = tz.UTC
def get_timezone(key):
if not key:
raise KeyError("Unknown time zone: %s" % key)
try:
rv = tz.gettz(key)
except Exception:
rv = None
if rv is None or not isinstance(rv, (tz.tzutc, tz.tzfile)):
raise KeyError("Unknown time zone: %s" % key)
return rv
def get_timezone_file(f, key=None):
return tz.tzfile(f)
def get_fixed_offset_zone(offset):
return tz.tzoffset(None, timedelta(minutes=offset))
def is_ambiguous(dt):
return tz.datetime_ambiguous(dt)
def is_imaginary(dt):
return not tz.datetime_exists(dt)
enfold = tz.enfold
def get_fold(dt):
return getattr(dt, "fold", 0)

View file

@ -0,0 +1,58 @@
# Note: This file could use Python 3-only syntax, but at the moment this breaks
# the coverage job on Python 2. Until we make it so that coverage can ignore
# this file only on Python 2, we'll have to stick to 2/3-compatible syntax.
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
import datetime
UTC = datetime.timezone.utc
def get_timezone(key):
try:
return zoneinfo.ZoneInfo(key)
except (ValueError, OSError):
# TODO: Use `from e` when this file can use Python 3 syntax
raise KeyError(key)
def get_timezone_file(f, key=None):
return zoneinfo.ZoneInfo.from_file(f, key=key)
def get_fixed_offset_zone(offset):
return datetime.timezone(datetime.timedelta(minutes=offset))
def is_imaginary(dt):
dt_rt = dt.astimezone(UTC).astimezone(dt.tzinfo)
return not (dt == dt_rt)
def is_ambiguous(dt):
if is_imaginary(dt):
return False
wall_0 = dt
wall_1 = dt.replace(fold=not dt.fold)
# Ambiguous datetimes can only exist if the offset changes, so we don't
# actually have to check whether dst() or tzname() are different.
same_offset = wall_0.utcoffset() == wall_1.utcoffset()
return not same_offset
def enfold(dt, fold=1):
if dt.fold != fold:
return dt.replace(fold=fold)
else:
return dt
def get_fold(dt):
return dt.fold

View file

@ -0,0 +1,75 @@
from ._common import pytz_imported
class PytzUsageWarning(RuntimeWarning):
"""Warning raised when accessing features specific to ``pytz``'s interface.
This warning is used to direct users of ``pytz``-specific features like the
``localize`` and ``normalize`` methods towards using the standard
``tzinfo`` interface, so that these shims can be replaced with one of the
underlying libraries they are wrapping.
"""
class UnknownTimeZoneError(KeyError):
"""Raised when no time zone is found for a specified key."""
class InvalidTimeError(Exception):
"""The base class for exceptions related to folds and gaps."""
class AmbiguousTimeError(InvalidTimeError):
"""Exception raised when ``is_dst=None`` for an ambiguous time (fold)."""
class NonExistentTimeError(InvalidTimeError):
"""Exception raised when ``is_dst=None`` for a non-existent time (gap)."""
PYTZ_BASE_ERROR_MAPPING = {}
def _make_pytz_derived_errors(
InvalidTimeError_=InvalidTimeError,
AmbiguousTimeError_=AmbiguousTimeError,
NonExistentTimeError_=NonExistentTimeError,
UnknownTimeZoneError_=UnknownTimeZoneError,
):
if PYTZ_BASE_ERROR_MAPPING or not pytz_imported():
return
import pytz
class InvalidTimeError(InvalidTimeError_, pytz.InvalidTimeError):
pass
class AmbiguousTimeError(AmbiguousTimeError_, pytz.AmbiguousTimeError):
pass
class NonExistentTimeError(
NonExistentTimeError_, pytz.NonExistentTimeError
):
pass
class UnknownTimeZoneError(
UnknownTimeZoneError_, pytz.UnknownTimeZoneError
):
pass
PYTZ_BASE_ERROR_MAPPING.update(
{
InvalidTimeError_: InvalidTimeError,
AmbiguousTimeError_: AmbiguousTimeError,
NonExistentTimeError_: NonExistentTimeError,
UnknownTimeZoneError_: UnknownTimeZoneError,
}
)
def get_exception(exc_type, msg):
_make_pytz_derived_errors()
out_exc_type = PYTZ_BASE_ERROR_MAPPING.get(exc_type, exc_type)
return out_exc_type(msg)

View file

@ -0,0 +1,296 @@
# -*- coding: utf-8 -*-
import warnings
from datetime import tzinfo
from . import _compat
from ._exceptions import (
AmbiguousTimeError,
NonExistentTimeError,
PytzUsageWarning,
UnknownTimeZoneError,
get_exception,
)
IS_DST_SENTINEL = object()
KEY_SENTINEL = object()
def timezone(key, _cache={}):
"""Builds an IANA database time zone shim.
This is the equivalent of ``pytz.timezone``.
:param key:
A valid key from the IANA time zone database.
:raises UnknownTimeZoneError:
If an unknown value is passed, this will raise an exception that can be
caught by :exc:`pytz_deprecation_shim.UnknownTimeZoneError` or
``pytz.UnknownTimeZoneError``. Like
:exc:`zoneinfo.ZoneInfoNotFoundError`, both of those are subclasses of
:exc:`KeyError`.
"""
instance = _cache.get(key, None)
if instance is None:
if len(key) == 3 and key.lower() == "utc":
instance = _cache.setdefault(key, UTC)
else:
try:
zone = _compat.get_timezone(key)
except KeyError:
raise get_exception(UnknownTimeZoneError, key)
instance = _cache.setdefault(key, wrap_zone(zone, key=key))
return instance
def fixed_offset_timezone(offset, _cache={}):
"""Builds a fixed offset time zone shim.
This is the equivalent of ``pytz.FixedOffset``. An alias is available as
``pytz_deprecation_shim.FixedOffset`` as well.
:param offset:
A fixed offset from UTC, in minutes. This must be in the range ``-1439
<= offset <= 1439``.
:raises ValueError:
For offsets whose absolute value is greater than or equal to 24 hours.
:return:
A shim time zone.
"""
if not (-1440 < offset < 1440):
raise ValueError("absolute offset is too large", offset)
instance = _cache.get(offset, None)
if instance is None:
if offset == 0:
instance = _cache.setdefault(offset, UTC)
else:
zone = _compat.get_fixed_offset_zone(offset)
instance = _cache.setdefault(offset, wrap_zone(zone, key=None))
return instance
def build_tzinfo(zone, fp):
"""Builds a shim object from a TZif file.
This is a shim for ``pytz.build_tzinfo``. Given a value to use as the zone
IANA key and a file-like object containing a valid TZif file (i.e.
conforming to :rfc:`8536`), this builds a time zone object and wraps it in
a shim class.
The argument names are chosen to match those in ``pytz.build_tzinfo``.
:param zone:
A string to be used as the time zone object's IANA key.
:param fp:
A readable file-like object emitting bytes, pointing to a valid TZif
file.
:return:
A shim time zone.
"""
zone_file = _compat.get_timezone_file(fp)
return wrap_zone(zone_file, key=zone)
def wrap_zone(tz, key=KEY_SENTINEL, _cache={}):
"""Wrap an existing time zone object in a shim class.
This is likely to be useful if you would like to work internally with
non-``pytz`` zones, but you expose an interface to callers relying on
``pytz``'s interface. It may also be useful for passing non-``pytz`` zones
to libraries expecting to use ``pytz``'s interface.
:param tz:
A :pep:`495`-compatible time zone, such as those provided by
:mod:`dateutil.tz` or :mod:`zoneinfo`.
:param key:
The value for the IANA time zone key. This is optional for ``zoneinfo``
zones, but required for ``dateutil.tz`` zones.
:return:
A shim time zone.
"""
if key is KEY_SENTINEL:
key = getattr(tz, "key", KEY_SENTINEL)
if key is KEY_SENTINEL:
raise TypeError(
"The `key` argument is required when wrapping zones that do not "
+ "have a `key` attribute."
)
instance = _cache.get((id(tz), key), None)
if instance is None:
instance = _cache.setdefault((id(tz), key), _PytzShimTimezone(tz, key))
return instance
class _PytzShimTimezone(tzinfo):
# Add instance variables for _zone and _key because this will make error
# reporting with partially-initialized _BasePytzShimTimezone objects
# work better.
_zone = None
_key = None
def __init__(self, zone, key):
self._key = key
self._zone = zone
def utcoffset(self, dt):
return self._zone.utcoffset(dt)
def dst(self, dt):
return self._zone.dst(dt)
def tzname(self, dt):
return self._zone.tzname(dt)
def fromutc(self, dt):
# The default fromutc implementation only works if tzinfo is "self"
dt_base = dt.replace(tzinfo=self._zone)
dt_out = self._zone.fromutc(dt_base)
return dt_out.replace(tzinfo=self)
def __str__(self):
if self._key is not None:
return str(self._key)
else:
return repr(self)
def __repr__(self):
return "%s(%s, %s)" % (
self.__class__.__name__,
repr(self._zone),
repr(self._key),
)
def unwrap_shim(self):
"""Returns the underlying class that the shim is a wrapper for.
This is a shim-specific method equivalent to
:func:`pytz_deprecation_shim.helpers.upgrade_tzinfo`. It is provided as
a method to allow end-users to upgrade shim timezones without requiring
an explicit dependency on ``pytz_deprecation_shim``, e.g.:
.. code-block:: python
if getattr(tz, "unwrap_shim", None) is None:
tz = tz.unwrap_shim()
"""
return self._zone
@property
def zone(self):
warnings.warn(
"The zone attribute is specific to pytz's interface; "
+ "please migrate to a new time zone provider. "
+ "For more details on how to do so, see %s"
% PYTZ_MIGRATION_GUIDE_URL,
PytzUsageWarning,
stacklevel=2,
)
return self._key
def localize(self, dt, is_dst=IS_DST_SENTINEL):
warnings.warn(
"The localize method is no longer necessary, as this "
+ "time zone supports the fold attribute (PEP 495). "
+ "For more details on migrating to a PEP 495-compliant "
+ "implementation, see %s" % PYTZ_MIGRATION_GUIDE_URL,
PytzUsageWarning,
stacklevel=2,
)
if dt.tzinfo is not None:
raise ValueError("Not naive datetime (tzinfo is already set)")
dt_out = dt.replace(tzinfo=self)
if is_dst is IS_DST_SENTINEL:
return dt_out
dt_ambiguous = _compat.is_ambiguous(dt_out)
dt_imaginary = (
_compat.is_imaginary(dt_out) if not dt_ambiguous else False
)
if is_dst is None:
if dt_imaginary:
raise get_exception(
NonExistentTimeError, dt.replace(tzinfo=None)
)
if dt_ambiguous:
raise get_exception(AmbiguousTimeError, dt.replace(tzinfo=None))
elif dt_ambiguous or dt_imaginary:
# Start by normalizing the folds; dt_out may have fold=0 or fold=1,
# but we need to know the DST offset on both sides anyway, so we
# will get one datetime representing each side of the fold, then
# decide which one we're going to return.
if _compat.get_fold(dt_out):
dt_enfolded = dt_out
dt_out = _compat.enfold(dt_out, fold=0)
else:
dt_enfolded = _compat.enfold(dt_out, fold=1)
# Now we want to decide whether the fold=0 or fold=1 represents
# what pytz would return for `is_dst=True`
enfolded_dst = bool(dt_enfolded.dst())
if bool(dt_out.dst()) == enfolded_dst:
# If this is not a transition between standard time and
# daylight saving time, pytz will consider the larger offset
# the DST offset.
enfolded_dst = dt_enfolded.utcoffset() > dt_out.utcoffset()
# The default we've established is that dt_out is fold=0; swap it
# for the fold=1 datetime if is_dst == True and the enfolded side
# is DST or if is_dst == False and the enfolded side is *not* DST.
if is_dst == enfolded_dst:
dt_out = dt_enfolded
return dt_out
def normalize(self, dt):
warnings.warn(
"The normalize method is no longer necessary, as this "
+ "time zone supports the fold attribute (PEP 495). "
+ "For more details on migrating to a PEP 495-compliant "
+ "implementation, see %s" % PYTZ_MIGRATION_GUIDE_URL,
PytzUsageWarning,
stacklevel=2,
)
if dt.tzinfo is None:
raise ValueError("Naive time - no tzinfo set")
if dt.tzinfo is self:
return dt
return dt.astimezone(self)
def __copy__(self):
return self
def __deepcopy__(self, memo=None):
return self
def __reduce__(self):
return wrap_zone, (self._zone, self._key)
UTC = wrap_zone(_compat.UTC, "UTC")
PYTZ_MIGRATION_GUIDE_URL = (
"https://pytz-deprecation-shim.readthedocs.io/en/latest/migration.html"
)

View file

@ -0,0 +1,90 @@
"""
This module contains helper functions to ease the transition from ``pytz`` to
another :pep:`495`-compatible library.
"""
from . import _common, _compat
from ._impl import _PytzShimTimezone
_PYTZ_BASE_CLASSES = None
def is_pytz_zone(tz):
"""Check if a time zone is a ``pytz`` time zone.
This will only import ``pytz`` if it has already been imported, and does
not rely on the existence of the ``localize`` or ``normalize`` methods
(since the shim classes also have these methods, but are not ``pytz``
zones).
"""
# If pytz is not in sys.modules, then we will assume the time zone is not a
# pytz zone. It is possible that someone has manipulated sys.modules to
# remove pytz, but that's the kind of thing that causes all kinds of other
# problems anyway, so we'll call that an unsupported configuration.
if not _common.pytz_imported():
return False
if _PYTZ_BASE_CLASSES is None:
_populate_pytz_base_classes()
return isinstance(tz, _PYTZ_BASE_CLASSES)
def upgrade_tzinfo(tz):
"""Convert a ``pytz`` or shim timezone into its modern equivalent.
The shim classes are thin wrappers around :mod:`zoneinfo` or
:mod:`dateutil.tz` implementations of the :class:`datetime.tzinfo` base
class. This function removes the shim and returns the underlying "upgraded"
time zone.
When passed a ``pytz`` zone (not a shim), this returns the non-``pytz``
equivalent. This may fail if ``pytz`` is using a data source incompatible
with the upgraded provider's data source, or if the ``pytz`` zone was built
from a file rather than an IANA key.
When passed an object that is not a shim or a ``pytz`` zone, this returns
the original object.
:param tz:
A :class:`datetime.tzinfo` object.
:raises KeyError:
If a ``pytz`` zone is passed to the function with no equivalent in the
:pep:`495`-compatible library's version of the Olson database.
:return:
A :pep:`495`-compatible equivalent of any ``pytz`` or shim
class, or the original object.
"""
if isinstance(tz, _PytzShimTimezone):
return tz._zone
if is_pytz_zone(tz):
if tz.zone is None:
# This is a fixed offset zone
offset = tz.utcoffset(None)
offset_minutes = offset.total_seconds() / 60
return _compat.get_fixed_offset_zone(offset_minutes)
if tz.zone == "UTC":
return _compat.UTC
return _compat.get_timezone(tz.zone)
return tz
def _populate_pytz_base_classes():
import pytz
from pytz.tzinfo import BaseTzInfo
base_classes = (BaseTzInfo, pytz._FixedOffset)
# In releases prior to 2018.4, pytz.UTC was not a subclass of BaseTzInfo
if not isinstance(pytz.UTC, BaseTzInfo): # pragma: nocover
base_classes = base_classes + (type(pytz.UTC),)
global _PYTZ_BASE_CLASSES
_PYTZ_BASE_CLASSES = base_classes