From adaea8ed0352634e211af8397e7889de951a3af9 Mon Sep 17 00:00:00 2001 From: Matteo Bernardini Date: Mon, 10 Feb 2025 16:28:31 +0800 Subject: [PATCH 1/7] fix: typo in return type of `DateTime.time()` --- src/datetype/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetype/__init__.py b/src/datetype/__init__.py index 1edced5..9bdda85 100644 --- a/src/datetype/__init__.py +++ b/src/datetype/__init__.py @@ -341,7 +341,7 @@ def utctimetuple(self) -> struct_time: ... def date(self) -> Date: ... - def time(self) -> NaiveDateTime: ... + def time(self) -> NaiveTime: ... @overload def replace( From 3de7d8e3f6c4a963c5b78b7ee70ee112bc86c00d Mon Sep 17 00:00:00 2001 From: Glyph Date: Thu, 13 Feb 2025 16:28:24 -0800 Subject: [PATCH 2/7] ignore dist This commit was sponsored by Matt Campbell, Blaise Pabon, Cassie Jones, and my other patrons. If you want to join them, you can support my work at https://glyph.im/patrons/. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index dce73e9..3165552 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build +dist .tox \ No newline at end of file From 2815bdbdbfc7c6569df1093ad646b587975ee3a8 Mon Sep 17 00:00:00 2001 From: Glyph Date: Thu, 13 Feb 2025 16:28:33 -0800 Subject: [PATCH 3/7] make this a bit less annoying to run in my terminal This commit was sponsored by Blaise Pabon, Matt Campbell, and my other patrons. If you want to join them, you can support my work at https://glyph.im/patrons/. --- src/datetype/test/test_datetype.py | 44 ++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/datetype/test/test_datetype.py b/src/datetype/test/test_datetype.py index a64d782..43f46e5 100644 --- a/src/datetype/test/test_datetype.py +++ b/src/datetype/test/test_datetype.py @@ -1,16 +1,16 @@ -from datetime import date, datetime, time, timezone -from os import popen +from datetime import date, datetime, time, timedelta, timezone +from os import chdir, getcwd, popen +from pathlib import Path from sys import version_info from unittest import TestCase +from zoneinfo import ZoneInfo -from datetype import ( - AwareDateTime, - NaiveDateTime, - NaiveTime, - Time, - aware, - naive, -) +from datetype import AwareDateTime, NaiveDateTime, NaiveTime, Time, aware, naive + +TEST_DATA = (Path(__file__) / "..").resolve() +while not (TEST_DATA / ".git").is_dir(): + TEST_DATA = TEST_DATA / ".." +TEST_DATA = TEST_DATA.resolve() class DateTypeTests(TestCase): @@ -57,15 +57,31 @@ def test_mypy_output(self) -> None: Make sure that we get expected mypy errors. """ mypy_command = "mypy" - expected_file_name = "expected_mypy" + expected_file_name = TEST_DATA / "expected_mypy" if version_info < (3, 9): mypy_command += " --ignore-missing-imports" # zoneinfo if version_info[:2] == (3, 7): - expected_file_name += "_37" + expected_file_name = expected_file_name.with_suffix("_37") - with popen(f"{mypy_command} tryit.py") as f: + cwd = getcwd() + try: + chdir(TEST_DATA) + it = popen(f"{mypy_command} tryit.py") + finally: + chdir(cwd) + with it as f: actual = f.read() - with open(f"{expected_file_name}.txt") as f: + with expected_file_name.with_suffix(".txt").open() as f: expected = f.read() self.maxDiff = 9999 self.assertEqual(expected, actual) + + def test_none_aware(self) -> None: + """ + L{aware} with no argument will produce a ZoneInfo. + """ + zi = ZoneInfo("US/Pacific") + stddt = datetime(2025, 2, 13, 15, 35, 13, 574354, tzinfo=zi) + awareified = aware(stddt) + self.assertIs(awareified.tzinfo, zi) + self.assertEqual(awareified.tzinfo.dst(stddt), timedelta(0)) From 41104cfed3223640d79311aaa7ecc91bc78a23a9 Mon Sep 17 00:00:00 2001 From: Glyph Date: Thu, 13 Feb 2025 16:28:42 -0800 Subject: [PATCH 4/7] expand tzinfo check to include explicit ignore c.f. https://github.com/python/mypy/issues/4717 This commit was sponsored by Blaise Pabon, Matt Campbell, and my other patrons. If you want to join them, you can support my work at https://glyph.im/patrons/. --- src/datetype/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/datetype/__init__.py b/src/datetype/__init__.py index 9bdda85..d367e07 100644 --- a/src/datetype/__init__.py +++ b/src/datetype/__init__.py @@ -591,7 +591,13 @@ def aware(t: _time, tztype: Type[_FuncTZ]) -> Time[_FuncTZ]: ... def aware( t: Union[_datetime, _time], tztype: Optional[Type[_FuncTZ]] = None ) -> Union[DateTime[_FuncTZ], Time[_FuncTZ]]: - tzcheck: Type[_tzinfo] = tztype if tztype is not None else _tzinfo + tzcheck: Type[_tzinfo] + if tztype is not None: + tzcheck = tztype + else: + # tzinfo works just fine with isinstance checks, which is why we care + # about matching against type[...] here, so we can cast it away safely + tzcheck = _tzinfo # type:ignore if not isinstance(t.tzinfo, tzcheck): raise TypeError(f"{t} is naive, not aware") return t # type: ignore[return-value] From 05d4f7677099fabf564513370af6ff7bd9640185 Mon Sep 17 00:00:00 2001 From: Glyph Date: Thu, 13 Feb 2025 16:32:36 -0800 Subject: [PATCH 5/7] drop EOL python versions to work in GHA CI This commit was sponsored by Blaise Pabon, and my other patrons. If you want to join them, you can support my work at https://glyph.im/patrons/. --- .github/workflows/ci.yml | 2 +- pyproject.toml | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb7fdb5..0c70d66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - python: ["3.12", "3.11", "3.10", "3.9", "3.8", "3.7"] + python: ["3.13", "3.12", "3.11", "3.10", "3.9"] TOX_ENV: ["lint", "py", "mypy"] exclude: - TOX_ENV: "lint" diff --git a/pyproject.toml b/pyproject.toml index 8d340e6..fd691d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "datetype" version = "2024.2.28" description = "A type wrapper for the standard library `datetime` that supplies stricter checks, such as making 'datetime' not substitutable for 'date', and separating out Naive and Aware datetimes into separate, mutually-incompatible types." readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.9" # This file is derived directly from typeshed's `datetime.pyi`, so it's the # typeshed license. license = {file = "LICENSE.txt"} @@ -14,12 +14,11 @@ classifiers = [ "Development Status :: 5 - Production/Stable", # Indicate who your project is intended for "Intended Audience :: Developers", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.7", ] [build-system] From 4ea22c7d358a0ea769b8724c431dac37b057e1de Mon Sep 17 00:00:00 2001 From: Glyph Date: Thu, 13 Feb 2025 16:39:31 -0800 Subject: [PATCH 6/7] verify that we get an error This commit was sponsored by Blaise Pabon, and my other patrons. If you want to join them, you can support my work at https://glyph.im/patrons/. --- expected_mypy.txt | 3 ++- tryit.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/expected_mypy.txt b/expected_mypy.txt index dbf6a2d..1f8eb77 100644 --- a/expected_mypy.txt +++ b/expected_mypy.txt @@ -43,4 +43,5 @@ tryit.py:53: note: def timetz(self) -> Time[None] tryit.py:53: note: Got: tryit.py:53: note: def timetz(self) -> Time[timezone] tryit.py:53: note: tzinfo: expected "None", got "timezone" -Found 9 errors in 1 file (checked 1 source file) +tryit.py:56: error: Incompatible types in assignment (expression has type "NaiveTime", variable has type "DateTime[timezone]") [assignment] +Found 10 errors in 1 file (checked 1 source file) diff --git a/tryit.py b/tryit.py index 46a7c8c..0a796e0 100644 --- a/tryit.py +++ b/tryit.py @@ -52,3 +52,5 @@ not_naive: NaiveDateTime = a.astimezone() is_aware: DateTime[timezone] = a.astimezone(None) + +not_aware_method_time: DateTime[timezone] = a.time() # nope; actually naive From 716e650cabaf44ddfeabd4c4da548f7ba6762c66 Mon Sep 17 00:00:00 2001 From: Glyph Date: Thu, 13 Feb 2025 16:57:56 -0800 Subject: [PATCH 7/7] sigh, "best effort", I guess, given how core this library is This commit was sponsored by Blaise Pabon, and my other patrons. If you want to join them, you can support my work at https://glyph.im/patrons/. --- expected_mypy_37.txt | 3 ++- pyproject.toml | 7 ++++++- src/datetype/test/test_datetype.py | 12 +++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/expected_mypy_37.txt b/expected_mypy_37.txt index bc5ac05..386e580 100644 --- a/expected_mypy_37.txt +++ b/expected_mypy_37.txt @@ -29,4 +29,5 @@ tryit.py:53: note: def timetz(self) -> Time[None] tryit.py:53: note: Got: tryit.py:53: note: def timetz(self) -> Time[timezone] tryit.py:53: note: tzinfo: expected "None", got "timezone" -Found 7 errors in 1 file (checked 1 source file) +tryit.py:56: error: Incompatible types in assignment (expression has type "NaiveTime", variable has type "DateTime[timezone]") [assignment] +Found 8 errors in 1 file (checked 1 source file) diff --git a/pyproject.toml b/pyproject.toml index fd691d9..4b55c63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "datetype" version = "2024.2.28" description = "A type wrapper for the standard library `datetime` that supplies stricter checks, such as making 'datetime' not substitutable for 'date', and separating out Naive and Aware datetimes into separate, mutually-incompatible types." readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.7" # This file is derived directly from typeshed's `datetime.pyi`, so it's the # typeshed license. license = {file = "LICENSE.txt"} @@ -19,6 +19,11 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.7", +] +dependencies = [ + "typing_extensions ; python_version < '3.8'" ] [build-system] diff --git a/src/datetype/test/test_datetype.py b/src/datetype/test/test_datetype.py index 43f46e5..352a800 100644 --- a/src/datetype/test/test_datetype.py +++ b/src/datetype/test/test_datetype.py @@ -2,8 +2,7 @@ from os import chdir, getcwd, popen from pathlib import Path from sys import version_info -from unittest import TestCase -from zoneinfo import ZoneInfo +from unittest import TestCase, skipIf from datetype import AwareDateTime, NaiveDateTime, NaiveTime, Time, aware, naive @@ -57,11 +56,11 @@ def test_mypy_output(self) -> None: Make sure that we get expected mypy errors. """ mypy_command = "mypy" - expected_file_name = TEST_DATA / "expected_mypy" + expected_file_name = ( + TEST_DATA / f"expected_mypy{'_37' if (version_info < (3, 8)) else ''}" + ) if version_info < (3, 9): mypy_command += " --ignore-missing-imports" # zoneinfo - if version_info[:2] == (3, 7): - expected_file_name = expected_file_name.with_suffix("_37") cwd = getcwd() try: @@ -76,10 +75,13 @@ def test_mypy_output(self) -> None: self.maxDiff = 9999 self.assertEqual(expected, actual) + @skipIf(version_info < (3, 9), "ZoneInfo") def test_none_aware(self) -> None: """ L{aware} with no argument will produce a ZoneInfo. """ + from zoneinfo import ZoneInfo + zi = ZoneInfo("US/Pacific") stddt = datetime(2025, 2, 13, 15, 35, 13, 574354, tzinfo=zi) awareified = aware(stddt)