From 0d2c163b8ad58f7c4ce14c9f55754b2e658afb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sat, 16 Dec 2023 14:26:01 +0100 Subject: [PATCH] Make the test helpers opt-in via a test extra --- poetry.lock | 5 +- pyproject.toml | 25 +++-- src/pendulum/testing/traveller.py | 178 +++++++++++++++++------------- 3 files changed, 117 insertions(+), 91 deletions(-) diff --git a/poetry.lock b/poetry.lock index d8e8432b..b792b1df 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1235,7 +1235,10 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +[extras] +test = ["time-machine"] + [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "8ae5ffe0dadc93c6eb7f15485f176a208c87508c323a381dfd1d834e3d1069bb" +content-hash = "8df704933b8ad4ffb92d760c1215dfe15be3227a5d65d93f3a5cac8a2035565d" diff --git a/pyproject.toml b/pyproject.toml index a4361d61..7d7705e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ version = "3.0.0b1" description = "Python datetimes made easy" readme = "README.rst" requires-python = ">=3.8" -license = {text = "MIT License"} -authors = [{name = "Sébastien Eustace", email="sebastien@eustace.io>"}] +license = { text = "MIT License" } +authors = [{ name = "Sébastien Eustace", email = "sebastien@eustace.io>" }] keywords = ['datetime', 'date', 'time'] classifiers = [ @@ -18,7 +18,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", ] -dependencies =[ +dependencies = [ "python-dateutil>=2.6", "tzdata>=2020.1", 'backports.zoneinfo>=0.2.1; python_version < "3.9"', @@ -49,14 +49,14 @@ keywords = ['datetime', 'date', 'time'] python = ">=3.8" python-dateutil = ">=2.6" "backports.zoneinfo" = { version = ">=0.2.1", python = "<3.9" } -time-machine = { version = ">=2.6.0", markers = "implementation_name != 'pypy'" } +time-machine = { version = ">=2.6.0", markers = "implementation_name != 'pypy'", optional = true } tzdata = ">=2020.1" importlib-resources = { version = ">=5.9.0", python = "<3.9" } [tool.poetry.group.test.dependencies] pytest = "^7.1.2" pytz = ">=2022.1" -time-machine = "^2.7.1" +time-machine = ">=2.6.0" pytest-benchmark = "^4.0.0" [tool.poetry.group.doc.dependencies] @@ -75,7 +75,7 @@ types-pytz = ">=2022.7.1.2" [tool.poetry.group.dev.dependencies] babel = "^2.10.3" -cleo = {version = "^2.0.1", python = ">=3.8,<4.0"} +cleo = { version = "^2.0.1", python = ">=3.8,<4.0" } tox = "^4.0.0" [tool.poetry.group.benchmark.dependencies] @@ -84,6 +84,9 @@ pytest-codspeed = "^1.2.2" [tool.poetry.group.build.dependencies] maturin = ">=1.0,<2.0" +[tool.poetry.extras] +test = ["time-machine"] + [tool.maturin] module-name = "pendulum._pendulum" @@ -96,18 +99,18 @@ unfixable = [ target-version = "py38" line-length = 88 extend-select = [ - "B", # flake8-bugbear - "C4", # flake8-comprehensions + "B", # flake8-bugbear + "C4", # flake8-comprehensions "ERA", # flake8-eradicate/eradicate - "I", # isort - "N", # pep8-naming + "I", # isort + "N", # pep8-naming "PIE", # flake8-pie "PGH", # pygrep "RUF", # ruff checks "SIM", # flake8-simplify "TCH", # flake8-type-checking "TID", # flake8-tidy-imports - "UP", # pyupgrade + "UP", # pyupgrade ] ignore = [ "B904", # use 'raise ... from err' diff --git a/src/pendulum/testing/traveller.py b/src/pendulum/testing/traveller.py index d1be3bb6..3ef3af4b 100644 --- a/src/pendulum/testing/traveller.py +++ b/src/pendulum/testing/traveller.py @@ -18,10 +18,10 @@ def __init__(self, datetime_class: type[DateTime] = DateTime) -> None: self._datetime_class: type[DateTime] = datetime_class def freeze(self) -> Self: - raise NotImplementedError() + raise self._not_implemented() def travel_back(self) -> Self: - raise NotImplementedError() + raise self._not_implemented() def travel( self, @@ -34,10 +34,10 @@ def travel( seconds: int = 0, microseconds: int = 0, ) -> Self: - raise NotImplementedError() + raise self._not_implemented() def travel_to(self, dt: DateTime, *, freeze: bool = False) -> Self: - raise NotImplementedError() + raise self._not_implemented() def __enter__(self) -> Self: return self @@ -50,103 +50,123 @@ def __exit__( ) -> None: ... + def _not_implemented(self) -> NotImplementedError: + return NotImplementedError() + if not PYPY: - import time_machine + try: + import time_machine + except ImportError: + time_machine = None # type: ignore[assignment] - class Traveller(BaseTraveller): - def __init__(self, datetime_class: type[DateTime] = DateTime) -> None: - super().__init__(datetime_class) + if time_machine is not None: - self._started: bool = False - self._traveller: time_machine.travel | None = None - self._coordinates: time_machine.Coordinates | None = None + class Traveller(BaseTraveller): + def __init__(self, datetime_class: type[DateTime] = DateTime) -> None: + super().__init__(datetime_class) - def freeze(self) -> Self: - if self._started: - cast(time_machine.Coordinates, self._coordinates).move_to( - self._datetime_class.now(), tick=False - ) - else: - self._start(freeze=True) + self._started: bool = False + self._traveller: time_machine.travel | None = None + self._coordinates: time_machine.Coordinates | None = None - return self + def freeze(self) -> Self: + if self._started: + cast(time_machine.Coordinates, self._coordinates).move_to( + self._datetime_class.now(), tick=False + ) + else: + self._start(freeze=True) - def travel_back(self) -> Self: - if not self._started: return self - cast(time_machine.travel, self._traveller).stop() - self._coordinates = None - self._traveller = None - self._started = False - - return self - - def travel( - self, - years: int = 0, - months: int = 0, - weeks: int = 0, - days: int = 0, - hours: int = 0, - minutes: int = 0, - seconds: int = 0, - microseconds: int = 0, - *, - freeze: bool = False, - ) -> Self: - self._start(freeze=freeze) - - cast(time_machine.Coordinates, self._coordinates).move_to( - self._datetime_class.now().add( - years=years, - months=months, - weeks=weeks, - days=days, - hours=hours, - minutes=minutes, - seconds=seconds, - microseconds=microseconds, + def travel_back(self) -> Self: + if not self._started: + return self + + cast(time_machine.travel, self._traveller).stop() + self._coordinates = None + self._traveller = None + self._started = False + + return self + + def travel( + self, + years: int = 0, + months: int = 0, + weeks: int = 0, + days: int = 0, + hours: int = 0, + minutes: int = 0, + seconds: int = 0, + microseconds: int = 0, + *, + freeze: bool = False, + ) -> Self: + self._start(freeze=freeze) + + cast(time_machine.Coordinates, self._coordinates).move_to( + self._datetime_class.now().add( + years=years, + months=months, + weeks=weeks, + days=days, + hours=hours, + minutes=minutes, + seconds=seconds, + microseconds=microseconds, + ) ) - ) - return self + return self - def travel_to(self, dt: DateTime, *, freeze: bool = False) -> Self: - self._start(freeze=freeze) + def travel_to(self, dt: DateTime, *, freeze: bool = False) -> Self: + self._start(freeze=freeze) - cast(time_machine.Coordinates, self._coordinates).move_to(dt) + cast(time_machine.Coordinates, self._coordinates).move_to(dt) - return self + return self - def _start(self, freeze: bool = False) -> None: - if self._started: - return + def _start(self, freeze: bool = False) -> None: + if self._started: + return - if not self._traveller: - self._traveller = time_machine.travel( - self._datetime_class.now(), tick=not freeze - ) + if not self._traveller: + self._traveller = time_machine.travel( + self._datetime_class.now(), tick=not freeze + ) - self._coordinates = self._traveller.start() + self._coordinates = self._traveller.start() - self._started = True + self._started = True - def __enter__(self) -> Self: - self._start() + def __enter__(self) -> Self: + self._start() - return self + return self - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType, - ) -> None: - self.travel_back() + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType, + ) -> None: + self.travel_back() + + else: + + class Traveller(BaseTraveller): # type: ignore[no-redef] + def _not_implemented(self) -> NotImplementedError: + return NotImplementedError( + "Time travelling is an optional feature. " + 'You can add it by installing Pendulum with the "test" extra.' + ) else: class Traveller(BaseTraveller): # type: ignore[no-redef] - ... + def _not_implemented(self) -> NotImplementedError: + return NotImplementedError( + "Time travelling is not supported on the PyPy Python implementation." + )