From 12dbba8d77a236c572b51e233d576f6d0428ddd1 Mon Sep 17 00:00:00 2001 From: Mohammad Khan Date: Sun, 20 Nov 2022 19:53:03 -0500 Subject: [PATCH 01/10] add cython setup Co-Authored by: Lukas Lemke <90070416+13MK3@users.noreply.github.com> --- setup.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 52563cf9..9b652003 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,21 @@ # mypy: ignore-errors from pathlib import Path -from setuptools import setup +from Cython.Build import build_ext, cythonize +from setuptools import Extension, setup readme = Path("README.rst").read_text(encoding="utf-8") version = Path("arrow/_version.py").read_text(encoding="utf-8") about = {} exec(version, about) +extensions = [ + Extension( + "*", + ["arrow/*.py"], + ) +] + setup( name="arrow", version=about["__version__"], @@ -46,4 +54,6 @@ "Bug Reports": "https://github.com/arrow-py/arrow/issues", "Documentation": "https://arrow.readthedocs.io", }, + ext_modules=cythonize(extensions, language_level="3"), + cmdclass={"build_ext": build_ext}, ) From e819f8722b4d06aa98dd03fa099644812426fdfb Mon Sep 17 00:00:00 2001 From: Mohammad Khan Date: Sun, 20 Nov 2022 20:37:15 -0500 Subject: [PATCH 02/10] modify arrow.py to work with cython Co-Authored by: Lukas Lemke <90070416+13MK3@users.noreply.github.com> --- arrow/arrow.py | 65 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/arrow/arrow.py b/arrow/arrow.py index e855eee0..dedd0c3c 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -1148,7 +1148,7 @@ def humanize( """ locale_name = locale - locale = locales.get_locale(locale) + locale_cls = locales.get_locale(locale) if other is None: utc = dt_datetime.utcnow().replace(tzinfo=dateutil_tz.tzutc()) @@ -1179,41 +1179,55 @@ def humanize( try: if granularity == "auto": if diff < 10: - return locale.describe("now", only_distance=only_distance) + return locale_cls.describe("now", only_distance=only_distance) if diff < self._SECS_PER_MINUTE: seconds = sign * delta_second - return locale.describe( + return locale_cls.describe( "seconds", seconds, only_distance=only_distance ) elif diff < self._SECS_PER_MINUTE * 2: - return locale.describe("minute", sign, only_distance=only_distance) + return locale_cls.describe( + "minute", sign, only_distance=only_distance + ) elif diff < self._SECS_PER_HOUR: minutes = sign * max(delta_second // self._SECS_PER_MINUTE, 2) - return locale.describe( + return locale_cls.describe( "minutes", minutes, only_distance=only_distance ) elif diff < self._SECS_PER_HOUR * 2: - return locale.describe("hour", sign, only_distance=only_distance) + return locale_cls.describe( + "hour", sign, only_distance=only_distance + ) elif diff < self._SECS_PER_DAY: hours = sign * max(delta_second // self._SECS_PER_HOUR, 2) - return locale.describe("hours", hours, only_distance=only_distance) + return locale_cls.describe( + "hours", hours, only_distance=only_distance + ) elif diff < self._SECS_PER_DAY * 2: - return locale.describe("day", sign, only_distance=only_distance) + return locale_cls.describe("day", sign, only_distance=only_distance) elif diff < self._SECS_PER_WEEK: days = sign * max(delta_second // self._SECS_PER_DAY, 2) - return locale.describe("days", days, only_distance=only_distance) + return locale_cls.describe( + "days", days, only_distance=only_distance + ) elif diff < self._SECS_PER_WEEK * 2: - return locale.describe("week", sign, only_distance=only_distance) + return locale_cls.describe( + "week", sign, only_distance=only_distance + ) elif diff < self._SECS_PER_MONTH: weeks = sign * max(delta_second // self._SECS_PER_WEEK, 2) - return locale.describe("weeks", weeks, only_distance=only_distance) + return locale_cls.describe( + "weeks", weeks, only_distance=only_distance + ) elif diff < self._SECS_PER_MONTH * 2: - return locale.describe("month", sign, only_distance=only_distance) + return locale_cls.describe( + "month", sign, only_distance=only_distance + ) elif diff < self._SECS_PER_YEAR: # TODO revisit for humanization during leap years self_months = self._datetime.year * 12 + self._datetime.month @@ -1221,15 +1235,19 @@ def humanize( months = sign * max(abs(other_months - self_months), 2) - return locale.describe( + return locale_cls.describe( "months", months, only_distance=only_distance ) elif diff < self._SECS_PER_YEAR * 2: - return locale.describe("year", sign, only_distance=only_distance) + return locale_cls.describe( + "year", sign, only_distance=only_distance + ) else: years = sign * max(delta_second // self._SECS_PER_YEAR, 2) - return locale.describe("years", years, only_distance=only_distance) + return locale_cls.describe( + "years", years, only_distance=only_distance + ) elif isinstance(granularity, str): granularity = cast(TimeFrameLiteral, granularity) # type: ignore[assignment] @@ -1237,7 +1255,7 @@ def humanize( if granularity == "second": delta = sign * float(delta_second) if abs(delta) < 2: - return locale.describe("now", only_distance=only_distance) + return locale_cls.describe("now", only_distance=only_distance) elif granularity == "minute": delta = sign * delta_second / self._SECS_PER_MINUTE elif granularity == "hour": @@ -1260,7 +1278,9 @@ def humanize( if trunc(abs(delta)) != 1: granularity += "s" # type: ignore[assignment] - return locale.describe(granularity, delta, only_distance=only_distance) + return locale_cls.describe( + granularity, delta, only_distance=only_distance + ) else: @@ -1304,7 +1324,9 @@ def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." ) - return locale.describe_multi(timeframes, only_distance=only_distance) + return locale_cls.describe_multi( + timeframes, only_distance=only_distance + ) except KeyError as e: raise ValueError( @@ -1410,9 +1432,7 @@ def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow": # Add change value to the correct unit (incorporates the plurality that exists within timeframe i.e second v.s seconds) time_unit_to_change = str(unit) - time_unit_to_change += ( - "s" if (str(time_unit_to_change)[-1] != "s") else "" - ) + time_unit_to_change += "s" if time_unit_to_change[-1] != "s" else "" time_object_info[time_unit_to_change] = change_value unit_visited[time_unit_to_change] = True @@ -1663,7 +1683,8 @@ def isocalendar(self) -> Tuple[int, int, int]: """ - return self._datetime.isocalendar() + cal = tuple(self._datetime.isocalendar()) + return (cal[0], cal[1], cal[2]) def isoformat(self, sep: str = "T", timespec: str = "auto") -> str: """Returns an ISO 8601 formatted representation of the date and time. From 693076e1d5600f2d3b0cbb1e32c75d4dc0ae6030 Mon Sep 17 00:00:00 2001 From: Mohammad Khan Date: Sun, 20 Nov 2022 20:38:47 -0500 Subject: [PATCH 03/10] modify test_locales.py to work with cython Co-Authored by: Lukas Lemke <90070416+13MK3@users.noreply.github.com> - since cython doesn't support changing builtins at runtime --- tests/test_locales.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_locales.py b/tests/test_locales.py index 4bbbd3dc..dd7a3fb4 100644 --- a/tests/test_locales.py +++ b/tests/test_locales.py @@ -77,13 +77,10 @@ def test_get_locale_by_class_name(self, mocker): mock_locale_cls = mocker.Mock() mock_locale_obj = mock_locale_cls.return_value = mocker.Mock() - globals_fn = mocker.Mock() - globals_fn.return_value = {"NonExistentLocale": mock_locale_cls} - with pytest.raises(ValueError): arrow.locales.get_locale_by_class_name("NonExistentLocale") - mocker.patch.object(locales, "globals", globals_fn) + mocker.patch.object(locales, "NonExistentLocale", mock_locale_cls, create=True) result = arrow.locales.get_locale_by_class_name("NonExistentLocale") mock_locale_cls.assert_called_once_with() From 5d5fe3f2723d096c1f1cc1057aaeeeee28606f0a Mon Sep 17 00:00:00 2001 From: Mohammad Khan Date: Sun, 20 Nov 2022 20:41:36 -0500 Subject: [PATCH 04/10] add code coverage compatibility for cython Co-Authored by: Lukas Lemke <90070416+13MK3@users.noreply.github.com> --- setup.py | 5 ++++- tox.ini | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9b652003..f359fec7 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,7 @@ Extension( "*", ["arrow/*.py"], + define_macros=[("CYTHON_TRACE", "1")], ) ] @@ -54,6 +55,8 @@ "Bug Reports": "https://github.com/arrow-py/arrow/issues", "Documentation": "https://arrow.readthedocs.io", }, - ext_modules=cythonize(extensions, language_level="3"), + ext_modules=cythonize( + extensions, language_level="3", compiler_directives={"linetrace": True} + ), cmdclass={"build_ext": build_ext}, ) diff --git a/tox.ini b/tox.ini index 11d70cb2..167f40cb 100644 --- a/tox.ini +++ b/tox.ini @@ -47,3 +47,6 @@ include_trailing_comma = true [flake8] per-file-ignores = arrow/__init__.py:F401,tests/*:ANN001,ANN201 ignore = E203,E501,W503,ANN101,ANN102,ANN401 + +[coverage:run] +plugins = Cython.Coverage From 00253c85619d9f269f33d59274dbba4d175fb9d7 Mon Sep 17 00:00:00 2001 From: Mohammad Khan Date: Sun, 20 Nov 2022 20:43:31 -0500 Subject: [PATCH 05/10] add make target to compile cython Co-Authored by: Lukas Lemke <90070416+13MK3@users.noreply.github.com> --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f55a3dce..b99d5ce0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: auto test docs clean +.PHONY: auto test docs clean compile-cython auto: build310 @@ -17,6 +17,10 @@ build36 build37 build38 build39 build310: clean pip install -r requirements/requirements-docs.txt; \ pre-commit install +compile-cython: + . venv/bin/activate; \ + python setup.py build_ext --inplace + test: rm -f .coverage coverage.xml . venv/bin/activate; \ @@ -45,7 +49,7 @@ clean: clean-dist clean-dist: rm -rf dist build .egg .eggs arrow.egg-info -build-dist: +build-dist: compile-cython . venv/bin/activate; \ pip install -U pip setuptools twine wheel; \ python setup.py sdist bdist_wheel From c7e8ee0b6bf0e7a7915c92015cc7126635232a94 Mon Sep 17 00:00:00 2001 From: Mohammad Khan Date: Sun, 20 Nov 2022 20:44:07 -0500 Subject: [PATCH 06/10] add cython to requirements.txt Co-Authored by: Lukas Lemke <90070416+13MK3@users.noreply.github.com> --- requirements/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index bcdff0e8..14ac874f 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,2 +1,3 @@ +Cython>=3.0.0a11 python-dateutil>=2.7.0 typing_extensions; python_version < '3.8' From 64fdceceae66b9c8bed54514a183d8ca8241229a Mon Sep 17 00:00:00 2001 From: Mohammad Khan Date: Mon, 21 Nov 2022 14:16:43 -0500 Subject: [PATCH 07/10] add cython to setup.py requirements Co-Authored by: Lukas Lemke <90070416+13MK3@users.noreply.github.com> --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index f359fec7..b2ecec20 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ zip_safe=False, python_requires=">=3.6", install_requires=[ + "Cython>=3.0.0a11", "python-dateutil>=2.7.0", "typing_extensions; python_version<'3.8'", ], From 006f27bd75d9cb1fdc1fa0d772e3b553bf13253c Mon Sep 17 00:00:00 2001 From: Mohammad Khan Date: Mon, 21 Nov 2022 14:40:42 -0500 Subject: [PATCH 08/10] add improvements to Makefile Co-Authored by: Lukas Lemke <90070416+13MK3@users.noreply.github.com> - automatically compile cython when running make build3x - add target to clean .c and binary files produced from compiling cython --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b99d5ce0..04e768b6 100644 --- a/Makefile +++ b/Makefile @@ -15,12 +15,16 @@ build36 build37 build38 build39 build310: clean pip install -U pip setuptools wheel; \ pip install -r requirements/requirements-tests.txt; \ pip install -r requirements/requirements-docs.txt; \ - pre-commit install + pre-commit install; \ + python setup.py build_ext --inplace compile-cython: . venv/bin/activate; \ python setup.py build_ext --inplace +clean-cython: + rm -f ./arrow/*.c ./arrow/*.so ./arrow/*.pyd + test: rm -f .coverage coverage.xml . venv/bin/activate; \ @@ -42,7 +46,7 @@ live-docs: clean-docs . venv/bin/activate; \ sphinx-autobuild docs docs/_build/html -clean: clean-dist +clean: clean-dist clean-cython rm -rf venv .pytest_cache ./**/__pycache__ rm -f .coverage coverage.xml ./**/*.pyc From 4182e95835f85a7e2eab11501bf91308a8112d61 Mon Sep 17 00:00:00 2001 From: Mohammad Khan Date: Mon, 21 Nov 2022 15:02:52 -0500 Subject: [PATCH 09/10] Revert "add cython to setup.py requirements" This reverts commit 64fdceceae66b9c8bed54514a183d8ca8241229a. --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index b2ecec20..f359fec7 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,6 @@ zip_safe=False, python_requires=">=3.6", install_requires=[ - "Cython>=3.0.0a11", "python-dateutil>=2.7.0", "typing_extensions; python_version<'3.8'", ], From 3d416be064851435b31da8b4df333b89892b8db8 Mon Sep 17 00:00:00 2001 From: "Kristijan \"Fremen\" Velkovski" Date: Wed, 23 Nov 2022 11:59:36 -0600 Subject: [PATCH 10/10] add Cython to tox github deps --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 34d9c4f2..ef9f98ee 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -43,7 +43,7 @@ jobs: - name: Install dependencies run: | pip install -U pip setuptools wheel - pip install -U tox tox-gh-actions + pip install -U tox tox-gh-actions "Cython>=3.0.0a11" - name: Test with tox run: tox - name: Upload coverage to Codecov