Skip to content

Commit

Permalink
Make the test helpers opt-in via a test extra (#778)
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater authored Dec 16, 2023
1 parent 482bb80 commit f6a61db
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 91 deletions.
5 changes: 4 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 14 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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"',
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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"

Expand All @@ -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'
Expand Down
178 changes: 99 additions & 79 deletions src/pendulum/testing/traveller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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."
)

0 comments on commit f6a61db

Please sign in to comment.