Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate Python 2.7 and Python 3.5 #877

Merged
merged 7 commits into from
Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["pypy3", "2.7", "3.5", "3.6", "3.7", "3.8", "3.9"]
python-version: ["pypy3", "3.6", "3.7", "3.8", "3.9"]
os: [ubuntu-latest, macos-latest, windows-latest]
exclude:
# pypy3 randomly fails on Windows builds
Expand Down
13 changes: 7 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ default_language_version:
python: python3
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
rev: v3.3.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: fix-encoding-pragma
exclude: ^arrow/_version.py
jadchaar marked this conversation as resolved.
Show resolved Hide resolved
args: [--remove]
- id: requirements-txt-fixer
- id: check-ast
- id: check-yaml
Expand All @@ -16,15 +16,16 @@ repos:
- id: check-merge-conflict
- id: debug-statements
- repo: https://github.com/timothycrosley/isort
rev: 5.5.4
rev: 5.6.4
hooks:
- id: isort
- repo: https://github.com/asottile/pyupgrade
rev: v2.7.2
rev: v2.7.3
hooks:
- id: pyupgrade
args: [--py36-plus]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.6.0
rev: v1.7.0
hooks:
- id: python-no-eval
- id: python-check-blanket-noqa
Expand All @@ -33,7 +34,7 @@ repos:
rev: 20.8b1
hooks:
- id: black
args: [--safe, --quiet]
args: [--safe, --quiet, --target-version=py36]
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.4
hooks:
Expand Down
21 changes: 12 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,32 @@

auto: build38

build27: PYTHON_VER = python2.7
build35: PYTHON_VER = python3.5
build36: PYTHON_VER = python3.6
build37: PYTHON_VER = python3.7
build38: PYTHON_VER = python3.8
build39: PYTHON_VER = python3.9

build27 build35 build36 build37 build38 build39: clean
virtualenv venv --python=$(PYTHON_VER)
build36 build37 build38 build39: clean
$(PYTHON_VER) -m venv venv
. venv/bin/activate; \
pip install -U pip setuptools wheel; \
pip install -r requirements.txt; \
pre-commit install

test:
rm -f .coverage coverage.xml
. venv/bin/activate; pytest
. venv/bin/activate; \
pytest

lint:
. venv/bin/activate; pre-commit run --all-files --show-diff-on-failure
. venv/bin/activate; \
pre-commit run --all-files --show-diff-on-failure

docs:
rm -rf docs/_build
. venv/bin/activate; cd docs; make html
. venv/bin/activate; \
cd docs; \
make html

clean: clean-dist
rm -rf venv .pytest_cache ./**/__pycache__
Expand All @@ -36,10 +38,11 @@ clean-dist:

build-dist:
. venv/bin/activate; \
pip install -U setuptools twine wheel; \
pip install -U pip setuptools twine wheel; \
python setup.py sdist bdist_wheel

upload-dist:
. venv/bin/activate; twine upload dist/*
. venv/bin/activate; \
twine upload dist/*

publish: test clean-dist build-dist upload-dist clean-dist
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Features
--------

- Fully-implemented, drop-in replacement for datetime
- Supports Python 2.7, 3.5, 3.6, 3.7, 3.8 and 3.9
- Supports Python 3.6+
- Timezone-aware and UTC by default
- Provides super-simple creation options for many common input scenarios
- :code:`shift` method with support for relative offsets, including weeks
Expand Down
1 change: 0 additions & 1 deletion arrow/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from ._version import __version__
from .api import get, now, utcnow
from .arrow import Arrow
Expand Down
2 changes: 0 additions & 2 deletions arrow/api.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# -*- coding: utf-8 -*-
"""
Provides the default implementation of :class:`ArrowFactory <arrow.factory.ArrowFactory>`
methods for use as a module API.

"""

from __future__ import absolute_import

from arrow.factory import ArrowFactory

Expand Down
70 changes: 17 additions & 53 deletions arrow/arrow.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
# -*- coding: utf-8 -*-
"""
Provides the :class:`Arrow <arrow.arrow.Arrow>` class, an enhanced ``datetime``
replacement.

"""

from __future__ import absolute_import

import calendar
import sys
import warnings
from datetime import datetime, timedelta
from datetime import tzinfo as dt_tzinfo
from math import trunc
Expand All @@ -19,17 +16,8 @@

from arrow import formatter, locales, parser, util

if sys.version_info[:2] < (3, 6): # pragma: no cover
with warnings.catch_warnings():
warnings.simplefilter("default", DeprecationWarning)
warnings.warn(
"Arrow will drop support for Python 2.7 and 3.5 in the upcoming v1.0.0 release. Please upgrade to "
"Python 3.6+ to continue receiving updates for Arrow.",
DeprecationWarning,
)


class Arrow(object):
class Arrow:
"""An :class:`Arrow <arrow.arrow.Arrow>` object.

Implements the ``datetime`` interface, behaving as an aware ``datetime`` while implementing
Expand Down Expand Up @@ -65,7 +53,7 @@ class Arrow(object):
resolution = datetime.resolution

_ATTRS = ["year", "month", "day", "hour", "minute", "second", "microsecond"]
_ATTRS_PLURAL = ["{}s".format(a) for a in _ATTRS]
_ATTRS_PLURAL = [f"{a}s" for a in _ATTRS]
_MONTHS_PER_QUARTER = 3
_SECS_PER_MINUTE = float(60)
_SECS_PER_HOUR = float(60 * 60)
Expand All @@ -84,7 +72,7 @@ def __init__(
second=0,
microsecond=0,
tzinfo=None,
**kwargs
**kwargs,
):
if tzinfo is None:
tzinfo = dateutil_tz.tzutc()
Expand All @@ -96,7 +84,7 @@ def __init__(
and tzinfo.zone
):
tzinfo = parser.TzinfoParser.parse(tzinfo.zone)
elif util.isstr(tzinfo):
elif isinstance(tzinfo, str):
tzinfo = parser.TzinfoParser.parse(tzinfo)

fold = kwargs.get("fold", 0)
Expand Down Expand Up @@ -177,13 +165,11 @@ def fromtimestamp(cls, timestamp, tzinfo=None):

if tzinfo is None:
tzinfo = dateutil_tz.tzlocal()
elif util.isstr(tzinfo):
elif isinstance(tzinfo, str):
tzinfo = parser.TzinfoParser.parse(tzinfo)

if not util.is_timestamp(timestamp):
raise ValueError(
"The provided timestamp '{}' is invalid.".format(timestamp)
)
raise ValueError(f"The provided timestamp '{timestamp}' is invalid.")

timestamp = util.normalize_timestamp(float(timestamp))
dt = datetime.fromtimestamp(timestamp, tzinfo)
Expand All @@ -209,9 +195,7 @@ def utcfromtimestamp(cls, timestamp):
"""

if not util.is_timestamp(timestamp):
raise ValueError(
"The provided timestamp '{}' is invalid.".format(timestamp)
)
raise ValueError(f"The provided timestamp '{timestamp}' is invalid.")

timestamp = util.normalize_timestamp(float(timestamp))
dt = datetime.utcfromtimestamp(timestamp)
Expand Down Expand Up @@ -604,7 +588,7 @@ def interval(cls, frame, start, end, interval=1, tz=None, bounds="[)"):
# representations

def __repr__(self):
return "<{} [{}]>".format(self.__class__.__name__, self.__str__())
return f"<{self.__class__.__name__} [{self.__str__()}]>"

def __str__(self):
return self._datetime.isoformat()
Expand Down Expand Up @@ -688,7 +672,6 @@ def naive(self):

return self._datetime.replace(tzinfo=None)

@property
def timestamp(self):
"""Returns a timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
UTC time.
Expand All @@ -700,13 +683,7 @@ def timestamp(self):

"""

warnings.warn(
"For compatibility with the datetime.timestamp() method this property will be replaced with a method in "
"the 1.0.0 release, please switch to the .int_timestamp property for identical behaviour as soon as "
"possible.",
DeprecationWarning,
)
return calendar.timegm(self._datetime.utctimetuple())
return self._datetime.timestamp()

@property
def int_timestamp(self):
Expand All @@ -720,7 +697,7 @@ def int_timestamp(self):

"""

return calendar.timegm(self._datetime.utctimetuple())
return int(self.timestamp())
Comment on lines -723 to +700
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably best to stick with old way to guarantee compatibility.


@property
def float_timestamp(self):
Expand All @@ -734,11 +711,7 @@ def float_timestamp(self):

"""

# IDEA get rid of this in 1.0.0 and wrap datetime.timestamp()
# Or for compatibility retain this but make it call the timestamp method
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return self.timestamp + float(self.microsecond) / 1000000
return self.timestamp()
systemcatch marked this conversation as resolved.
Show resolved Hide resolved

@property
def fold(self):
Expand Down Expand Up @@ -802,9 +775,9 @@ def replace(self, **kwargs):
if key in self._ATTRS:
absolute_kwargs[key] = value
elif key in ["week", "quarter"]:
raise AttributeError("setting absolute {} is not supported".format(key))
raise AttributeError(f"setting absolute {key} is not supported")
elif key not in ["tzinfo", "fold"]:
raise AttributeError('unknown attribute: "{}"'.format(key))
raise AttributeError(f'unknown attribute: "{key}"')

current = self._datetime.replace(**absolute_kwargs)

Expand Down Expand Up @@ -1062,7 +1035,7 @@ def humanize(
years = sign * int(max(delta / self._SECS_PER_YEAR, 2))
return locale.describe("years", years, only_distance=only_distance)

elif util.isstr(granularity):
elif isinstance(granularity, str):
if granularity == "second":
delta = sign * delta
if abs(delta) < 2:
Expand Down Expand Up @@ -1491,13 +1464,6 @@ def __le__(self, other):

return self._datetime <= self._get_datetime(other)

def __cmp__(self, other):
if sys.version_info[0] < 3: # pragma: no cover
if not isinstance(other, (Arrow, datetime)):
raise TypeError(
"can't compare '{}' to '{}'".format(type(self), type(other))
)

# internal methods

@staticmethod
Expand All @@ -1511,7 +1477,7 @@ def _get_tzinfo(tz_expr):
try:
return parser.TzinfoParser.parse(tz_expr)
except parser.ParserError:
raise ValueError("'{}' not recognized as a timezone".format(tz_expr))
raise ValueError(f"'{tz_expr}' not recognized as a timezone")

@classmethod
def _get_datetime(cls, expr):
Expand All @@ -1524,15 +1490,13 @@ def _get_datetime(cls, expr):
timestamp = float(expr)
return cls.utcfromtimestamp(timestamp).datetime
else:
raise ValueError(
"'{}' not recognized as a datetime or timestamp.".format(expr)
)
raise ValueError(f"'{expr}' not recognized as a datetime or timestamp.")

@classmethod
def _get_frames(cls, name):

if name in cls._ATTRS:
return name, "{}s".format(name), 1
return name, f"{name}s", 1
elif name[-1] == "s" and name[:-1] in cls._ATTRS:
return name[:-1], name, 1
elif name in ["week", "weeks"]:
Expand Down
15 changes: 9 additions & 6 deletions arrow/constants.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
import os
from datetime import datetime

# Output of time.mktime(datetime.max.timetuple()) on macOS
# This value must be hardcoded for compatibility with Windows
# Platform-independent max timestamps are hard to form
# https://stackoverflow.com/q/46133223
MAX_TIMESTAMP = 253402318799.0
# datetime.max.timestamp() errors on Windows, so we must hardcode
# the highest possible datetime value that can output a timestamp.
# tl;dr platform-independent max timestamps are hard to form
# See: https://stackoverflow.com/q/46133223
MAX_TIMESTAMP = (
datetime(3001, 1, 18, 23, 59, 59, 999999) if os.name == "nt" else datetime.max
).timestamp()
MAX_TIMESTAMP_MS = MAX_TIMESTAMP * 1000
MAX_TIMESTAMP_US = MAX_TIMESTAMP * 1000000
Loading