From db25da676d4d8bf7db000051e09590ce7fae2512 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Thu, 1 Dec 2022 13:41:56 -0600 Subject: [PATCH 01/12] Update mypy and fix issues --- .github/workflows/tests.yml | 10 ++++- .pre-commit-config.yaml | 11 ------ pendulum/__init__.py | 2 +- pendulum/_extensions/helpers.py | 9 +---- pendulum/date.py | 18 +++++++-- pendulum/datetime.py | 9 +++-- pendulum/helpers.py | 4 +- pendulum/interval.py | 2 +- pendulum/locales/locale.py | 6 +-- pendulum/parser.py | 2 +- pendulum/parsing/__init__.py | 4 +- pendulum/time.py | 9 +++-- pendulum/tz/__init__.py | 11 ++---- pendulum/tz/local_timezone.py | 2 +- pendulum/utils/_compat.py | 6 ++- poetry.lock | 68 ++++++++++++++++++++++++++------- pyproject.toml | 7 +++- 17 files changed, 113 insertions(+), 67 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 341859e8..be7e115c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,8 +64,14 @@ jobs: if: steps.cache.outputs.cache-hit == 'true' && matrix.os != 'MacOS' run: timeout 10s poetry run pip --version || rm -rf .venv - - name: Install dependencies - run: poetry install --only main --only test -vvv + - name: Install typing dependencies + run: poetry install --only main --only testing --only typing --sync -vvv + + - name: Run type checking + run: poetry run mypy + + - name: Install runtime and testing dependencies + run: poetry install --only main --only test --sync -vvv - name: Test Pure Python run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ca4f1607..2ee2fd44 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -58,14 +58,3 @@ repos: exclude: ^build\.py$ args: - --py37-plus - - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.982 - hooks: - - id: mypy - pass_filenames: false - exclude: ^build\.py$ - additional_dependencies: - - pytest>=7.1.2 - - types-backports - - types-python-dateutil diff --git a/pendulum/__init__.py b/pendulum/__init__.py index 44912442..891774d5 100644 --- a/pendulum/__init__.py +++ b/pendulum/__init__.py @@ -73,7 +73,7 @@ def _safe_timezone( elif isinstance(obj, _datetime.tzinfo): # zoneinfo if hasattr(obj, "key"): - obj = obj.key # type: ignore + obj = obj.key # pytz elif hasattr(obj, "localize"): obj = obj.zone # type: ignore diff --git a/pendulum/_extensions/helpers.py b/pendulum/_extensions/helpers.py index e9839507..e0b16a33 100644 --- a/pendulum/_extensions/helpers.py +++ b/pendulum/_extensions/helpers.py @@ -3,7 +3,7 @@ import datetime import math -from collections import namedtuple +from typing import NamedTuple from typing import cast from pendulum.constants import DAY_OF_WEEK_TABLE @@ -25,12 +25,7 @@ from pendulum.utils._compat import zoneinfo -class PreciseDiff( - namedtuple( - "PreciseDiff", - "years months days " "hours minutes seconds microseconds " "total_days", - ) -): +class PreciseDiff(NamedTuple): years: int months: int days: int diff --git a/pendulum/date.py b/pendulum/date.py index f2aec479..bf5ec7eb 100644 --- a/pendulum/date.py +++ b/pendulum/date.py @@ -2,11 +2,13 @@ import calendar import math +import sys from datetime import date from datetime import datetime from datetime import timedelta from typing import NoReturn +from typing import TypeVar from typing import cast from typing import overload @@ -27,6 +29,8 @@ from pendulum.interval import Interval from pendulum.mixins.default import FormattableMixin +_D = TypeVar("_D", bound="Date") + class Date(FormattableMixin, date): # Names of days of the week @@ -266,9 +270,17 @@ def __sub__(self, delta: timedelta) -> Date: def __sub__(self, dt: datetime) -> NoReturn: ... - @overload - def __sub__(self, dt: Date) -> Interval: - ... + if sys.version_info >= (3, 8): + + @overload + def __sub__(self: _D, dt: _D) -> Interval: + ... + + else: + + @overload + def __sub__(self, dt: date) -> Interval: + ... def __sub__(self, other: timedelta | date) -> Date | Interval: if isinstance(other, timedelta): diff --git a/pendulum/datetime.py b/pendulum/datetime.py index 52ad3cc7..a2178faf 100644 --- a/pendulum/datetime.py +++ b/pendulum/datetime.py @@ -41,7 +41,8 @@ from pendulum.utils._compat import PY38 if TYPE_CHECKING: - from typing import Literal + from typing_extensions import Literal + from typing_extensions import SupportsIndex class DateTime(datetime.datetime, Date): @@ -1329,7 +1330,7 @@ def __getnewargs__(self) -> tuple[DateTime]: return (self,) def _getstate( - self, protocol: int = 3 + self, protocol: SupportsIndex = 3 ) -> tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]: return ( self.year, @@ -1349,8 +1350,8 @@ def __reduce__( ]: return self.__reduce_ex__(2) - def __reduce_ex__( # type: ignore[override] - self, protocol: int + def __reduce_ex__( + self, protocol: SupportsIndex ) -> tuple[ type[DateTime], tuple[int, int, int, int, int, int, int, datetime.tzinfo | None] ]: diff --git a/pendulum/helpers.py b/pendulum/helpers.py index 13b7f221..d468e399 100644 --- a/pendulum/helpers.py +++ b/pendulum/helpers.py @@ -40,12 +40,12 @@ from pendulum._extensions._helpers import timestamp from pendulum._extensions._helpers import week_day except ImportError: - from pendulum._extensions.helpers import PreciseDiff # type: ignore[misc] + from pendulum._extensions.helpers import PreciseDiff # type: ignore[assignment] from pendulum._extensions.helpers import days_in_year from pendulum._extensions.helpers import is_leap from pendulum._extensions.helpers import is_long_year from pendulum._extensions.helpers import local_time - from pendulum._extensions.helpers import precise_diff # type: ignore[misc] + from pendulum._extensions.helpers import precise_diff # type: ignore[assignment] from pendulum._extensions.helpers import timestamp from pendulum._extensions.helpers import week_day diff --git a/pendulum/interval.py b/pendulum/interval.py index f20042bf..30da16f0 100644 --- a/pendulum/interval.py +++ b/pendulum/interval.py @@ -18,7 +18,7 @@ from pendulum.helpers import precise_diff if TYPE_CHECKING: - from typing import SupportsIndex + from typing_extensions import SupportsIndex from pendulum.helpers import PreciseDiff from pendulum.locales.locale import Locale # noqa diff --git a/pendulum/locales/locale.py b/pendulum/locales/locale.py index 637509a1..dba73bdf 100644 --- a/pendulum/locales/locale.py +++ b/pendulum/locales/locale.py @@ -4,14 +4,10 @@ from pathlib import Path import re -import sys from typing import Any, cast from typing import Dict -if sys.version_info >= (3, 9): - from importlib import resources -else: - import importlib_resources as resources +from pendulum.utils._compat import resources class Locale: diff --git a/pendulum/parser.py b/pendulum/parser.py index 77900e20..1b050621 100644 --- a/pendulum/parser.py +++ b/pendulum/parser.py @@ -109,7 +109,7 @@ def _parse(text: str, **options: t.Any) -> Date | DateTime | Time | Duration | I ), ) - if CDuration and isinstance(parsed, CDuration): + if CDuration and isinstance(parsed, CDuration): # type: ignore[truthy-function] return pendulum.duration( years=parsed.years, months=parsed.months, diff --git a/pendulum/parsing/__init__.py b/pendulum/parsing/__init__.py index 81c876f5..ca465926 100644 --- a/pendulum/parsing/__init__.py +++ b/pendulum/parsing/__init__.py @@ -26,8 +26,8 @@ from pendulum.parsing._iso8601 import Duration from pendulum.parsing._iso8601 import parse_iso8601 except ImportError: - from pendulum.duration import Duration # type: ignore[misc] - from pendulum.parsing.iso8601 import parse_iso8601 # type: ignore[misc] + from pendulum.duration import Duration # type: ignore[assignment] + from pendulum.parsing.iso8601 import parse_iso8601 # type: ignore[assignment] COMMON = re.compile( # Date (optional) # noqa: E800 diff --git a/pendulum/time.py b/pendulum/time.py index f979e254..f70cf16a 100644 --- a/pendulum/time.py +++ b/pendulum/time.py @@ -19,7 +19,8 @@ from pendulum.mixins.default import FormattableMixin if TYPE_CHECKING: - from typing import Literal + from typing_extensions import Literal + from typing_extensions import SupportsIndex class Time(FormattableMixin, time): @@ -281,7 +282,7 @@ def __getnewargs__(self) -> tuple[Time]: return (self,) def _get_state( - self, protocol: int = 3 + self, protocol: SupportsIndex = 3 ) -> tuple[int, int, int, int, datetime.tzinfo | None]: tz = self.tzinfo @@ -292,8 +293,8 @@ def __reduce__( ) -> tuple[type[Time], tuple[int, int, int, int, datetime.tzinfo | None]]: return self.__reduce_ex__(2) - def __reduce_ex__( # type: ignore[override] - self, protocol: int + def __reduce_ex__( + self, protocol: SupportsIndex ) -> tuple[type[Time], tuple[int, int, int, int, datetime.tzinfo | None]]: return self.__class__, self._get_state(protocol) diff --git a/pendulum/tz/__init__.py b/pendulum/tz/__init__.py index 45c9855d..564d07aa 100644 --- a/pendulum/tz/__init__.py +++ b/pendulum/tz/__init__.py @@ -1,6 +1,7 @@ from __future__ import annotations -import sys +from pathlib import Path +from typing import cast from pendulum.tz.local_timezone import get_local_timezone from pendulum.tz.local_timezone import set_local_timezone @@ -8,11 +9,7 @@ from pendulum.tz.timezone import UTC from pendulum.tz.timezone import FixedTimezone from pendulum.tz.timezone import Timezone - -if sys.version_info >= (3, 9): - from importlib import resources -else: - import importlib_resources as resources +from pendulum.utils._compat import resources PRE_TRANSITION = "pre" POST_TRANSITION = "post" @@ -27,7 +24,7 @@ def timezones() -> tuple[str, ...]: global _timezones if _timezones is None: - with resources.files("tzdata").joinpath("zones").open() as f: + with cast(Path, resources.files("tzdata").joinpath("zones")).open() as f: _timezones = tuple(tz.strip() for tz in f.readlines()) return _timezones diff --git a/pendulum/tz/local_timezone.py b/pendulum/tz/local_timezone.py index 28b07ba7..e45bc858 100644 --- a/pendulum/tz/local_timezone.py +++ b/pendulum/tz/local_timezone.py @@ -141,7 +141,7 @@ def _get_windows_timezone() -> Timezone: else: - def _get_windows_timezone() -> Timezone: + def _get_windows_timezone() -> Timezone: # type: ignore[empty-body] ... diff --git a/pendulum/utils/_compat.py b/pendulum/utils/_compat.py index 81b0a308..f4ff0f85 100644 --- a/pendulum/utils/_compat.py +++ b/pendulum/utils/_compat.py @@ -6,8 +6,12 @@ PY38 = sys.version_info[:2] >= (3, 8) if sys.version_info < (3, 9): + import importlib_resources as resources + from backports import zoneinfo else: import zoneinfo -__all__ = ["zoneinfo"] + from importlib import resources + +__all__ = ["resources", "zoneinfo"] diff --git a/poetry.lock b/poetry.lock index 22d75ed1..2aac76c1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "atomicwrites" @@ -553,6 +553,58 @@ watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] +[[package]] +name = "mypy" +version = "0.991" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, + {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, + {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, + {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, + {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, + {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, + {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, + {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, + {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, + {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, + {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, + {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, + {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, + {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, + {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, + {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, + {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, + {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, + {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, + {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, + {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, + {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, + {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, + {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, + {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, + {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, + {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, + {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, + {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, + {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, +] + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + [[package]] name = "mypy-extensions" version = "0.4.3" @@ -1196,18 +1248,6 @@ files = [ {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] -[[package]] -name = "types-backports" -version = "0.1.3" -description = "Typing stubs for backports" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "types-backports-0.1.3.tar.gz", hash = "sha256:f4b7206c073df88d6200891e3d27506185fd60cda66fb289737b2fa92c0010cf"}, - {file = "types_backports-0.1.3-py2.py3-none-any.whl", hash = "sha256:dafcd61848081503e738a7768872d1dd6c018401b4d2a1cfb608ea87ec9864b9"}, -] - [[package]] name = "types-python-dateutil" version = "2.8.19" @@ -1323,4 +1363,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "c8ac8cbd6ca3a6f7be217c160e93b23f7bf89056ca9cd1b66febc70c4c809e10" +content-hash = "4644fa648646ad4fd481913b06df287d6c553ee92bd5af771ebc0d1cc1bb52ba" diff --git a/pyproject.toml b/pyproject.toml index 63cfac55..05a885db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,9 @@ markdown-include = "^0.5.1" black = { version = "^22.6.0", markers = "implementation_name != 'pypy'" } isort = "^5.10.1" pre-commit = "^2.20.0" -types-backports = "^0.1.3" + +[tool.poetry.group.typing.dependencies] +mypy = "0.991" types-python-dateutil = "^2.8.19" [tool.poetry.group.dev.dependencies] @@ -97,6 +99,9 @@ strict = true files = "pendulum, tests" show_error_codes = true pretty = true +exclude = [ + "^build\\.py$" +] # The following whitelist is used to allow for incremental adoption # of Mypy. Modules should be removed from this whitelist as and when From 3b7243391171f339f0f7775ea6f360a8d795ec73 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Thu, 1 Dec 2022 15:18:02 -0600 Subject: [PATCH 02/12] Fix install invocation --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index be7e115c..19bf413d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -65,7 +65,7 @@ jobs: run: timeout 10s poetry run pip --version || rm -rf .venv - name: Install typing dependencies - run: poetry install --only main --only testing --only typing --sync -vvv + run: poetry install --only main --only test --only typing -vvv - name: Run type checking run: poetry run mypy From 5307290a2c4209314aa00952dcc2d406f4cb95f6 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Thu, 1 Dec 2022 15:47:34 -0600 Subject: [PATCH 03/12] Remove importing _winreg --- pendulum/tz/local_timezone.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pendulum/tz/local_timezone.py b/pendulum/tz/local_timezone.py index e45bc858..0965768e 100644 --- a/pendulum/tz/local_timezone.py +++ b/pendulum/tz/local_timezone.py @@ -14,10 +14,7 @@ from pendulum.tz.timezone import Timezone if sys.platform == "win32": - try: - import _winreg as winreg - except (ImportError, AttributeError): - import winreg + import winreg _mock_local_timezone = None _local_timezone = None @@ -141,8 +138,8 @@ def _get_windows_timezone() -> Timezone: else: - def _get_windows_timezone() -> Timezone: # type: ignore[empty-body] - ... + def _get_windows_timezone() -> Timezone: + raise NotImplementedError def _get_darwin_timezone() -> Timezone: From 8eb0f3f0480178c471f7943d7e15fa97c99d20de Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Thu, 1 Dec 2022 17:06:19 -0600 Subject: [PATCH 04/12] Remove namedtuple usage --- pendulum/_extensions/_helpers.pyi | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pendulum/_extensions/_helpers.pyi b/pendulum/_extensions/_helpers.pyi index 99a53978..f7763557 100644 --- a/pendulum/_extensions/_helpers.pyi +++ b/pendulum/_extensions/_helpers.pyi @@ -1,8 +1,8 @@ from __future__ import annotations -from collections import namedtuple from datetime import date from datetime import datetime +from typing import NamedTuple def days_in_year(year: int) -> int: ... def is_leap(year: int) -> bool: ... @@ -11,12 +11,7 @@ def local_time( unix_time: int, utc_offset: int, microseconds: int ) -> tuple[int, int, int, int, int, int, int]: ... -class PreciseDiff( - namedtuple( - "PreciseDiff", - "years months days " "hours minutes seconds microseconds " "total_days", - ) -): +class PreciseDiff(NamedTuple): years: int months: int days: int From 63b74146b4b3033e59ab8a03c6ae0af5a75e5627 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Fri, 3 Mar 2023 11:06:45 -0600 Subject: [PATCH 05/12] Update to mypy 1.0.1 --- pendulum/date.py | 109 ++++++++++++------------ pendulum/datetime.py | 145 +++++++++++++++----------------- pendulum/duration.py | 22 +++-- pendulum/helpers.py | 12 +-- pendulum/interval.py | 53 ++++++------ pendulum/parsing/_iso8601.pyi | 1 - pendulum/time.py | 7 +- pendulum/tz/timezone.py | 54 ++++++------ pendulum/utils/_compat.py | 6 +- pendulum/utils/_zoneinfo.py | 79 +++++++++++++++++ poetry.lock | 60 ++++++------- pyproject.toml | 2 +- tests/benchmarks/test_format.py | 2 +- 13 files changed, 314 insertions(+), 238 deletions(-) create mode 100644 pendulum/utils/_zoneinfo.py diff --git a/pendulum/date.py b/pendulum/date.py index bf5ec7eb..0e94121e 100644 --- a/pendulum/date.py +++ b/pendulum/date.py @@ -7,6 +7,7 @@ from datetime import date from datetime import datetime from datetime import timedelta +from typing import TYPE_CHECKING from typing import NoReturn from typing import TypeVar from typing import cast @@ -29,6 +30,10 @@ from pendulum.interval import Interval from pendulum.mixins.default import FormattableMixin +if TYPE_CHECKING: + from typing_extensions import Self + + _D = TypeVar("_D", bound="Date") @@ -50,7 +55,7 @@ class Date(FormattableMixin, date): def set( self, year: int | None = None, month: int | None = None, day: int | None = None - ) -> Date: + ) -> Self: return self.replace(year=year, month=month, day=day) @property @@ -114,7 +119,7 @@ def __repr__(self) -> str: # COMPARISONS - def closest(self, dt1: date, dt2: date) -> Date: + def closest(self, dt1: date, dt2: date) -> Self: """ Get the closest date from the instance. """ @@ -126,7 +131,7 @@ def closest(self, dt1: date, dt2: date) -> Date: return dt2 - def farthest(self, dt1: date, dt2: date) -> Date: + def farthest(self, dt1: date, dt2: date) -> Self: """ Get the farthest date from the instance. """ @@ -192,7 +197,7 @@ def is_anniversary(self, dt: date | None = None) -> bool: def add( self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0 - ) -> Date: + ) -> Self: """ Add duration to the instance. @@ -213,7 +218,7 @@ def add( def subtract( self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0 - ) -> Date: + ) -> Self: """ Remove duration from the instance. @@ -224,7 +229,7 @@ def subtract( """ return self.add(years=-years, months=-months, weeks=-weeks, days=-days) - def _add_timedelta(self, delta: timedelta) -> Date: + def _add_timedelta(self, delta: timedelta) -> Self: """ Add timedelta duration to the instance. @@ -240,7 +245,7 @@ def _add_timedelta(self, delta: timedelta) -> Date: return self.add(days=delta.days) - def _subtract_timedelta(self, delta: timedelta) -> Date: + def _subtract_timedelta(self, delta: timedelta) -> Self: """ Remove timedelta duration from the instance. @@ -256,33 +261,33 @@ def _subtract_timedelta(self, delta: timedelta) -> Date: return self.subtract(days=delta.days) - def __add__(self, other: timedelta) -> Date: + def __add__(self, other: timedelta) -> Self: if not isinstance(other, timedelta): return NotImplemented return self._add_timedelta(other) - @overload - def __sub__(self, delta: timedelta) -> Date: + @overload # type: ignore[override] + def __sub__(self, __delta: timedelta) -> Self: ... @overload - def __sub__(self, dt: datetime) -> NoReturn: + def __sub__(self, __dt: datetime) -> NoReturn: ... if sys.version_info >= (3, 8): @overload - def __sub__(self: _D, dt: _D) -> Interval: + def __sub__(self: _D, __dt: _D) -> Interval: ... else: @overload - def __sub__(self, dt: date) -> Interval: + def __sub__(self, __dt: date) -> Interval: ... - def __sub__(self, other: timedelta | date) -> Date | Interval: + def __sub__(self, other: timedelta | date) -> Self | Interval: if isinstance(other, timedelta): return self._subtract_timedelta(other) @@ -347,7 +352,7 @@ def diff_for_humans( # MODIFIERS - def start_of(self, unit: str) -> Date: + def start_of(self, unit: str) -> Self: """ Returns a copy of the instance with the time reset with the following rules: @@ -364,9 +369,9 @@ def start_of(self, unit: str) -> Date: if unit not in self._MODIFIERS_VALID_UNITS: raise ValueError(f'Invalid unit "{unit}" for start_of()') - return cast(Date, getattr(self, f"_start_of_{unit}")()) + return cast("Self", getattr(self, f"_start_of_{unit}")()) - def end_of(self, unit: str) -> Date: + def end_of(self, unit: str) -> Self: """ Returns a copy of the instance with the time reset with the following rules: @@ -382,45 +387,45 @@ def end_of(self, unit: str) -> Date: if unit not in self._MODIFIERS_VALID_UNITS: raise ValueError(f'Invalid unit "{unit}" for end_of()') - return cast(Date, getattr(self, f"_end_of_{unit}")()) + return cast("Self", getattr(self, f"_end_of_{unit}")()) - def _start_of_day(self) -> Date: + def _start_of_day(self) -> Self: """ Compatibility method. """ return self - def _end_of_day(self) -> Date: + def _end_of_day(self) -> Self: """ Compatibility method """ return self - def _start_of_month(self) -> Date: + def _start_of_month(self) -> Self: """ Reset the date to the first day of the month. """ return self.set(self.year, self.month, 1) - def _end_of_month(self) -> Date: + def _end_of_month(self) -> Self: """ Reset the date to the last day of the month. """ return self.set(self.year, self.month, self.days_in_month) - def _start_of_year(self) -> Date: + def _start_of_year(self) -> Self: """ Reset the date to the first day of the year. """ return self.set(self.year, 1, 1) - def _end_of_year(self) -> Date: + def _end_of_year(self) -> Self: """ Reset the date to the last day of the year. """ return self.set(self.year, 12, 31) - def _start_of_decade(self) -> Date: + def _start_of_decade(self) -> Self: """ Reset the date to the first day of the decade. """ @@ -428,7 +433,7 @@ def _start_of_decade(self) -> Date: return self.set(year, 1, 1) - def _end_of_decade(self) -> Date: + def _end_of_decade(self) -> Self: """ Reset the date to the last day of the decade. """ @@ -436,7 +441,7 @@ def _end_of_decade(self) -> Date: return self.set(year, 12, 31) - def _start_of_century(self) -> Date: + def _start_of_century(self) -> Self: """ Reset the date to the first day of the century. """ @@ -444,7 +449,7 @@ def _start_of_century(self) -> Date: return self.set(year, 1, 1) - def _end_of_century(self) -> Date: + def _end_of_century(self) -> Self: """ Reset the date to the last day of the century. """ @@ -452,7 +457,7 @@ def _end_of_century(self) -> Date: return self.set(year, 12, 31) - def _start_of_week(self) -> Date: + def _start_of_week(self) -> Self: """ Reset the date to the first day of the week. """ @@ -463,7 +468,7 @@ def _start_of_week(self) -> Date: return dt.start_of("day") - def _end_of_week(self) -> Date: + def _end_of_week(self) -> Self: """ Reset the date to the last day of the week. """ @@ -474,7 +479,7 @@ def _end_of_week(self) -> Date: return dt.end_of("day") - def next(self, day_of_week: int | None = None) -> Date: + def next(self, day_of_week: int | None = None) -> Self: """ Modify to the next occurrence of a given day of the week. If no day_of_week is provided, modify to the next occurrence @@ -495,7 +500,7 @@ def next(self, day_of_week: int | None = None) -> Date: return dt - def previous(self, day_of_week: int | None = None) -> Date: + def previous(self, day_of_week: int | None = None) -> Self: """ Modify to the previous occurrence of a given day of the week. If no day_of_week is provided, modify to the previous occurrence @@ -516,7 +521,7 @@ def previous(self, day_of_week: int | None = None) -> Date: return dt - def first_of(self, unit: str, day_of_week: int | None = None) -> Date: + def first_of(self, unit: str, day_of_week: int | None = None) -> Self: """ Returns an instance set to the first occurrence of a given day of the week in the current unit. @@ -531,9 +536,9 @@ def first_of(self, unit: str, day_of_week: int | None = None) -> Date: if unit not in ["month", "quarter", "year"]: raise ValueError(f'Invalid unit "{unit}" for first_of()') - return cast(Date, getattr(self, f"_first_of_{unit}")(day_of_week)) + return cast("Self", getattr(self, f"_first_of_{unit}")(day_of_week)) - def last_of(self, unit: str, day_of_week: int | None = None) -> Date: + def last_of(self, unit: str, day_of_week: int | None = None) -> Self: """ Returns an instance set to the last occurrence of a given day of the week in the current unit. @@ -548,9 +553,9 @@ def last_of(self, unit: str, day_of_week: int | None = None) -> Date: if unit not in ["month", "quarter", "year"]: raise ValueError(f'Invalid unit "{unit}" for first_of()') - return cast(Date, getattr(self, f"_last_of_{unit}")(day_of_week)) + return cast("Self", getattr(self, f"_last_of_{unit}")(day_of_week)) - def nth_of(self, unit: str, nth: int, day_of_week: int) -> Date: + def nth_of(self, unit: str, nth: int, day_of_week: int) -> Self: """ Returns a new instance set to the given occurrence of a given day of the week in the current unit. @@ -567,7 +572,7 @@ def nth_of(self, unit: str, nth: int, day_of_week: int) -> Date: if unit not in ["month", "quarter", "year"]: raise ValueError(f'Invalid unit "{unit}" for first_of()') - dt = cast(Date, getattr(self, f"_nth_of_{unit}")(nth, day_of_week)) + dt = cast("Self", getattr(self, f"_nth_of_{unit}")(nth, day_of_week)) if not dt: raise PendulumException( f"Unable to find occurence {nth}" @@ -576,7 +581,7 @@ def nth_of(self, unit: str, nth: int, day_of_week: int) -> Date: return dt - def _first_of_month(self, day_of_week: int) -> Date: + def _first_of_month(self, day_of_week: int) -> Self: """ Modify to the first occurrence of a given day of the week in the current month. If no day_of_week is provided, @@ -601,7 +606,7 @@ def _first_of_month(self, day_of_week: int) -> Date: return dt.set(day=day_of_month) - def _last_of_month(self, day_of_week: int | None = None) -> Date: + def _last_of_month(self, day_of_week: int | None = None) -> Self: """ Modify to the last occurrence of a given day of the week in the current month. If no day_of_week is provided, @@ -626,7 +631,7 @@ def _last_of_month(self, day_of_week: int | None = None) -> Date: return dt.set(day=day_of_month) - def _nth_of_month(self, nth: int, day_of_week: int) -> Date | None: + def _nth_of_month(self, nth: int, day_of_week: int) -> Self | None: """ Modify to the given occurrence of a given day of the week in the current month. If the calculated occurrence is outside, @@ -647,7 +652,7 @@ def _nth_of_month(self, nth: int, day_of_week: int) -> Date | None: return None - def _first_of_quarter(self, day_of_week: int | None = None) -> Date: + def _first_of_quarter(self, day_of_week: int | None = None) -> Self: """ Modify to the first occurrence of a given day of the week in the current quarter. If no day_of_week is provided, @@ -658,7 +663,7 @@ def _first_of_quarter(self, day_of_week: int | None = None) -> Date: "month", day_of_week ) - def _last_of_quarter(self, day_of_week: int | None = None) -> Date: + def _last_of_quarter(self, day_of_week: int | None = None) -> Self: """ Modify to the last occurrence of a given day of the week in the current quarter. If no day_of_week is provided, @@ -667,7 +672,7 @@ def _last_of_quarter(self, day_of_week: int | None = None) -> Date: """ return self.set(self.year, self.quarter * 3, 1).last_of("month", day_of_week) - def _nth_of_quarter(self, nth: int, day_of_week: int) -> Date | None: + def _nth_of_quarter(self, nth: int, day_of_week: int) -> Self | None: """ Modify to the given occurrence of a given day of the week in the current quarter. If the calculated occurrence is outside, @@ -690,7 +695,7 @@ def _nth_of_quarter(self, nth: int, day_of_week: int) -> Date | None: return self.set(self.year, dt.month, dt.day) - def _first_of_year(self, day_of_week: int | None = None) -> Date: + def _first_of_year(self, day_of_week: int | None = None) -> Self: """ Modify to the first occurrence of a given day of the week in the current year. If no day_of_week is provided, @@ -699,7 +704,7 @@ def _first_of_year(self, day_of_week: int | None = None) -> Date: """ return self.set(month=1).first_of("month", day_of_week) - def _last_of_year(self, day_of_week: int | None = None) -> Date: + def _last_of_year(self, day_of_week: int | None = None) -> Self: """ Modify to the last occurrence of a given day of the week in the current year. If no day_of_week is provided, @@ -708,7 +713,7 @@ def _last_of_year(self, day_of_week: int | None = None) -> Date: """ return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week) - def _nth_of_year(self, nth: int, day_of_week: int) -> Date | None: + def _nth_of_year(self, nth: int, day_of_week: int) -> Self | None: """ Modify to the given occurrence of a given day of the week in the current year. If the calculated occurrence is outside, @@ -729,7 +734,7 @@ def _nth_of_year(self, nth: int, day_of_week: int) -> Date | None: return self.set(self.year, dt.month, dt.day) - def average(self, dt: date | None = None) -> Date: + def average(self, dt: date | None = None) -> Self: """ Modify the current instance to the average of a given instance (default now) and the current instance. @@ -742,19 +747,19 @@ def average(self, dt: date | None = None) -> Date: # Native methods override @classmethod - def today(cls) -> Date: + def today(cls) -> Self: dt = date.today() return cls(dt.year, dt.month, dt.day) @classmethod - def fromtimestamp(cls, t: float) -> Date: + def fromtimestamp(cls, t: float) -> Self: dt = super().fromtimestamp(t) return cls(dt.year, dt.month, dt.day) @classmethod - def fromordinal(cls, n: int) -> Date: + def fromordinal(cls, n: int) -> Self: dt = super().fromordinal(n) return cls(dt.year, dt.month, dt.day) @@ -764,7 +769,7 @@ def replace( year: int | None = None, month: int | None = None, day: int | None = None, - ) -> Date: + ) -> Self: year = year if year is not None else self.year month = month if month is not None else self.month day = day if day is not None else self.day diff --git a/pendulum/datetime.py b/pendulum/datetime.py index a2178faf..7d5f090b 100644 --- a/pendulum/datetime.py +++ b/pendulum/datetime.py @@ -42,6 +42,7 @@ if TYPE_CHECKING: from typing_extensions import Literal + from typing_extensions import Self from typing_extensions import SupportsIndex @@ -91,7 +92,7 @@ def create( tz: str | float | Timezone | FixedTimezone | None | datetime.tzinfo = UTC, fold: int = 1, raise_on_unknown_times: bool = False, - ) -> DateTime: + ) -> Self: """ Creates a new DateTime instance from a specific date and time. """ @@ -119,18 +120,18 @@ def create( @overload @classmethod - def now(cls, tz: datetime.tzinfo | None = None) -> DateTime: + def now(cls, tz: datetime.tzinfo | None = None) -> Self: ... @overload @classmethod - def now(cls, tz: str | Timezone | FixedTimezone | None = None) -> DateTime: + def now(cls, tz: str | Timezone | FixedTimezone | None = None) -> Self: ... @classmethod def now( cls, tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = None - ) -> DateTime: + ) -> Self: """ Get a DateTime instance for the current date and time. """ @@ -156,14 +157,14 @@ def now( ) @classmethod - def utcnow(cls) -> DateTime: + def utcnow(cls) -> Self: """ Get a DateTime instance for the current date and time in UTC. """ return cls.now(UTC) @classmethod - def today(cls) -> DateTime: + def today(cls) -> Self: return cls.now() @classmethod @@ -182,7 +183,7 @@ def set( second: int | None = None, microsecond: int | None = None, tz: str | float | Timezone | FixedTimezone | datetime.tzinfo | None = None, - ) -> DateTime: + ) -> Self: if year is None: year = self.year if month is None: @@ -200,7 +201,7 @@ def set( if tz is None: tz = self.tz - return DateTime.create( + return self.__class__.create( year, month, day, hour, minute, second, microsecond, tz=tz ) @@ -287,7 +288,7 @@ def date(self) -> Date: def time(self) -> Time: return Time(self.hour, self.minute, self.second, self.microsecond) - def naive(self) -> DateTime: + def naive(self) -> Self: """ Return the DateTime without timezone information. """ @@ -301,7 +302,7 @@ def naive(self) -> DateTime: self.microsecond, ) - def on(self, year: int, month: int, day: int) -> DateTime: + def on(self, year: int, month: int, day: int) -> Self: """ Returns a new instance with the current date set to a different date. """ @@ -309,7 +310,7 @@ def on(self, year: int, month: int, day: int) -> DateTime: def at( self, hour: int, minute: int = 0, second: int = 0, microsecond: int = 0 - ) -> DateTime: + ) -> Self: """ Returns a new instance with the current time to a different time. """ @@ -317,7 +318,7 @@ def at( hour=hour, minute=minute, second=second, microsecond=microsecond ) - def in_timezone(self, tz: str | Timezone | FixedTimezone) -> DateTime: + def in_timezone(self, tz: str | Timezone | FixedTimezone) -> Self: """ Set the instance's timezone from a string or object. """ @@ -327,9 +328,9 @@ def in_timezone(self, tz: str | Timezone | FixedTimezone) -> DateTime: if not self.timezone: dt = dt.replace(fold=1) - return cast(DateTime, tz.convert(dt)) + return tz.convert(dt) - def in_tz(self, tz: str | Timezone | FixedTimezone) -> DateTime: + def in_tz(self, tz: str | Timezone | FixedTimezone) -> Self: """ Set the instance's timezone from a string or object. """ @@ -541,7 +542,7 @@ def add( minutes: int = 0, seconds: float = 0, microseconds: int = 0, - ) -> DateTime: + ) -> Self: """ Add a duration to the instance. @@ -578,7 +579,7 @@ def add( ) if units_of_variable_length or self.tz is None: - return DateTime.create( + return self.__class__.create( dt.year, dt.month, dt.day, @@ -624,7 +625,7 @@ def subtract( minutes: int = 0, seconds: float = 0, microseconds: int = 0, - ) -> DateTime: + ) -> Self: """ Remove duration from the instance. """ @@ -642,7 +643,7 @@ def subtract( # Adding a final underscore to the method name # to avoid errors for PyPy which already defines # a _add_timedelta method - def _add_timedelta_(self, delta: datetime.timedelta) -> DateTime: + def _add_timedelta_(self, delta: datetime.timedelta) -> Self: """ Add timedelta duration to the instance. """ @@ -664,7 +665,7 @@ def _add_timedelta_(self, delta: datetime.timedelta) -> DateTime: return self.add(seconds=delta.total_seconds()) - def _subtract_timedelta(self, delta: datetime.timedelta) -> DateTime: + def _subtract_timedelta(self, delta: datetime.timedelta) -> Self: """ Remove timedelta duration from the instance. """ @@ -723,7 +724,7 @@ def diff_for_humans( # type: ignore[override] return pendulum.format_diff(diff, is_now, absolute, locale) # Modifiers - def start_of(self, unit: str) -> DateTime: + def start_of(self, unit: str) -> Self: """ Returns a copy of the instance with the time reset with the following rules: @@ -741,9 +742,9 @@ def start_of(self, unit: str) -> DateTime: if unit not in self._MODIFIERS_VALID_UNITS: raise ValueError(f'Invalid unit "{unit}" for start_of()') - return cast(DateTime, getattr(self, f"_start_of_{unit}")()) + return cast("Self", getattr(self, f"_start_of_{unit}")()) - def end_of(self, unit: str) -> DateTime: + def end_of(self, unit: str) -> Self: """ Returns a copy of the instance with the time reset with the following rules: @@ -761,83 +762,83 @@ def end_of(self, unit: str) -> DateTime: if unit not in self._MODIFIERS_VALID_UNITS: raise ValueError(f'Invalid unit "{unit}" for end_of()') - return cast(DateTime, getattr(self, f"_end_of_{unit}")()) + return cast("Self", getattr(self, f"_end_of_{unit}")()) - def _start_of_second(self) -> DateTime: + def _start_of_second(self) -> Self: """ Reset microseconds to 0. """ return self.set(microsecond=0) - def _end_of_second(self) -> DateTime: + def _end_of_second(self) -> Self: """ Set microseconds to 999999. """ return self.set(microsecond=999999) - def _start_of_minute(self) -> DateTime: + def _start_of_minute(self) -> Self: """ Reset seconds and microseconds to 0. """ return self.set(second=0, microsecond=0) - def _end_of_minute(self) -> DateTime: + def _end_of_minute(self) -> Self: """ Set seconds to 59 and microseconds to 999999. """ return self.set(second=59, microsecond=999999) - def _start_of_hour(self) -> DateTime: + def _start_of_hour(self) -> Self: """ Reset minutes, seconds and microseconds to 0. """ return self.set(minute=0, second=0, microsecond=0) - def _end_of_hour(self) -> DateTime: + def _end_of_hour(self) -> Self: """ Set minutes and seconds to 59 and microseconds to 999999. """ return self.set(minute=59, second=59, microsecond=999999) - def _start_of_day(self) -> DateTime: + def _start_of_day(self) -> Self: """ Reset the time to 00:00:00. """ return self.at(0, 0, 0, 0) - def _end_of_day(self) -> DateTime: + def _end_of_day(self) -> Self: """ Reset the time to 23:59:59.999999. """ return self.at(23, 59, 59, 999999) - def _start_of_month(self) -> DateTime: + def _start_of_month(self) -> Self: """ Reset the date to the first day of the month and the time to 00:00:00. """ return self.set(self.year, self.month, 1, 0, 0, 0, 0) - def _end_of_month(self) -> DateTime: + def _end_of_month(self) -> Self: """ Reset the date to the last day of the month and the time to 23:59:59.999999. """ return self.set(self.year, self.month, self.days_in_month, 23, 59, 59, 999999) - def _start_of_year(self) -> DateTime: + def _start_of_year(self) -> Self: """ Reset the date to the first day of the year and the time to 00:00:00. """ return self.set(self.year, 1, 1, 0, 0, 0, 0) - def _end_of_year(self) -> DateTime: + def _end_of_year(self) -> Self: """ Reset the date to the last day of the year and the time to 23:59:59.999999. """ return self.set(self.year, 12, 31, 23, 59, 59, 999999) - def _start_of_decade(self) -> DateTime: + def _start_of_decade(self) -> Self: """ Reset the date to the first day of the decade and the time to 00:00:00. @@ -845,7 +846,7 @@ def _start_of_decade(self) -> DateTime: year = self.year - self.year % YEARS_PER_DECADE return self.set(year, 1, 1, 0, 0, 0, 0) - def _end_of_decade(self) -> DateTime: + def _end_of_decade(self) -> Self: """ Reset the date to the last day of the decade and the time to 23:59:59.999999. @@ -854,7 +855,7 @@ def _end_of_decade(self) -> DateTime: return self.set(year, 12, 31, 23, 59, 59, 999999) - def _start_of_century(self) -> DateTime: + def _start_of_century(self) -> Self: """ Reset the date to the first day of the century and the time to 00:00:00. @@ -863,7 +864,7 @@ def _start_of_century(self) -> DateTime: return self.set(year, 1, 1, 0, 0, 0, 0) - def _end_of_century(self) -> DateTime: + def _end_of_century(self) -> Self: """ Reset the date to the last day of the century and the time to 23:59:59.999999. @@ -872,7 +873,7 @@ def _end_of_century(self) -> DateTime: return self.set(year, 12, 31, 23, 59, 59, 999999) - def _start_of_week(self) -> DateTime: + def _start_of_week(self) -> Self: """ Reset the date to the first day of the week and the time to 00:00:00. @@ -884,7 +885,7 @@ def _start_of_week(self) -> DateTime: return dt.start_of("day") - def _end_of_week(self) -> DateTime: + def _end_of_week(self) -> Self: """ Reset the date to the last day of the week and the time to 23:59:59. @@ -896,7 +897,7 @@ def _end_of_week(self) -> DateTime: return dt.end_of("day") - def next(self, day_of_week: int | None = None, keep_time: bool = False) -> DateTime: + def next(self, day_of_week: int | None = None, keep_time: bool = False) -> Self: """ Modify to the next occurrence of a given day of the week. If no day_of_week is provided, modify to the next occurrence @@ -920,9 +921,7 @@ def next(self, day_of_week: int | None = None, keep_time: bool = False) -> DateT return dt - def previous( - self, day_of_week: int | None = None, keep_time: bool = False - ) -> DateTime: + def previous(self, day_of_week: int | None = None, keep_time: bool = False) -> Self: """ Modify to the previous occurrence of a given day of the week. If no day_of_week is provided, modify to the previous occurrence @@ -946,7 +945,7 @@ def previous( return dt - def first_of(self, unit: str, day_of_week: int | None = None) -> DateTime: + def first_of(self, unit: str, day_of_week: int | None = None) -> Self: """ Returns an instance set to the first occurrence of a given day of the week in the current unit. @@ -958,9 +957,9 @@ def first_of(self, unit: str, day_of_week: int | None = None) -> DateTime: if unit not in ["month", "quarter", "year"]: raise ValueError(f'Invalid unit "{unit}" for first_of()') - return cast(DateTime, getattr(self, f"_first_of_{unit}")(day_of_week)) + return cast("Self", getattr(self, f"_first_of_{unit}")(day_of_week)) - def last_of(self, unit: str, day_of_week: int | None = None) -> DateTime: + def last_of(self, unit: str, day_of_week: int | None = None) -> Self: """ Returns an instance set to the last occurrence of a given day of the week in the current unit. @@ -972,9 +971,9 @@ def last_of(self, unit: str, day_of_week: int | None = None) -> DateTime: if unit not in ["month", "quarter", "year"]: raise ValueError(f'Invalid unit "{unit}" for first_of()') - return cast(DateTime, getattr(self, f"_last_of_{unit}")(day_of_week)) + return cast("Self", getattr(self, f"_last_of_{unit}")(day_of_week)) - def nth_of(self, unit: str, nth: int, day_of_week: int) -> DateTime: + def nth_of(self, unit: str, nth: int, day_of_week: int) -> Self: """ Returns a new instance set to the given occurrence of a given day of the week in the current unit. @@ -987,9 +986,7 @@ def nth_of(self, unit: str, nth: int, day_of_week: int) -> DateTime: if unit not in ["month", "quarter", "year"]: raise ValueError(f'Invalid unit "{unit}" for first_of()') - dt = cast( - Optional[DateTime], getattr(self, f"_nth_of_{unit}")(nth, day_of_week) - ) + dt = cast(Optional["Self"], getattr(self, f"_nth_of_{unit}")(nth, day_of_week)) if not dt: raise PendulumException( f"Unable to find occurence {nth}" @@ -998,7 +995,7 @@ def nth_of(self, unit: str, nth: int, day_of_week: int) -> DateTime: return dt - def _first_of_month(self, day_of_week: int | None = None) -> DateTime: + def _first_of_month(self, day_of_week: int | None = None) -> Self: """ Modify to the first occurrence of a given day of the week in the current month. If no day_of_week is provided, @@ -1021,7 +1018,7 @@ def _first_of_month(self, day_of_week: int | None = None) -> DateTime: return dt.set(day=day_of_month) - def _last_of_month(self, day_of_week: int | None = None) -> DateTime: + def _last_of_month(self, day_of_week: int | None = None) -> Self: """ Modify to the last occurrence of a given day of the week in the current month. If no day_of_week is provided, @@ -1044,9 +1041,7 @@ def _last_of_month(self, day_of_week: int | None = None) -> DateTime: return dt.set(day=day_of_month) - def _nth_of_month( - self, nth: int, day_of_week: int | None = None - ) -> DateTime | None: + def _nth_of_month(self, nth: int, day_of_week: int | None = None) -> Self | None: """ Modify to the given occurrence of a given day of the week in the current month. If the calculated occurrence is outside, @@ -1067,7 +1062,7 @@ def _nth_of_month( return None - def _first_of_quarter(self, day_of_week: int | None = None) -> DateTime: + def _first_of_quarter(self, day_of_week: int | None = None) -> Self: """ Modify to the first occurrence of a given day of the week in the current quarter. If no day_of_week is provided, @@ -1078,7 +1073,7 @@ def _first_of_quarter(self, day_of_week: int | None = None) -> DateTime: "month", day_of_week ) - def _last_of_quarter(self, day_of_week: int | None = None) -> DateTime: + def _last_of_quarter(self, day_of_week: int | None = None) -> Self: """ Modify to the last occurrence of a given day of the week in the current quarter. If no day_of_week is provided, @@ -1087,9 +1082,7 @@ def _last_of_quarter(self, day_of_week: int | None = None) -> DateTime: """ return self.on(self.year, self.quarter * 3, 1).last_of("month", day_of_week) - def _nth_of_quarter( - self, nth: int, day_of_week: int | None = None - ) -> DateTime | None: + def _nth_of_quarter(self, nth: int, day_of_week: int | None = None) -> Self | None: """ Modify to the given occurrence of a given day of the week in the current quarter. If the calculated occurrence is outside, @@ -1112,7 +1105,7 @@ def _nth_of_quarter( return self.on(self.year, dt.month, dt.day).start_of("day") - def _first_of_year(self, day_of_week: int | None = None) -> DateTime: + def _first_of_year(self, day_of_week: int | None = None) -> Self: """ Modify to the first occurrence of a given day of the week in the current year. If no day_of_week is provided, @@ -1121,7 +1114,7 @@ def _first_of_year(self, day_of_week: int | None = None) -> DateTime: """ return self.set(month=1).first_of("month", day_of_week) - def _last_of_year(self, day_of_week: int | None = None) -> DateTime: + def _last_of_year(self, day_of_week: int | None = None) -> Self: """ Modify to the last occurrence of a given day of the week in the current year. If no day_of_week is provided, @@ -1130,7 +1123,7 @@ def _last_of_year(self, day_of_week: int | None = None) -> DateTime: """ return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week) - def _nth_of_year(self, nth: int, day_of_week: int | None = None) -> DateTime | None: + def _nth_of_year(self, nth: int, day_of_week: int | None = None) -> Self | None: """ Modify to the given occurrence of a given day of the week in the current year. If the calculated occurrence is outside, @@ -1153,7 +1146,7 @@ def _nth_of_year(self, nth: int, day_of_week: int | None = None) -> DateTime | N def average( # type: ignore[override] self, dt: datetime.datetime | None = None - ) -> DateTime: + ) -> Self: """ Modify the current instance to the average of a given instance (default now) and the current instance. @@ -1167,16 +1160,14 @@ def average( # type: ignore[override] ) @overload # type: ignore[override] - def __sub__(self, other: datetime.timedelta) -> DateTime: + def __sub__(self, other: datetime.timedelta) -> Self: ... @overload def __sub__(self, other: DateTime) -> Interval: ... - def __sub__( - self, other: datetime.datetime | datetime.timedelta - ) -> DateTime | Interval: + def __sub__(self, other: datetime.datetime | datetime.timedelta) -> Self | Interval: if isinstance(other, datetime.timedelta): return self._subtract_timedelta(other) @@ -1219,7 +1210,7 @@ def __rsub__(self, other: datetime.datetime) -> Interval: return self.diff(other, False) - def __add__(self, other: datetime.timedelta) -> DateTime: + def __add__(self, other: datetime.timedelta) -> Self: if not isinstance(other, datetime.timedelta): return NotImplemented @@ -1231,11 +1222,11 @@ def __add__(self, other: datetime.timedelta) -> DateTime: caller = inspect.stack()[1][3] if caller == "astimezone": - return cast(DateTime, super().__add__(other)) + return super().__add__(other) return self._add_timedelta_(other) - def __radd__(self, other: datetime.timedelta) -> DateTime: + def __radd__(self, other: datetime.timedelta) -> Self: return self.__add__(other) # Native methods override @@ -1265,7 +1256,7 @@ def combine( ) -> DateTime: return pendulum.instance(datetime.datetime.combine(date, time), tz=tzinfo) - def astimezone(self, tz: datetime.tzinfo | None = None) -> DateTime: + def astimezone(self, tz: datetime.tzinfo | None = None) -> Self: dt = super().astimezone(tz) return self.__class__( @@ -1291,7 +1282,7 @@ def replace( microsecond: int | None = None, tzinfo: bool | datetime.tzinfo | Literal[True] | None = True, fold: int | None = None, - ) -> DateTime: + ) -> Self: if year is None: year = self.year if month is None: @@ -1314,7 +1305,7 @@ def replace( if tzinfo is not None: tzinfo = pendulum._safe_timezone(tzinfo) - return DateTime.create( + return self.__class__.create( year, month, day, diff --git a/pendulum/duration.py b/pendulum/duration.py index a3a68b1a..2e9daf2d 100644 --- a/pendulum/duration.py +++ b/pendulum/duration.py @@ -1,6 +1,7 @@ from __future__ import annotations from datetime import timedelta +from typing import TYPE_CHECKING from typing import cast from typing import overload @@ -12,6 +13,9 @@ from pendulum.constants import US_PER_SECOND from pendulum.utils._compat import PYPY +if TYPE_CHECKING: + from typing_extensions import Self + def _divide_and_round(a: float, b: float) -> int: """divide a by b and round result to the nearest integer @@ -74,7 +78,7 @@ def __new__( weeks: float = 0, years: float = 0, months: float = 0, - ) -> Duration: + ) -> Self: if not isinstance(years, int) or not isinstance(months, int): raise ValueError("Float year and months are not supported") @@ -313,7 +317,7 @@ def __repr__(self) -> str: return rep.replace(", )", ")") - def __add__(self, other: timedelta) -> Duration: + def __add__(self, other: timedelta) -> Self: if isinstance(other, timedelta): return self.__class__(seconds=self.total_seconds() + other.total_seconds()) @@ -321,13 +325,13 @@ def __add__(self, other: timedelta) -> Duration: __radd__ = __add__ - def __sub__(self, other: timedelta) -> Duration: + def __sub__(self, other: timedelta) -> Self: if isinstance(other, timedelta): return self.__class__(seconds=self.total_seconds() - other.total_seconds()) return NotImplemented - def __neg__(self) -> Duration: + def __neg__(self) -> Self: return self.__class__( years=-self._years, months=-self._months, @@ -340,7 +344,7 @@ def __neg__(self) -> Duration: def _to_microseconds(self) -> int: return (self._days * (24 * 3600) + self._seconds) * 1000000 + self._microseconds - def __mul__(self, other: int | float) -> Duration: + def __mul__(self, other: int | float) -> Self: if isinstance(other, int): return self.__class__( years=self._years * other, @@ -363,7 +367,7 @@ def __floordiv__(self, other: timedelta) -> int: ... @overload - def __floordiv__(self, other: int) -> Duration: + def __floordiv__(self, other: int) -> Self: ... def __floordiv__(self, other: int | timedelta) -> int | Duration: @@ -388,10 +392,10 @@ def __truediv__(self, other: timedelta) -> float: ... @overload - def __truediv__(self, other: float) -> Duration: + def __truediv__(self, other: float) -> Self: ... - def __truediv__(self, other: int | float | timedelta) -> Duration | float: + def __truediv__(self, other: int | float | timedelta) -> Self | float: if not isinstance(other, (int, float, timedelta)): return NotImplemented @@ -421,7 +425,7 @@ def __truediv__(self, other: int | float | timedelta) -> Duration | float: __div__ = __floordiv__ - def __mod__(self, other: timedelta) -> Duration: + def __mod__(self, other: timedelta) -> Self: if isinstance(other, timedelta): r = self._to_microseconds() % other._to_microseconds() # type: ignore[attr-defined] diff --git a/pendulum/helpers.py b/pendulum/helpers.py index d468e399..a23f8b70 100644 --- a/pendulum/helpers.py +++ b/pendulum/helpers.py @@ -54,7 +54,7 @@ @overload def add_duration( - dt: datetime, + dt: _DT, years: int = 0, months: int = 0, weeks: int = 0, @@ -63,23 +63,23 @@ def add_duration( minutes: int = 0, seconds: float = 0, microseconds: int = 0, -) -> datetime: +) -> _DT: ... @overload def add_duration( - dt: date, + dt: _D, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0, -) -> date: +) -> _D: pass def add_duration( - dt: date | datetime, + dt: _D, years: int = 0, months: int = 0, weeks: int = 0, @@ -88,7 +88,7 @@ def add_duration( minutes: int = 0, seconds: float = 0, microseconds: int = 0, -) -> date | datetime: +) -> _D: """ Adds a duration to a date/datetime instance. """ diff --git a/pendulum/interval.py b/pendulum/interval.py index 30da16f0..ed2cfe63 100644 --- a/pendulum/interval.py +++ b/pendulum/interval.py @@ -18,6 +18,7 @@ from pendulum.helpers import precise_diff if TYPE_CHECKING: + from typing_extensions import Self from typing_extensions import SupportsIndex from pendulum.helpers import PreciseDiff @@ -35,7 +36,7 @@ def __new__( start: pendulum.DateTime | datetime, end: pendulum.DateTime | datetime, absolute: bool = False, - ) -> Interval: + ) -> Self: ... @overload @@ -44,7 +45,7 @@ def __new__( start: pendulum.Date | date, end: pendulum.Date | date, absolute: bool = False, - ) -> Interval: + ) -> Self: ... def __new__( @@ -52,7 +53,7 @@ def __new__( start: pendulum.DateTime | pendulum.Date | datetime | date, end: pendulum.DateTime | pendulum.Date | datetime | date, absolute: bool = False, - ) -> Interval: + ) -> Self: if ( isinstance(start, datetime) and not isinstance(end, datetime) @@ -125,7 +126,7 @@ def __new__( delta: timedelta = _end - _start # type: ignore[operator] - return cast(Interval, super().__new__(cls, seconds=delta.total_seconds())) + return super().__new__(cls, seconds=delta.total_seconds()) def __init__( self, @@ -316,9 +317,9 @@ def range( i += amount - def as_interval(self) -> Duration: + def as_duration(self) -> Duration: """ - Return the Period as a Duration. + Return the Interval as a Duration. """ return Duration(seconds=self.total_seconds()) @@ -330,23 +331,23 @@ def __contains__( ) -> bool: return self.start <= item <= self.end - def __add__(self, other: timedelta) -> Duration: - return self.as_interval().__add__(other) + def __add__(self, other: timedelta) -> Duration: # type: ignore[override] + return self.as_duration().__add__(other) - __radd__ = __add__ + __radd__ = __add__ # type: ignore[assignment] - def __sub__(self, other: timedelta) -> Duration: - return self.as_interval().__sub__(other) + def __sub__(self, other: timedelta) -> Duration: # type: ignore[override] + return self.as_duration().__sub__(other) - def __neg__(self) -> Interval: + def __neg__(self) -> Self: return self.__class__(self.end, self.start, self._absolute) - def __mul__(self, other: int | float) -> Duration: - return self.as_interval().__mul__(other) + def __mul__(self, other: int | float) -> Duration: # type: ignore[override] + return self.as_duration().__mul__(other) - __rmul__ = __mul__ + __rmul__ = __mul__ # type: ignore[assignment] - @overload + @overload # type: ignore[override] def __floordiv__(self, other: timedelta) -> int: ... @@ -355,11 +356,11 @@ def __floordiv__(self, other: int) -> Duration: ... def __floordiv__(self, other: int | timedelta) -> int | Duration: - return self.as_interval().__floordiv__(other) + return self.as_duration().__floordiv__(other) __div__ = __floordiv__ # type: ignore[assignment] - @overload + @overload # type: ignore[override] def __truediv__(self, other: timedelta) -> float: ... @@ -368,15 +369,15 @@ def __truediv__(self, other: float) -> Duration: ... def __truediv__(self, other: float | timedelta) -> Duration | float: - return self.as_interval().__truediv__(other) + return self.as_duration().__truediv__(other) - def __mod__(self, other: timedelta) -> Duration: - return self.as_interval().__mod__(other) + def __mod__(self, other: timedelta) -> Duration: # type: ignore[override] + return self.as_duration().__mod__(other) def __divmod__(self, other: timedelta) -> tuple[int, Duration]: - return self.as_interval().__divmod__(other) + return self.as_duration().__divmod__(other) - def __abs__(self) -> Interval: + def __abs__(self) -> Self: return self.__class__(self.start, self.end, absolute=True) def __repr__(self) -> str: @@ -413,7 +414,7 @@ def _getstate( def __reduce__( self, ) -> tuple[ - type[Interval], + type[Self], tuple[ pendulum.DateTime | pendulum.Date | datetime | date, pendulum.DateTime | pendulum.Date | datetime | date, @@ -425,7 +426,7 @@ def __reduce__( def __reduce_ex__( self, protocol: SupportsIndex ) -> tuple[ - type[Interval], + type[Self], tuple[ pendulum.DateTime | pendulum.Date | datetime | date, pendulum.DateTime | pendulum.Date | datetime | date, @@ -445,4 +446,4 @@ def __eq__(self, other: object) -> bool: other._absolute, ) else: - return self.as_interval() == other + return self.as_duration() == other diff --git a/pendulum/parsing/_iso8601.pyi b/pendulum/parsing/_iso8601.pyi index b9ce5d4e..761fe6b3 100644 --- a/pendulum/parsing/_iso8601.pyi +++ b/pendulum/parsing/_iso8601.pyi @@ -5,7 +5,6 @@ from datetime import datetime from datetime import time class Duration: - years: int = 0 months: int = 0 weeks: int = 0 diff --git a/pendulum/time.py b/pendulum/time.py index f70cf16a..a6699a12 100644 --- a/pendulum/time.py +++ b/pendulum/time.py @@ -20,6 +20,7 @@ if TYPE_CHECKING: from typing_extensions import Literal + from typing_extensions import Self from typing_extensions import SupportsIndex @@ -45,7 +46,7 @@ def __repr__(self) -> str: # Comparisons - def closest(self, dt1: Time | time, dt2: Time | time) -> Time: + def closest(self, dt1: Time | time, dt2: Time | time) -> Self: """ Get the closest time from the instance. """ @@ -57,7 +58,7 @@ def closest(self, dt1: Time | time, dt2: Time | time) -> Time: return dt2 - def farthest(self, dt1: Time | time, dt2: Time | time) -> Time: + def farthest(self, dt1: Time | time, dt2: Time | time) -> Self: """ Get the farthest time from the instance. """ @@ -257,7 +258,7 @@ def replace( microsecond: int | None = None, tzinfo: bool | datetime.tzinfo | Literal[True] | None = True, fold: int = 0, - ) -> Time: + ) -> Self: if tzinfo is True: tzinfo = self.tzinfo diff --git a/pendulum/tz/timezone.py b/pendulum/tz/timezone.py index e7e84af9..3458085a 100644 --- a/pendulum/tz/timezone.py +++ b/pendulum/tz/timezone.py @@ -1,9 +1,11 @@ from __future__ import annotations -import datetime as datetime_ +import datetime as _datetime from abc import ABC from abc import abstractmethod +from typing import TYPE_CHECKING +from typing import TypeVar from typing import cast from pendulum.tz.exceptions import AmbiguousTime @@ -11,11 +13,17 @@ from pendulum.tz.exceptions import NonExistingTime from pendulum.utils._compat import zoneinfo +if TYPE_CHECKING: + from typing_extensions import Self + POST_TRANSITION = "post" PRE_TRANSITION = "pre" TRANSITION_ERROR = "error" +_DT = TypeVar("_DT", bound=_datetime.datetime) + + class PendulumTimezone(ABC): @property @abstractmethod @@ -23,9 +31,7 @@ def name(self) -> str: raise NotImplementedError @abstractmethod - def convert( - self, dt: datetime_.datetime, raise_on_unknown_times: bool = False - ) -> datetime_.datetime: + def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT: raise NotImplementedError @abstractmethod @@ -38,7 +44,7 @@ def datetime( minute: int = 0, second: int = 0, microsecond: int = 0, - ) -> datetime_.datetime: + ) -> _datetime.datetime: raise NotImplementedError @@ -52,7 +58,7 @@ class Timezone(zoneinfo.ZoneInfo, PendulumTimezone): >>> tz = Timezone('Europe/Paris') """ - def __new__(cls, key: str) -> Timezone: + def __new__(cls, key: str) -> Self: try: return super().__new__(cls, key) # type: ignore[call-arg] except zoneinfo.ZoneInfoNotFoundError: @@ -62,9 +68,7 @@ def __new__(cls, key: str) -> Timezone: def name(self) -> str: return self.key - def convert( - self, dt: datetime_.datetime, raise_on_unknown_times: bool = False - ) -> datetime_.datetime: + def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT: """ Converts a datetime in the current timezone. @@ -98,11 +102,11 @@ def convert( # >>> print(tzname) offset_before = cast( - datetime_.timedelta, + _datetime.timedelta, (self.utcoffset(dt.replace(fold=0)) if dt.fold else self.utcoffset(dt)), ) offset_after = cast( - datetime_.timedelta, + _datetime.timedelta, (self.utcoffset(dt) if dt.fold else self.utcoffset(dt.replace(fold=1))), ) @@ -133,12 +137,12 @@ def datetime( minute: int = 0, second: int = 0, microsecond: int = 0, - ) -> datetime_.datetime: + ) -> _datetime.datetime: """ Return a normalized datetime for the current timezone. """ return self.convert( - datetime_.datetime( + _datetime.datetime( year, month, day, hour, minute, second, microsecond, fold=1 ) ) @@ -147,7 +151,7 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}('{self.name}')" -class FixedTimezone(datetime_.tzinfo, PendulumTimezone): +class FixedTimezone(_datetime.tzinfo, PendulumTimezone): def __init__(self, offset: int, name: str | None = None) -> None: sign = "-" if offset < 0 else "+" @@ -159,15 +163,13 @@ def __init__(self, offset: int, name: str | None = None) -> None: self._name = name self._offset = offset - self._utcoffset = datetime_.timedelta(seconds=offset) + self._utcoffset = _datetime.timedelta(seconds=offset) @property def name(self) -> str: return self._name - def convert( - self, dt: datetime_.datetime, raise_on_unknown_times: bool = False - ) -> datetime_.datetime: + def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT: if dt.tzinfo is None: return dt.__class__( dt.year, @@ -192,9 +194,9 @@ def datetime( minute: int = 0, second: int = 0, microsecond: int = 0, - ) -> datetime_.datetime: + ) -> _datetime.datetime: return self.convert( - datetime_.datetime( + _datetime.datetime( year, month, day, hour, minute, second, microsecond, fold=1 ) ) @@ -203,17 +205,17 @@ def datetime( def offset(self) -> int: return self._offset - def utcoffset(self, dt: datetime_.datetime | None) -> datetime_.timedelta: + def utcoffset(self, dt: _datetime.datetime | None) -> _datetime.timedelta: return self._utcoffset - def dst(self, dt: datetime_.datetime | None) -> datetime_.timedelta: - return datetime_.timedelta() + def dst(self, dt: _datetime.datetime | None) -> _datetime.timedelta: + return _datetime.timedelta() - def fromutc(self, dt: datetime_.datetime) -> datetime_.datetime: + def fromutc(self, dt: _datetime.datetime) -> _datetime.datetime: # Use the stdlib datetime's add method to avoid infinite recursion - return (datetime_.datetime.__add__(dt, self._utcoffset)).replace(tzinfo=self) + return (_datetime.datetime.__add__(dt, self._utcoffset)).replace(tzinfo=self) - def tzname(self, dt: datetime_.datetime | None) -> str | None: + def tzname(self, dt: _datetime.datetime | None) -> str | None: return self._name def __getinitargs__(self) -> tuple[int, str]: diff --git a/pendulum/utils/_compat.py b/pendulum/utils/_compat.py index f4ff0f85..90101fd0 100644 --- a/pendulum/utils/_compat.py +++ b/pendulum/utils/_compat.py @@ -2,16 +2,14 @@ import sys +from pendulum.utils import _zoneinfo as zoneinfo + PYPY = hasattr(sys, "pypy_version_info") PY38 = sys.version_info[:2] >= (3, 8) if sys.version_info < (3, 9): import importlib_resources as resources - - from backports import zoneinfo else: - import zoneinfo - from importlib import resources __all__ = ["resources", "zoneinfo"] diff --git a/pendulum/utils/_zoneinfo.py b/pendulum/utils/_zoneinfo.py new file mode 100644 index 00000000..8f87b795 --- /dev/null +++ b/pendulum/utils/_zoneinfo.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import sys + +from typing import TYPE_CHECKING + +if sys.version_info < (3, 9): + # Works around https://github.com/pganssle/zoneinfo/issues/125 + from backports.zoneinfo import TZPATH + from backports.zoneinfo import InvalidTZPathWarning + from backports.zoneinfo import ZoneInfoNotFoundError + from backports.zoneinfo import available_timezones + from backports.zoneinfo import reset_tzpath + + if TYPE_CHECKING: + from collections.abc import Iterable + from datetime import datetime + from datetime import timedelta + from datetime import tzinfo + from typing import Any + from typing import Protocol + + from typing_extensions import Self + + class _IOBytes(Protocol): + def read(self, __size: int) -> bytes: + ... + + def seek(self, __size: int, __whence: int = ...) -> Any: + ... + + class ZoneInfo(tzinfo): + @property + def key(self) -> str: + ... + + def __init__(self, key: str) -> None: + ... + + @classmethod + def no_cache(cls, key: str) -> Self: + ... + + @classmethod + def from_file(cls, __fobj: _IOBytes, key: str | None = ...) -> Self: + ... + + @classmethod + def clear_cache(cls, *, only_keys: Iterable[str] | None = ...) -> None: + ... + + def tzname(self, __dt: datetime | None) -> str | None: + ... + + def utcoffset(self, __dt: datetime | None) -> timedelta | None: + ... + + def dst(self, __dt: datetime | None) -> timedelta | None: + ... + + else: + from backports.zoneinfo import ZoneInfo + +else: + from zoneinfo import TZPATH + from zoneinfo import InvalidTZPathWarning + from zoneinfo import ZoneInfo + from zoneinfo import ZoneInfoNotFoundError + from zoneinfo import available_timezones + from zoneinfo import reset_tzpath + +__all__ = [ + "ZoneInfo", + "reset_tzpath", + "available_timezones", + "TZPATH", + "ZoneInfoNotFoundError", + "InvalidTZPathWarning", +] diff --git a/poetry.lock b/poetry.lock index 2aac76c1..27c3ff5c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -555,42 +555,38 @@ i18n = ["babel (>=2.9.0)"] [[package]] name = "mypy" -version = "0.991" +version = "1.0.1" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, - {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, - {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, - {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, - {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, - {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, - {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, - {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, - {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, - {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, - {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, - {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, - {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, - {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, - {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, - {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, - {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, - {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, - {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, - {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, - {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, - {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, - {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, - {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, + {file = "mypy-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:71a808334d3f41ef011faa5a5cd8153606df5fc0b56de5b2e89566c8093a0c9a"}, + {file = "mypy-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:920169f0184215eef19294fa86ea49ffd4635dedfdea2b57e45cb4ee85d5ccaf"}, + {file = "mypy-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27a0f74a298769d9fdc8498fcb4f2beb86f0564bcdb1a37b58cbbe78e55cf8c0"}, + {file = "mypy-1.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:65b122a993d9c81ea0bfde7689b3365318a88bde952e4dfa1b3a8b4ac05d168b"}, + {file = "mypy-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:5deb252fd42a77add936b463033a59b8e48eb2eaec2976d76b6878d031933fe4"}, + {file = "mypy-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2013226d17f20468f34feddd6aae4635a55f79626549099354ce641bc7d40262"}, + {file = "mypy-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:48525aec92b47baed9b3380371ab8ab6e63a5aab317347dfe9e55e02aaad22e8"}, + {file = "mypy-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96b8a0c019fe29040d520d9257d8c8f122a7343a8307bf8d6d4a43f5c5bfcc8"}, + {file = "mypy-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:448de661536d270ce04f2d7dddaa49b2fdba6e3bd8a83212164d4174ff43aa65"}, + {file = "mypy-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:d42a98e76070a365a1d1c220fcac8aa4ada12ae0db679cb4d910fabefc88b994"}, + {file = "mypy-1.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e64f48c6176e243ad015e995de05af7f22bbe370dbb5b32bd6988438ec873919"}, + {file = "mypy-1.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd63e4f50e3538617887e9aee91855368d9fc1dea30da743837b0df7373bc4"}, + {file = "mypy-1.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbeb24514c4acbc78d205f85dd0e800f34062efcc1f4a4857c57e4b4b8712bff"}, + {file = "mypy-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a2948c40a7dd46c1c33765718936669dc1f628f134013b02ff5ac6c7ef6942bf"}, + {file = "mypy-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bc8d6bd3b274dd3846597855d96d38d947aedba18776aa998a8d46fabdaed76"}, + {file = "mypy-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:17455cda53eeee0a4adb6371a21dd3dbf465897de82843751cf822605d152c8c"}, + {file = "mypy-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e831662208055b006eef68392a768ff83596035ffd6d846786578ba1714ba8f6"}, + {file = "mypy-1.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e60d0b09f62ae97a94605c3f73fd952395286cf3e3b9e7b97f60b01ddfbbda88"}, + {file = "mypy-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:0af4f0e20706aadf4e6f8f8dc5ab739089146b83fd53cb4a7e0e850ef3de0bb6"}, + {file = "mypy-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:24189f23dc66f83b839bd1cce2dfc356020dfc9a8bae03978477b15be61b062e"}, + {file = "mypy-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93a85495fb13dc484251b4c1fd7a5ac370cd0d812bbfc3b39c1bafefe95275d5"}, + {file = "mypy-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f546ac34093c6ce33f6278f7c88f0f147a4849386d3bf3ae193702f4fe31407"}, + {file = "mypy-1.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c6c2ccb7af7154673c591189c3687b013122c5a891bb5651eca3db8e6c6c55bd"}, + {file = "mypy-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:15b5a824b58c7c822c51bc66308e759243c32631896743f030daf449fe3677f3"}, + {file = "mypy-1.0.1-py3-none-any.whl", hash = "sha256:eda5c8b9949ed411ff752b9a01adda31afe7eae1e53e946dbdf9db23865e66c4"}, + {file = "mypy-1.0.1.tar.gz", hash = "sha256:28cea5a6392bb43d266782983b5a4216c25544cd7d80be681a155ddcdafd152d"}, ] [package.dependencies] @@ -1363,4 +1359,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "4644fa648646ad4fd481913b06df287d6c553ee92bd5af771ebc0d1cc1bb52ba" +content-hash = "3be7181d753122a569c6e4bb23a3ae466078e24a999c6d35be9af42c88362127" diff --git a/pyproject.toml b/pyproject.toml index 05a885db..e1f5c300 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ isort = "^5.10.1" pre-commit = "^2.20.0" [tool.poetry.group.typing.dependencies] -mypy = "0.991" +mypy = "^1.0.1" types-python-dateutil = "^2.8.19" [tool.poetry.group.dev.dependencies] diff --git a/tests/benchmarks/test_format.py b/tests/benchmarks/test_format.py index 6b3964e1..41e67828 100644 --- a/tests/benchmarks/test_format.py +++ b/tests/benchmarks/test_format.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from pytest_benchmark.fixture import BenchmarkFixture + from pytest_benchmark.fixture import BenchmarkFixture # type: ignore[import] import pendulum From a73eecf6313fd0c8ca2e4267c44acd3981b5902a Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Fri, 3 Mar 2023 12:45:33 -0600 Subject: [PATCH 06/12] Fix mypy errors --- .flake8 | 2 +- pendulum/date.py | 15 +++------------ pendulum/datetime.py | 4 ++-- pendulum/helpers.py | 4 ++-- pendulum/tz/timezone.py | 18 ++++++++++++------ pendulum/utils/_compat.py | 1 - 6 files changed, 20 insertions(+), 24 deletions(-) diff --git a/.flake8 b/.flake8 index d5712859..112b1133 100644 --- a/.flake8 +++ b/.flake8 @@ -5,7 +5,7 @@ per-file-ignores = # TC001: Move import into type checking block __init__.py:F401, TC001 # F811: Redefinition of unused name from line n - pendulum/tz/timezone.py:F811 + pendulum/tz/timezone.py:F811, E800 min_python_version = 3.7.0 ban-relative-imports = true # flake8-use-fstring: https://github.com/MichaelKim0407/flake8-use-fstring#--percent-greedy-and---format-greedy diff --git a/pendulum/date.py b/pendulum/date.py index 0e94121e..3ccfbcb1 100644 --- a/pendulum/date.py +++ b/pendulum/date.py @@ -2,7 +2,6 @@ import calendar import math -import sys from datetime import date from datetime import datetime @@ -275,17 +274,9 @@ def __sub__(self, __delta: timedelta) -> Self: def __sub__(self, __dt: datetime) -> NoReturn: ... - if sys.version_info >= (3, 8): - - @overload - def __sub__(self: _D, __dt: _D) -> Interval: - ... - - else: - - @overload - def __sub__(self, __dt: date) -> Interval: - ... + @overload + def __sub__(self: _D, __dt: _D) -> Interval: + ... def __sub__(self, other: timedelta | date) -> Self | Interval: if isinstance(other, timedelta): diff --git a/pendulum/datetime.py b/pendulum/datetime.py index 7d5f090b..407d76af 100644 --- a/pendulum/datetime.py +++ b/pendulum/datetime.py @@ -2,6 +2,7 @@ import calendar import datetime +import sys from typing import TYPE_CHECKING from typing import Any @@ -38,7 +39,6 @@ from pendulum.tz import local_timezone from pendulum.tz.timezone import FixedTimezone from pendulum.tz.timezone import Timezone -from pendulum.utils._compat import PY38 if TYPE_CHECKING: from typing_extensions import Literal @@ -1214,7 +1214,7 @@ def __add__(self, other: datetime.timedelta) -> Self: if not isinstance(other, datetime.timedelta): return NotImplemented - if PY38: + if sys.version_info >= (3, 8): # This is a workaround for Python 3.8+ # since calling astimezone() will call this method # instead of the base datetime class one. diff --git a/pendulum/helpers.py b/pendulum/helpers.py index a23f8b70..72c527db 100644 --- a/pendulum/helpers.py +++ b/pendulum/helpers.py @@ -79,7 +79,7 @@ def add_duration( def add_duration( - dt: _D, + dt: date | datetime, years: int = 0, months: int = 0, weeks: int = 0, @@ -88,7 +88,7 @@ def add_duration( minutes: int = 0, seconds: float = 0, microseconds: int = 0, -) -> _D: +) -> date | datetime: """ Adds a duration to a date/datetime instance. """ diff --git a/pendulum/tz/timezone.py b/pendulum/tz/timezone.py index 3458085a..9befc004 100644 --- a/pendulum/tz/timezone.py +++ b/pendulum/tz/timezone.py @@ -1,3 +1,4 @@ +# mypy: no-warn-redundant-casts from __future__ import annotations import datetime as _datetime @@ -89,6 +90,7 @@ def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT: >>> in_new_york.isoformat() '2013-03-30T21:30:00-04:00' """ + if dt.tzinfo is None: # Technically, utcoffset() can return None, but none of the zone information # in tzdata sets _tti_before to None. This can be checked with the following @@ -115,10 +117,14 @@ def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT: if raise_on_unknown_times: raise NonExistingTime(dt) - dt += ( - (offset_after - offset_before) - if dt.fold - else (offset_before - offset_after) + dt = cast( + _DT, + dt + + ( + (offset_after - offset_before) + if dt.fold + else (offset_before - offset_after) + ), ) elif offset_before > offset_after and raise_on_unknown_times: # Repeated time @@ -126,7 +132,7 @@ def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT: return dt.replace(tzinfo=self) - return dt.astimezone(self) + return cast(_DT, dt.astimezone(self)) def datetime( self, @@ -183,7 +189,7 @@ def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT: fold=0, ) - return dt.astimezone(self) + return cast(_DT, dt.astimezone(self)) def datetime( self, diff --git a/pendulum/utils/_compat.py b/pendulum/utils/_compat.py index 90101fd0..5ac51bdd 100644 --- a/pendulum/utils/_compat.py +++ b/pendulum/utils/_compat.py @@ -5,7 +5,6 @@ from pendulum.utils import _zoneinfo as zoneinfo PYPY = hasattr(sys, "pypy_version_info") -PY38 = sys.version_info[:2] >= (3, 8) if sys.version_info < (3, 9): import importlib_resources as resources From 9c4e45ee0217225593d14c0f7c9d0ac9383abd96 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Fri, 3 Mar 2023 12:56:04 -0600 Subject: [PATCH 07/12] Only build the project once in CI --- .github/workflows/tests.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 19bf413d..7659ec43 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,14 +64,15 @@ jobs: if: steps.cache.outputs.cache-hit == 'true' && matrix.os != 'MacOS' run: timeout 10s poetry run pip --version || rm -rf .venv - - name: Install typing dependencies + - name: Install runtime, testing, and typing dependencies run: poetry install --only main --only test --only typing -vvv - name: Run type checking run: poetry run mypy - - name: Install runtime and testing dependencies - run: poetry install --only main --only test --sync -vvv + - name: Uninstall typing dependencies + # This ensures pendulum runs without typing_extensions installed + run: poetry install --only main --only test --sync --no-root -vvv - name: Test Pure Python run: | From 681be2d906a42c202d05933a058ac09c37119ea3 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Wed, 8 Mar 2023 17:12:54 -0600 Subject: [PATCH 08/12] Improve more typing --- pendulum/date.py | 6 +---- pendulum/testing/traveller.py | 31 ++++++++++++++++------- pendulum/tz/__init__.py | 16 ++++++++++++ poetry.lock | 14 ++++++++++- pyproject.toml | 4 +-- tests/conftest.py | 46 ++++++++++++++++++++++++----------- tests/test_helpers.py | 44 +++++++++++++++++++-------------- tests/test_main.py | 2 +- tests/test_parsing.py | 17 +++++++------ 9 files changed, 122 insertions(+), 58 deletions(-) diff --git a/pendulum/date.py b/pendulum/date.py index 3ccfbcb1..c0d74ffb 100644 --- a/pendulum/date.py +++ b/pendulum/date.py @@ -8,7 +8,6 @@ from datetime import timedelta from typing import TYPE_CHECKING from typing import NoReturn -from typing import TypeVar from typing import cast from typing import overload @@ -33,9 +32,6 @@ from typing_extensions import Self -_D = TypeVar("_D", bound="Date") - - class Date(FormattableMixin, date): # Names of days of the week _days = { @@ -275,7 +271,7 @@ def __sub__(self, __dt: datetime) -> NoReturn: ... @overload - def __sub__(self: _D, __dt: _D) -> Interval: + def __sub__(self, __dt: Self) -> Interval: ... def __sub__(self, other: timedelta | date) -> Self | Interval: diff --git a/pendulum/testing/traveller.py b/pendulum/testing/traveller.py index 3c1d885f..8e96c5e0 100644 --- a/pendulum/testing/traveller.py +++ b/pendulum/testing/traveller.py @@ -9,15 +9,17 @@ if TYPE_CHECKING: from types import TracebackType + from typing_extensions import Self + class BaseTraveller: def __init__(self, datetime_class: type[DateTime] = DateTime) -> None: self._datetime_class: type[DateTime] = datetime_class - def freeze(self: BaseTraveller) -> BaseTraveller: + def freeze(self) -> Self: raise NotImplementedError() - def travel_back(self: BaseTraveller) -> BaseTraveller: + def travel_back(self) -> Self: raise NotImplementedError() def travel( @@ -30,12 +32,23 @@ def travel( minutes: int = 0, seconds: int = 0, microseconds: int = 0, - ) -> BaseTraveller: + ) -> Self: raise NotImplementedError() - def travel_to(self, dt: DateTime) -> BaseTraveller: + def travel_to(self, dt: DateTime, *, freeze: bool = False) -> Self: raise NotImplementedError() + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType, + ) -> None: + ... + if not PYPY: import time_machine @@ -48,7 +61,7 @@ def __init__(self, datetime_class: type[DateTime] = DateTime) -> None: self._traveller: time_machine.travel | None = None self._coordinates: time_machine.Coordinates | None = None - def freeze(self) -> Traveller: + def freeze(self) -> Self: if self._started: cast(time_machine.Coordinates, self._coordinates).move_to( self._datetime_class.now(), tick=False @@ -58,7 +71,7 @@ def freeze(self) -> Traveller: return self - def travel_back(self) -> Traveller: + def travel_back(self) -> Self: if not self._started: return self @@ -81,7 +94,7 @@ def travel( microseconds: int = 0, *, freeze: bool = False, - ) -> Traveller: + ) -> Self: self._start(freeze=freeze) cast(time_machine.Coordinates, self._coordinates).move_to( @@ -99,7 +112,7 @@ def travel( return self - def travel_to(self, dt: DateTime, *, freeze: bool = False) -> Traveller: + def travel_to(self, dt: DateTime, *, freeze: bool = False) -> Self: self._start(freeze=freeze) cast(time_machine.Coordinates, self._coordinates).move_to(dt) @@ -119,7 +132,7 @@ def _start(self, freeze: bool = False) -> None: self._started = True - def __enter__(self) -> Traveller: + def __enter__(self) -> Self: self._start() return self diff --git a/pendulum/tz/__init__.py b/pendulum/tz/__init__.py index 564d07aa..73449d8b 100644 --- a/pendulum/tz/__init__.py +++ b/pendulum/tz/__init__.py @@ -2,6 +2,7 @@ from pathlib import Path from typing import cast +from typing import overload from pendulum.tz.local_timezone import get_local_timezone from pendulum.tz.local_timezone import set_local_timezone @@ -30,6 +31,21 @@ def timezones() -> tuple[str, ...]: return _timezones +@overload +def timezone(name: int) -> FixedTimezone: + ... + + +@overload +def timezone(name: str) -> Timezone: + ... + + +@overload +def timezone(name: str | int) -> Timezone | FixedTimezone: + ... + + def timezone(name: str | int) -> Timezone | FixedTimezone: """ Return a Timezone instance given its name. diff --git a/poetry.lock b/poetry.lock index 27c3ff5c..0d7b601f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1256,6 +1256,18 @@ files = [ {file = "types_python_dateutil-2.8.19-py3-none-any.whl", hash = "sha256:6284df1e4783d8fc6e587f0317a81333856b872a6669a282f8a325342bce7fa8"}, ] +[[package]] +name = "types-pytz" +version = "2022.7.1.2" +description = "Typing stubs for pytz" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-pytz-2022.7.1.2.tar.gz", hash = "sha256:487d3e8e9f4071eec8081746d53fa982bbc05812e719dcbf2ebf3d55a1a4cd28"}, + {file = "types_pytz-2022.7.1.2-py3-none-any.whl", hash = "sha256:40ca448a928d566f7d44ddfde0066e384f7ffbd4da2778e42a4570eaca572446"}, +] + [[package]] name = "typing-extensions" version = "4.3.0" @@ -1359,4 +1371,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "3be7181d753122a569c6e4bb23a3ae466078e24a999c6d35be9af42c88362127" +content-hash = "4960e254e229dc7c5fdf40e18e5ec01ea2330df091c6ff61f5291bc319f53094" diff --git a/pyproject.toml b/pyproject.toml index e1f5c300..44d8daaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ pre-commit = "^2.20.0" [tool.poetry.group.typing.dependencies] mypy = "^1.0.1" types-python-dateutil = "^2.8.19" +types-pytz = ">=2022.7.1.2" [tool.poetry.group.dev.dependencies] babel = "^2.10.3" @@ -111,9 +112,6 @@ exclude = [ [[tool.mypy.overrides]] module = [ "pendulum.mixins.default", - "tests.conftest", - "tests.test_helpers", - "tests.test_main", "tests.test_parsing", "tests.date.test_add", "tests.date.test_behavior", diff --git a/tests/conftest.py b/tests/conftest.py index 060e9518..73b66db9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,17 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import pytest import pendulum +if TYPE_CHECKING: + from collections.abc import Iterator + @pytest.fixture(autouse=True) -def setup(): +def setup() -> Iterator[None]: pendulum.set_local_timezone(pendulum.timezone("America/Toronto")) yield @@ -18,8 +23,15 @@ def setup(): def assert_datetime( - d, year, month, day, hour=None, minute=None, second=None, microsecond=None -): + d: pendulum.DateTime, + year: int, + month: int, + day: int, + hour: int | None = None, + minute: int | None = None, + second: int | None = None, + microsecond: int | None = None, +) -> None: assert year == d.year assert month == d.month assert day == d.day @@ -37,13 +49,19 @@ def assert_datetime( assert microsecond == d.microsecond -def assert_date(d, year, month, day): +def assert_date(d: pendulum.Date, year: int, month: int, day: int) -> None: assert year == d.year assert month == d.month assert day == d.day -def assert_time(t, hour, minute, second, microsecond=None): +def assert_time( + t: pendulum.Time, + hour: int, + minute: int, + second: int, + microsecond: int | None = None, +) -> None: assert hour == t.hour assert minute == t.minute assert second == t.second @@ -53,15 +71,15 @@ def assert_time(t, hour, minute, second, microsecond=None): def assert_duration( - dur, - years=None, - months=None, - weeks=None, - days=None, - hours=None, - minutes=None, - seconds=None, - microseconds=None, + dur: pendulum.Duration, + years: int | None = None, + months: int | None = None, + weeks: int | None = None, + days: int | None = None, + hours: int | None = None, + minutes: int | None = None, + seconds: int | None = None, + microseconds: int | None = None, ) -> None: expected = {} actual = {} diff --git a/tests/test_helpers.py b/tests/test_helpers.py index e3daeac5..7756a063 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -8,14 +8,22 @@ import pendulum from pendulum import timezone +from pendulum.helpers import PreciseDiff from pendulum.helpers import days_in_year from pendulum.helpers import precise_diff from pendulum.helpers import week_day def assert_diff( - diff, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, microseconds=0 -): + diff: PreciseDiff, + years: int = 0, + months: int = 0, + days: int = 0, + hours: int = 0, + minutes: int = 0, + seconds: int = 0, + microseconds: int = 0, +) -> None: assert diff.years == years assert diff.months == months assert diff.days == days @@ -25,7 +33,7 @@ def assert_diff( assert diff.microseconds == microseconds -def test_precise_diff(): +def test_precise_diff() -> None: dt1 = datetime(2003, 3, 1, 0, 0, 0) dt2 = datetime(2003, 1, 31, 23, 59, 59) @@ -76,7 +84,7 @@ def test_precise_diff(): assert_diff(diff, days=6, hours=0) -def test_precise_diff_timezone(): +def test_precise_diff_timezone() -> None: paris = pendulum.timezone("Europe/Paris") toronto = pendulum.timezone("America/Toronto") @@ -92,16 +100,16 @@ def test_precise_diff_timezone(): assert_diff(diff, days=1, hours=5) # pytz - paris = pytz.timezone("Europe/Paris") - toronto = pytz.timezone("America/Toronto") + paris_pytz = pytz.timezone("Europe/Paris") + toronto_pytz = pytz.timezone("America/Toronto") - dt1 = paris.localize(datetime(2013, 3, 31, 1, 30)) - dt2 = paris.localize(datetime(2013, 4, 1, 1, 30)) + dt1 = paris_pytz.localize(datetime(2013, 3, 31, 1, 30)) + dt2 = paris_pytz.localize(datetime(2013, 4, 1, 1, 30)) diff = precise_diff(dt1, dt2) assert_diff(diff, days=1, hours=0) - dt2 = toronto.localize(datetime(2013, 4, 1, 1, 30)) + dt2 = toronto_pytz.localize(datetime(2013, 4, 1, 1, 30)) diff = precise_diff(dt1, dt2) assert_diff(diff, days=1, hours=5) @@ -113,17 +121,17 @@ def test_precise_diff_timezone(): assert_diff(diff, minutes=10) -def test_week_day(): +def test_week_day() -> None: assert week_day(2017, 6, 2) == 5 assert week_day(2017, 1, 1) == 7 -def test_days_in_years(): +def test_days_in_years() -> None: assert days_in_year(2017) == 365 assert days_in_year(2016) == 366 -def test_locale(): +def test_locale() -> None: dt = pendulum.datetime(2000, 11, 10, 12, 34, 56, 123456) pendulum.set_locale("fr") @@ -133,7 +141,7 @@ def test_locale(): assert dt.date().format("MMMM") == "novembre" -def test_set_locale_invalid(): +def test_set_locale_invalid() -> None: with pytest.raises(ValueError): pendulum.set_locale("invalid") @@ -141,13 +149,13 @@ def test_set_locale_invalid(): @pytest.mark.parametrize( "locale", ["DE", "pt-BR", "pt-br", "PT-br", "PT-BR", "pt_br", "PT_BR", "PT_BR"] ) -def test_set_locale_malformed_locale(locale): +def test_set_locale_malformed_locale(locale: str) -> None: pendulum.set_locale(locale) pendulum.set_locale("en") -def test_week_starts_at(): +def test_week_starts_at() -> None: pendulum.week_starts_at(pendulum.SATURDAY) dt = pendulum.now().start_of("week") @@ -155,7 +163,7 @@ def test_week_starts_at(): assert dt.date().day_of_week == pendulum.SATURDAY -def test_week_starts_at_invalid_value(): +def test_week_starts_at_invalid_value() -> None: with pytest.raises(ValueError): pendulum.week_starts_at(-1) @@ -163,7 +171,7 @@ def test_week_starts_at_invalid_value(): pendulum.week_starts_at(11) -def test_week_ends_at(): +def test_week_ends_at() -> None: pendulum.week_ends_at(pendulum.SATURDAY) dt = pendulum.now().end_of("week") @@ -171,7 +179,7 @@ def test_week_ends_at(): assert dt.date().day_of_week == pendulum.SATURDAY -def test_week_ends_at_invalid_value(): +def test_week_ends_at_invalid_value() -> None: with pytest.raises(ValueError): pendulum.week_ends_at(-1) diff --git a/tests/test_main.py b/tests/test_main.py index 1710bf29..7d6c46b0 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -6,7 +6,7 @@ from pendulum.tz.timezone import Timezone -def test_safe_timezone_with_tzinfo_objects(): +def test_safe_timezone_with_tzinfo_objects() -> None: tz = _safe_timezone(pytz.timezone("Europe/Paris")) assert isinstance(tz, Timezone) diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 0e5308c8..37812ee2 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -8,13 +8,14 @@ from tests.conftest import assert_time -def test_parse(): +def test_parse() -> None: text = "2016-10-16T12:34:56.123456+01:30" dt = pendulum.parse(text) assert isinstance(dt, pendulum.DateTime) assert_datetime(dt, 2016, 10, 16, 12, 34, 56, 123456) + assert dt.tz is not None assert dt.tz.name == "+01:30" assert dt.offset == 5400 @@ -36,16 +37,18 @@ def test_parse(): assert dt.offset == 0 -def test_parse_with_timezone(): +def test_parse_with_timezone() -> None: text = "2016-10-16T12:34:56.123456" dt = pendulum.parse(text, tz="Europe/Paris") + assert isinstance(dt, pendulum.DateTime) assert_datetime(dt, 2016, 10, 16, 12, 34, 56, 123456) + assert dt.tz is not None assert dt.tz.name == "Europe/Paris" assert dt.offset == 7200 -def test_parse_exact(): +def test_parse_exact() -> None: text = "2016-10-16T12:34:56.123456+01:30" dt = pendulum.parse(text, exact=True) @@ -76,7 +79,7 @@ def test_parse_exact(): assert_time(dt, 13, 0, 0) -def test_parse_duration(): +def test_parse_duration() -> None: text = "P2Y3M4DT5H6M7S" duration = pendulum.parse(text) @@ -92,7 +95,7 @@ def test_parse_duration(): assert_duration(duration, 0, 0, 2, 0, 0, 0, 0) -def test_parse_interval(): +def test_parse_interval() -> None: text = "2008-05-11T15:30:00Z/P1Y2M10DT2H30M" period = pendulum.parse(text) @@ -124,7 +127,7 @@ def test_parse_interval(): assert period.end.offset == 0 -def test_parse_now(): +def test_parse_now() -> None: dt = pendulum.parse("now") assert dt.timezone_name == "America/Toronto" @@ -135,7 +138,7 @@ def test_parse_now(): assert pendulum.parse("now") == mock_now -def test_parse_with_utc_timezone(): +def test_parse_with_utc_timezone() -> None: dt = pendulum.parse("2020-02-05T20:05:37.364951Z") assert dt.to_iso8601_string() == "2020-02-05T20:05:37.364951Z" From 3848621439c247e36ec89fe1ebe49f289a5297e1 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Fri, 9 Jun 2023 20:14:03 -0500 Subject: [PATCH 09/12] Update mypy and fix errors --- pendulum/date.py | 9 +-- pendulum/datetime.py | 28 ++++----- pendulum/time.py | 8 +-- poetry.lock | 133 ++++++++++++------------------------------- pyproject.toml | 2 +- 5 files changed, 59 insertions(+), 121 deletions(-) diff --git a/pendulum/date.py b/pendulum/date.py index c0d74ffb..9164cc0f 100644 --- a/pendulum/date.py +++ b/pendulum/date.py @@ -30,6 +30,7 @@ if TYPE_CHECKING: from typing_extensions import Self + from typing_extensions import SupportsIndex class Date(FormattableMixin, date): @@ -262,7 +263,7 @@ def __add__(self, other: timedelta) -> Self: return self._add_timedelta(other) - @overload # type: ignore[override] + @overload def __sub__(self, __delta: timedelta) -> Self: ... @@ -753,9 +754,9 @@ def fromordinal(cls, n: int) -> Self: def replace( self, - year: int | None = None, - month: int | None = None, - day: int | None = None, + year: SupportsIndex | None = None, + month: SupportsIndex | None = None, + day: SupportsIndex | None = None, ) -> Self: year = year if year is not None else self.year month = month if month is not None else self.month diff --git a/pendulum/datetime.py b/pendulum/datetime.py index 407d76af..ba6afcef 100644 --- a/pendulum/datetime.py +++ b/pendulum/datetime.py @@ -82,13 +82,13 @@ class DateTime(datetime.datetime, Date): @classmethod def create( cls, - year: int, - month: int, - day: int, - hour: int = 0, - minute: int = 0, - second: int = 0, - microsecond: int = 0, + year: SupportsIndex, + month: SupportsIndex, + day: SupportsIndex, + hour: SupportsIndex = 0, + minute: SupportsIndex = 0, + second: SupportsIndex = 0, + microsecond: SupportsIndex = 0, tz: str | float | Timezone | FixedTimezone | None | datetime.tzinfo = UTC, fold: int = 1, raise_on_unknown_times: bool = False, @@ -1273,13 +1273,13 @@ def astimezone(self, tz: datetime.tzinfo | None = None) -> Self: def replace( self, - year: int | None = None, - month: int | None = None, - day: int | None = None, - hour: int | None = None, - minute: int | None = None, - second: int | None = None, - microsecond: int | None = None, + year: SupportsIndex | None = None, + month: SupportsIndex | None = None, + day: SupportsIndex | None = None, + hour: SupportsIndex | None = None, + minute: SupportsIndex | None = None, + second: SupportsIndex | None = None, + microsecond: SupportsIndex | None = None, tzinfo: bool | datetime.tzinfo | Literal[True] | None = True, fold: int | None = None, ) -> Self: diff --git a/pendulum/time.py b/pendulum/time.py index a6699a12..6c0c3048 100644 --- a/pendulum/time.py +++ b/pendulum/time.py @@ -252,10 +252,10 @@ def diff_for_humans( def replace( self, - hour: int | None = None, - minute: int | None = None, - second: int | None = None, - microsecond: int | None = None, + hour: SupportsIndex | None = None, + minute: SupportsIndex | None = None, + second: SupportsIndex | None = None, + microsecond: SupportsIndex | None = None, tzinfo: bool | datetime.tzinfo | Literal[True] | None = True, fold: int = 0, ) -> Self: diff --git a/poetry.lock b/poetry.lock index 0d7b601f..96da94cc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "atomicwrites" version = "1.4.1" description = "Atomic file writes." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -15,7 +14,6 @@ files = [ name = "attrs" version = "22.1.0" description = "Classes Without Boilerplate" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -33,7 +31,6 @@ tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy name = "babel" version = "2.10.3" description = "Internationalization utilities" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -48,7 +45,6 @@ pytz = ">=2015.7" name = "backports-zoneinfo" version = "0.2.1" description = "Backport of the standard library zoneinfo module" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -77,7 +73,6 @@ tzdata = ["tzdata"] name = "black" version = "22.6.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.6.2" files = [ @@ -125,7 +120,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "dev" optional = false python-versions = "*" files = [ @@ -202,7 +196,6 @@ pycparser = "*" name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -214,7 +207,6 @@ files = [ name = "cleo" version = "2.0.1" description = "Cleo allows you to create beautiful and testable command-line interfaces." -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -230,7 +222,6 @@ rapidfuzz = ">=2.2.0,<3.0.0" name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -246,7 +237,6 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} name = "colorama" version = "0.4.5" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -258,7 +248,6 @@ files = [ name = "crashtest" version = "0.4.1" description = "Manage Python errors with ease" -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -270,7 +259,6 @@ files = [ name = "distlib" version = "0.3.5" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -282,7 +270,6 @@ files = [ name = "filelock" version = "3.7.1" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -298,7 +285,6 @@ testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-co name = "ghp-import" version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." -category = "dev" optional = false python-versions = "*" files = [ @@ -316,7 +302,6 @@ dev = ["flake8", "markdown", "twine", "wheel"] name = "identify" version = "2.5.3" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -331,7 +316,6 @@ license = ["ukkonen"] name = "importlib-metadata" version = "4.12.0" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -352,7 +336,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "5.9.0" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -371,7 +354,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = "*" files = [ @@ -383,7 +365,6 @@ files = [ name = "isort" version = "5.10.1" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.6.1,<4.0" files = [ @@ -401,7 +382,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -419,7 +399,6 @@ i18n = ["Babel (>=2.7)"] name = "markdown" version = "3.4.1" description = "Python implementation of Markdown." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -437,7 +416,6 @@ testing = ["coverage", "pyyaml"] name = "markdown-include" version = "0.5.1" description = "This is an extension to Python-Markdown which provides an \"include\" function, similar to that found in LaTeX (and also the C pre-processor and Fortran). I originally wrote it for my FORD Fortran auto-documentation generator." -category = "dev" optional = false python-versions = "*" files = [ @@ -451,7 +429,6 @@ markdown = "*" name = "markupsafe" version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -501,7 +478,6 @@ files = [ name = "mergedeep" version = "1.3.4" description = "A deep merge function for 🐍." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -513,7 +489,6 @@ files = [ name = "meson" version = "0.63.2" description = "A high performance build system" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -530,7 +505,6 @@ typing = ["mypy", "typing-extensions"] name = "mkdocs" version = "1.3.0" description = "Project documentation with Markdown." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -555,42 +529,41 @@ i18n = ["babel (>=2.9.0)"] [[package]] name = "mypy" -version = "1.0.1" +version = "1.3.0" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:71a808334d3f41ef011faa5a5cd8153606df5fc0b56de5b2e89566c8093a0c9a"}, - {file = "mypy-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:920169f0184215eef19294fa86ea49ffd4635dedfdea2b57e45cb4ee85d5ccaf"}, - {file = "mypy-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27a0f74a298769d9fdc8498fcb4f2beb86f0564bcdb1a37b58cbbe78e55cf8c0"}, - {file = "mypy-1.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:65b122a993d9c81ea0bfde7689b3365318a88bde952e4dfa1b3a8b4ac05d168b"}, - {file = "mypy-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:5deb252fd42a77add936b463033a59b8e48eb2eaec2976d76b6878d031933fe4"}, - {file = "mypy-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2013226d17f20468f34feddd6aae4635a55f79626549099354ce641bc7d40262"}, - {file = "mypy-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:48525aec92b47baed9b3380371ab8ab6e63a5aab317347dfe9e55e02aaad22e8"}, - {file = "mypy-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96b8a0c019fe29040d520d9257d8c8f122a7343a8307bf8d6d4a43f5c5bfcc8"}, - {file = "mypy-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:448de661536d270ce04f2d7dddaa49b2fdba6e3bd8a83212164d4174ff43aa65"}, - {file = "mypy-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:d42a98e76070a365a1d1c220fcac8aa4ada12ae0db679cb4d910fabefc88b994"}, - {file = "mypy-1.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e64f48c6176e243ad015e995de05af7f22bbe370dbb5b32bd6988438ec873919"}, - {file = "mypy-1.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd63e4f50e3538617887e9aee91855368d9fc1dea30da743837b0df7373bc4"}, - {file = "mypy-1.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbeb24514c4acbc78d205f85dd0e800f34062efcc1f4a4857c57e4b4b8712bff"}, - {file = "mypy-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a2948c40a7dd46c1c33765718936669dc1f628f134013b02ff5ac6c7ef6942bf"}, - {file = "mypy-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bc8d6bd3b274dd3846597855d96d38d947aedba18776aa998a8d46fabdaed76"}, - {file = "mypy-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:17455cda53eeee0a4adb6371a21dd3dbf465897de82843751cf822605d152c8c"}, - {file = "mypy-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e831662208055b006eef68392a768ff83596035ffd6d846786578ba1714ba8f6"}, - {file = "mypy-1.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e60d0b09f62ae97a94605c3f73fd952395286cf3e3b9e7b97f60b01ddfbbda88"}, - {file = "mypy-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:0af4f0e20706aadf4e6f8f8dc5ab739089146b83fd53cb4a7e0e850ef3de0bb6"}, - {file = "mypy-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:24189f23dc66f83b839bd1cce2dfc356020dfc9a8bae03978477b15be61b062e"}, - {file = "mypy-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93a85495fb13dc484251b4c1fd7a5ac370cd0d812bbfc3b39c1bafefe95275d5"}, - {file = "mypy-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f546ac34093c6ce33f6278f7c88f0f147a4849386d3bf3ae193702f4fe31407"}, - {file = "mypy-1.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c6c2ccb7af7154673c591189c3687b013122c5a891bb5651eca3db8e6c6c55bd"}, - {file = "mypy-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:15b5a824b58c7c822c51bc66308e759243c32631896743f030daf449fe3677f3"}, - {file = "mypy-1.0.1-py3-none-any.whl", hash = "sha256:eda5c8b9949ed411ff752b9a01adda31afe7eae1e53e946dbdf9db23865e66c4"}, - {file = "mypy-1.0.1.tar.gz", hash = "sha256:28cea5a6392bb43d266782983b5a4216c25544cd7d80be681a155ddcdafd152d"}, + {file = "mypy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d"}, + {file = "mypy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85"}, + {file = "mypy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd"}, + {file = "mypy-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152"}, + {file = "mypy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228"}, + {file = "mypy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd"}, + {file = "mypy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c"}, + {file = "mypy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae"}, + {file = "mypy-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca"}, + {file = "mypy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf"}, + {file = "mypy-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409"}, + {file = "mypy-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929"}, + {file = "mypy-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a"}, + {file = "mypy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee"}, + {file = "mypy-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f"}, + {file = "mypy-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb"}, + {file = "mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4"}, + {file = "mypy-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305"}, + {file = "mypy-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf"}, + {file = "mypy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8"}, + {file = "mypy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703"}, + {file = "mypy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017"}, + {file = "mypy-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e"}, + {file = "mypy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a"}, + {file = "mypy-1.3.0-py3-none-any.whl", hash = "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897"}, + {file = "mypy-1.3.0.tar.gz", hash = "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11"}, ] [package.dependencies] -mypy-extensions = ">=0.4.3" +mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} typing-extensions = ">=3.10" @@ -603,21 +576,19 @@ reports = ["lxml"] [[package]] name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "ninja" version = "1.10.2.3" description = "Ninja is a small build system with a focus on speed" -category = "dev" optional = false python-versions = "*" files = [ @@ -644,7 +615,6 @@ test = ["codecov (>=2.0.5)", "coverage (>=4.2)", "flake8 (>=3.0.4)", "pytest (>= name = "nodeenv" version = "1.7.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -659,7 +629,6 @@ setuptools = "*" name = "packaging" version = "21.3" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -674,7 +643,6 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" name = "pathspec" version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -686,7 +654,6 @@ files = [ name = "platformdirs" version = "2.5.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -702,7 +669,6 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -721,7 +687,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "2.20.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -742,7 +707,6 @@ virtualenv = ">=20.0.8" name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -754,7 +718,6 @@ files = [ name = "py-cpuinfo" version = "9.0.0" description = "Get CPU info with pure Python" -category = "dev" optional = false python-versions = "*" files = [ @@ -766,7 +729,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -778,7 +740,6 @@ files = [ name = "pygments" version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -790,7 +751,6 @@ files = [ name = "pymdown-extensions" version = "6.3" description = "Extension pack for Python Markdown." -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -805,7 +765,6 @@ Markdown = ">=3.2" name = "pyparsing" version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "dev" optional = false python-versions = ">=3.6.8" files = [ @@ -820,7 +779,6 @@ diagrams = ["jinja2", "railroad-diagrams"] name = "pytest" version = "7.1.2" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -846,7 +804,6 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2. name = "pytest-benchmark" version = "4.0.0" description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -867,7 +824,6 @@ histogram = ["pygal", "pygaljs"] name = "pytest-codspeed" version = "1.2.2" description = "Pytest plugin to create CodSpeed benchmarks" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -887,7 +843,6 @@ dev = ["black (>=22.3.0,<22.4.0)", "flake8 (>=5.0.4,<5.1.0)", "hatchling (>=1.11 name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -902,7 +857,6 @@ six = ">=1.5" name = "pytz" version = "2022.1" description = "World timezone definitions, modern and historical" -category = "dev" optional = false python-versions = "*" files = [ @@ -914,7 +868,6 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -964,7 +917,6 @@ files = [ name = "pyyaml-env-tag" version = "0.1" description = "A custom YAML tag for referencing environment variables in YAML files. " -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -979,7 +931,6 @@ pyyaml = "*" name = "rapidfuzz" version = "2.13.7" description = "rapid fuzzy string matching" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1081,7 +1032,6 @@ full = ["numpy"] name = "setuptools" version = "63.4.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1098,7 +1048,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1110,7 +1059,6 @@ files = [ name = "time-machine" version = "2.7.1" description = "Travel through time in your tests." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1163,7 +1111,6 @@ python-dateutil = "*" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1175,7 +1122,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1187,7 +1133,6 @@ files = [ name = "tox" version = "3.25.1" description = "tox is a generic virtualenv management and test command line tool" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -1214,7 +1159,6 @@ testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psu name = "typed-ast" version = "1.5.4" description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1248,7 +1192,6 @@ files = [ name = "types-python-dateutil" version = "2.8.19" description = "Typing stubs for python-dateutil" -category = "dev" optional = false python-versions = "*" files = [ @@ -1260,7 +1203,6 @@ files = [ name = "types-pytz" version = "2022.7.1.2" description = "Typing stubs for pytz" -category = "dev" optional = false python-versions = "*" files = [ @@ -1272,7 +1214,6 @@ files = [ name = "typing-extensions" version = "4.3.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1284,7 +1225,6 @@ files = [ name = "tzdata" version = "2022.1" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -1296,7 +1236,6 @@ files = [ name = "virtualenv" version = "20.16.3" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1318,7 +1257,6 @@ testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7 name = "watchdog" version = "2.1.9" description = "Filesystem events monitoring" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1356,7 +1294,6 @@ watchmedo = ["PyYAML (>=3.10)"] name = "zipp" version = "3.8.1" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1371,4 +1308,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "4960e254e229dc7c5fdf40e18e5ec01ea2330df091c6ff61f5291bc319f53094" +content-hash = "39af764fa2788e55fa847cb48f19c948c043b37bb52546ab5535d6953387200f" diff --git a/pyproject.toml b/pyproject.toml index 44d8daaa..3aa14314 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ isort = "^5.10.1" pre-commit = "^2.20.0" [tool.poetry.group.typing.dependencies] -mypy = "^1.0.1" +mypy = "^1.3.0" types-python-dateutil = "^2.8.19" types-pytz = ">=2022.7.1.2" From d762e27ea6a5237468adaa228aa9a886d279c940 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Fri, 9 Jun 2023 20:20:40 -0500 Subject: [PATCH 10/12] Move `timezone()` to main module to work around mypy sometimes mixing up the function and module --- pendulum/__init__.py | 31 ++++++++++++++++++++++++++++++- pendulum/tz/__init__.py | 30 ------------------------------ tests/datetime/test_construct.py | 2 +- tests/datetime/test_getters.py | 2 +- tests/tz/test_helpers.py | 2 +- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/pendulum/__init__.py b/pendulum/__init__.py index 891774d5..72f19eaf 100644 --- a/pendulum/__init__.py +++ b/pendulum/__init__.py @@ -4,6 +4,7 @@ from typing import Union from typing import cast +from typing import overload from pendulum.__version__ import __version__ from pendulum.constants import DAYS_PER_WEEK @@ -38,10 +39,10 @@ from pendulum.testing.traveller import Traveller from pendulum.time import Time from pendulum.tz import UTC +from pendulum.tz import fixed_timezone from pendulum.tz import local_timezone from pendulum.tz import set_local_timezone from pendulum.tz import test_local_timezone -from pendulum.tz import timezone from pendulum.tz import timezones from pendulum.tz.timezone import FixedTimezone from pendulum.tz.timezone import Timezone @@ -54,6 +55,34 @@ _formatter = Formatter() +@overload +def timezone(name: int) -> FixedTimezone: + ... + + +@overload +def timezone(name: str) -> Timezone: + ... + + +@overload +def timezone(name: str | int) -> Timezone | FixedTimezone: + ... + + +def timezone(name: str | int) -> Timezone | FixedTimezone: + """ + Return a Timezone instance given its name. + """ + if isinstance(name, int): + return fixed_timezone(name) + + if name.lower() == "utc": + return UTC + + return Timezone(name) + + def _safe_timezone( obj: str | float | _datetime.tzinfo | Timezone | FixedTimezone | None, dt: _datetime.datetime | None = None, diff --git a/pendulum/tz/__init__.py b/pendulum/tz/__init__.py index 73449d8b..0ca1b328 100644 --- a/pendulum/tz/__init__.py +++ b/pendulum/tz/__init__.py @@ -2,7 +2,6 @@ from pathlib import Path from typing import cast -from typing import overload from pendulum.tz.local_timezone import get_local_timezone from pendulum.tz.local_timezone import set_local_timezone @@ -31,34 +30,6 @@ def timezones() -> tuple[str, ...]: return _timezones -@overload -def timezone(name: int) -> FixedTimezone: - ... - - -@overload -def timezone(name: str) -> Timezone: - ... - - -@overload -def timezone(name: str | int) -> Timezone | FixedTimezone: - ... - - -def timezone(name: str | int) -> Timezone | FixedTimezone: - """ - Return a Timezone instance given its name. - """ - if isinstance(name, int): - return fixed_timezone(name) - - if name.lower() == "utc": - return UTC - - return Timezone(name) - - def fixed_timezone(offset: int) -> FixedTimezone: """ Return a Timezone instance given its offset in seconds. @@ -86,7 +57,6 @@ def local_timezone() -> Timezone | FixedTimezone: "set_local_timezone", "get_local_timezone", "test_local_timezone", - "timezone", "fixed_timezone", "local_timezone", "timezones", diff --git a/tests/datetime/test_construct.py b/tests/datetime/test_construct.py index 9488c08b..9435413b 100644 --- a/tests/datetime/test_construct.py +++ b/tests/datetime/test_construct.py @@ -12,7 +12,7 @@ import pendulum from pendulum import DateTime -from pendulum.tz import timezone +from pendulum import timezone from pendulum.utils._compat import PYPY from tests.conftest import assert_datetime diff --git a/tests/datetime/test_getters.py b/tests/datetime/test_getters.py index 50746234..9adfc189 100644 --- a/tests/datetime/test_getters.py +++ b/tests/datetime/test_getters.py @@ -7,7 +7,7 @@ import pendulum from pendulum import DateTime -from pendulum.tz import timezone +from pendulum import timezone from tests.conftest import assert_date from tests.conftest import assert_time diff --git a/tests/tz/test_helpers.py b/tests/tz/test_helpers.py index edec6fda..9402372c 100644 --- a/tests/tz/test_helpers.py +++ b/tests/tz/test_helpers.py @@ -2,7 +2,7 @@ import pytest -from pendulum.tz import timezone +from pendulum import timezone from pendulum.tz.exceptions import InvalidTimezone from pendulum.tz.timezone import FixedTimezone from pendulum.tz.timezone import Timezone From 294749e6a33e67942380e3ff84bc1e6144d0cf8e Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Fri, 9 Jun 2023 20:32:33 -0500 Subject: [PATCH 11/12] Improve `ClassVar` setting for `DateTime` --- pendulum/datetime.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pendulum/datetime.py b/pendulum/datetime.py index ba6afcef..d265aece 100644 --- a/pendulum/datetime.py +++ b/pendulum/datetime.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING from typing import Any from typing import Callable +from typing import ClassVar from typing import Optional from typing import cast from typing import overload @@ -47,7 +48,9 @@ class DateTime(datetime.datetime, Date): - EPOCH: DateTime + EPOCH: ClassVar[DateTime] + min: ClassVar[DateTime] + max: ClassVar[DateTime] # Formats @@ -1366,8 +1369,6 @@ def _cmp(self, other: datetime.datetime, **kwargs: Any) -> int: return 0 if dt == other else 1 if dt > other else -1 -DateTime.min: DateTime = DateTime(1, 1, 1, 0, 0, tzinfo=UTC) # type: ignore[misc] -DateTime.max: DateTime = DateTime( # type: ignore[misc] - 9999, 12, 31, 23, 59, 59, 999999, tzinfo=UTC -) -DateTime.EPOCH: DateTime = DateTime(1970, 1, 1, tzinfo=UTC) # type: ignore[misc] +DateTime.min = DateTime(1, 1, 1, 0, 0, tzinfo=UTC) +DateTime.max = DateTime(9999, 12, 31, 23, 59, 59, 999999, tzinfo=UTC) +DateTime.EPOCH = DateTime(1970, 1, 1, tzinfo=UTC) From 415fa8f83333920ddb6de79211e288db93a8b1ad Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Fri, 9 Jun 2023 21:04:10 -0500 Subject: [PATCH 12/12] Fix unused ignore error --- .flake8 | 1 + pendulum/date.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 112b1133..1d7308c8 100644 --- a/.flake8 +++ b/.flake8 @@ -4,6 +4,7 @@ per-file-ignores = # F401: Module imported but unused # TC001: Move import into type checking block __init__.py:F401, TC001 + pendulum/date.py:E800 # F811: Redefinition of unused name from line n pendulum/tz/timezone.py:F811, E800 min_python_version = 3.7.0 diff --git a/pendulum/date.py b/pendulum/date.py index 9164cc0f..0d8382a4 100644 --- a/pendulum/date.py +++ b/pendulum/date.py @@ -1,3 +1,5 @@ +# The following is only needed because of Python 3.7 +# mypy: no-warn-unused-ignores from __future__ import annotations import calendar @@ -263,7 +265,7 @@ def __add__(self, other: timedelta) -> Self: return self._add_timedelta(other) - @overload + @overload # type: ignore[override] # this is only needed because of Python 3.7 def __sub__(self, __delta: timedelta) -> Self: ...