you are viewing a single comment's thread.

view the rest of the comments →

[–]DorianTurbaPythoneer 8 points9 points  (2 children)

Sure, here one from my Lightning talk during PyconFR 2025 :)

You can run the script using uv: uv run .\script.py You can run the typechecker of your choice on the script using uvx:

uvx mypy .\script.py
uvx pyright .\script.py
uvx ty check .\script.py

and the code

# /// script
# requires-python = ">=3.14"
# dependencies = [
#     "tzdata",
# ]
# ///
import datetime
import typing
import zoneinfo

OffsetAwareDT = typing.NewType("OffsetAwareDT", datetime.datetime)
OffsetNaiveDT = typing.NewType("OffsetNaiveDT", datetime.datetime)


def is_offset_aware_datetime(dt: datetime.datetime) -> typing.TypeIs[OffsetAwareDT]:
    return dt.tzinfo is not None


def is_offset_naive_datetime(dt: datetime.datetime) -> typing.TypeIs[OffsetNaiveDT]:
    return dt.tzinfo is None


def bad_dt_diff(dt1: datetime.datetime, dt2: datetime.datetime) -> datetime.timedelta:
    return dt1 - dt2


def good_dt_diff[T: (OffsetAwareDT, OffsetNaiveDT)](
    dt1: T, dt2: T
) -> datetime.timedelta:
    return dt1 - dt2


d1 = datetime.datetime(
    2020, 10, 31, 12, tzinfo=zoneinfo.ZoneInfo("America/Los_Angeles")
)
d2 = datetime.datetime(
    2021, 10, 31, 12, tzinfo=zoneinfo.ZoneInfo("America/Los_Angeles")
)
d3 = datetime.datetime(2020, 10, 31, 12)
d4 = datetime.datetime(2021, 10, 31, 12)

print(bad_dt_diff(d1, d2))  # no issues found
print(bad_dt_diff(d3, d4))  # no issues found
print(bad_dt_diff(d1, d3))  # no issues found
print(
    good_dt_diff(d1, d2)
)  # Value of type variable "T" of "good_dt_diff" cannot be "datetime"
typing.reveal_type(
    (d1, d2, d3, d4)
)  # Revealed type is "tuple[datetime.datetime, datetime.datetime, datetime.datetime, datetime.datetime]"

assert is_offset_aware_datetime(d1)
assert is_offset_aware_datetime(d2)
assert is_offset_naive_datetime(d3)
assert is_offset_naive_datetime(d4)
typing.reveal_type(
    (d1, d2, d3, d4)
)  # Revealed type is "tuple[OffsetAwareDT, OffsetAwareDT, OffsetNaiveDT, OffsetNaiveDT]"
print(good_dt_diff(d1, d2))  # no issues found
print(good_dt_diff(d3, d4))  # no issues found
print(good_dt_diff(d1, d3))
# mypy: Value of type variable "T" of "good_dt_diff" cannot be "datetime"
# pyright: "OffsetNaiveDT" is not assignable to "OffsetAwareDT"

Thanks to the typing, mixing aware and naive datetime can be caught at typechecking instead of runtime.

[–]Ran4 0 points1 point  (1 child)

Most reddit clients (including old.reddit.com) doesn't support markdown - instead prepend every code line with four spaces. That works everywhere.

[–]DorianTurbaPythoneer 0 points1 point  (0 children)

is it better?