Open glyph opened 3 years ago
(I have to assume someone has asked for this already, but I promise I searched for it before filing!)
I happened to run into this due to the link with issue 2087. Assuming you know the data type somewhere in the process, wouldn't this be easily resolvable by using typing.NewType
and typing.cast
(or just# type: ignore
)? As a simple example:
import datetime, typing
WithTz = typing.NewType('WithTz', datetime.datetime)
WithoutTz = typing.NewType('WithoutTz', datetime.datetime)
a: WithoutTz = typing.cast(WithoutTz, datetime.datetime(2020, 12, 21))
b: WithTz = typing.cast(WithTz, datetime.datetime(2020, 12, 21))
def test_without(c: WithoutTz): pass
def test_with(c: WithTz): pass
test_without(a)
test_without(b)
test_with(a)
test_with(b)
This would yield incompatible type errors for test_without(b)
and test_with(a)
while correctly letting the others pass. The only downside would be the (in my opinion) slightly ugly call to typing.cast
(which you can also replace with an # type: ignore
if you'd really want) anywhere you create the variable(s). And this method would avoid the need for all kinds of mypy-specific subtypes.
Assuming you know the data type somewhere in the process
That's the trick though, isn't it :-). I'll note that even in your example, you might as well have used object()
since you didn't actually specify a timezone to your WithTz
.
(Also, no need for cast
, you can just do WithTz(datetime.datetime(...))
)
this method would avoid the need for all kinds of mypy-specific subtypes
but… you just defined two mypy-specific subtypes to accomplish this?
What if mypy
offered an overloaded datetime.__new__
that returns a tz-aware variant of the class?
I did a proof of concept here by just copying over the pyi file and starting from there, rather than trying to convince mypy to somehow transform the existing stubs (which is maybe impossible).
PEP 696 has a way to solve this using default and bound which should be timezone | None. datetime[timezone] would mean timezone-aware datetimes are expected to be passed, datetime[None] means naive datetimes and datetime[timezone | None] means you don’t care about awareness.
I actually implemented this change to my local copy of typeshed and found it surprisingly ergonomic.
@Gobot1234 brilliant! Could you elaborate on how you added this to your configuration?
I think this is also related to the way that mypy
types datetime.now().astimezone().tzinfo
as tzinfo | None
, since there is no way for the datetime
stub to declare that datetime.astimezone()
will always return a timezone-aware datetime
instance.
Feature
I would like to be able to have a way to say
foo: datetime
but know thatfoo
either:This could be resolved by an
Intersection[]
type as described in https://github.com/python/mypy/issues/2087 and a protocol that defines thetzinfo
attribute appropriately.Pitch
My database layer can only persist datetimes with a timezone; my timezone parsing code has portions where I want to make sure I haven't associated a timezone yet because to do so and then convert while replacing it would be an error. Really, naive datetimes and aware datetimes are subtly, but profoundly different types of objects, and their arithmetic represents different types of deltas.
Also, pytz requires different sorts of logic on arithmetic and comparison (i.e. possibly
normalize
is required) than other zoneinfo objects.