-
Notifications
You must be signed in to change notification settings - Fork 91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Timestamptz
autoconversion
#978
base: master
Are you sure you want to change the base?
Conversation
…ordering with isort
…s, migration tests are not fixed
Codecov ReportAttention: Patch coverage is
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## master #978 +/- ##
==========================================
- Coverage 92.78% 92.77% -0.02%
==========================================
Files 108 109 +1
Lines 8182 8216 +34
==========================================
+ Hits 7592 7622 +30
- Misses 590 594 +4 ☔ View full report in Codecov by Sentry. |
I think this is almost done now. Update: |
Now we need to work out what to do with SQLite - I might just raise a Update: I was able to get SQLite working. |
@dantownsend I tried your solution and everything worked great. Here is a Piccolo # if we declare a time zone in the table definition
# class TallinnConcerts(Table):
# event_start = Timestamptz(at_time_zone=ZoneInfo("Europe/Tallinn"))
In [1]: await TallinnConcerts.select()
Out[1]:
[{'id': 1,
'event_start': datetime.datetime(2024, 4, 5, 10, 44, 46, tzinfo=zoneinfo.ZoneInfo(key='Europe/Tallinn'))},
{'id': 2,
'event_start': datetime.datetime(2024, 4, 5, 10, 44, 46, tzinfo=zoneinfo.ZoneInfo(key='Europe/Tallinn'))}]
In [2]: [i.to_dict() for i in await TallinnConcerts.objects()]
Out[2]:
[{'id': 1,
'event_start': datetime.datetime(2024, 4, 5, 10, 44, 46, tzinfo=zoneinfo.ZoneInfo(key='Europe/Tallinn'))},
{'id': 2,
'event_start': datetime.datetime(2024, 4, 5, 10, 44, 46, tzinfo=zoneinfo.ZoneInfo(key='Europe/Tallinn'))}]
# if we do not declare a timezone in the table definition, we can define `at_time_zone` in the select query
# class TallinnConcerts(Table):
# event_start = Timestamptz()
In [3]: await TallinnConcerts.select(TallinnConcerts.event_start.at_time_zone("Europe/Tallinn"))
Out[3]:
[{'event_start': datetime.datetime(2024, 4, 6, 16, 37, 33, tzinfo=zoneinfo.ZoneInfo(key='Europe/Tallinn'))},
{'event_start': datetime.datetime(2024, 4, 6, 16, 37, 33, tzinfo=zoneinfo.ZoneInfo(key='Europe/Tallinn'))}] Piccolo Admin also work. |
@sinisaos Thanks a lot for testing it. What do you think about the design of it? I changed it to be: Timestamptz(at_time_zone=ZoneInfo("America/New_York")) So the argument is I was thinking of adding a method to the await Concert.select(Concert.starts_at.at_time_zone('America/Los_Angeles')) But there's currently no way of overriding it for await Concert.objects().at_time_zone({Concert.starts_at: 'America/Los_Angeles'}) |
@dantownsend Everything looks and works great to me, as I wrote in the previous comment.
It would be great to have that |
) -> None: | ||
self._validate_default( | ||
default, TimestamptzArg.__args__ # type: ignore | ||
) | ||
|
||
if isinstance(default, datetime): | ||
default = TimestamptzCustom.from_datetime(default) | ||
default = TimestamptzCustom.from_datetime(default, at_time_zone) | ||
|
||
if default == datetime.now: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused about this if
statement. It seems that datetime.now
is a callable, which doesn't seem to align with the TimestamptzArg
from the signature. By the way, it seems that the test doesn't cover this scenario involving a callable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, it should be the line if default == datetime.now:
.
@@ -5,3 +5,5 @@ targ>=0.3.7 | |||
inflection>=0.5.1 | |||
typing-extensions>=4.3.0 | |||
pydantic[email]==2.* | |||
tzdata>=2024.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on my experience with zoneinfo
on Windows
machines, it can exhibit some unexpected behavior even after installing tzdata. If piccolo
aims to support cross-platform conditions, I suggest running continuous integration tests for timestamp-related functionality in the Windows
container, at least for a period, once this PR is merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dantownsend @sinisaos This might be a little off-topic, but have you guys heard about uv? I've used it for a couple of personal projects and it looks promising to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jrycw Sorry, I've never used uv
. I always use virtualenv
and I'm used to it.
>>> await Concert( | ||
... starts=datetime.datetime( | ||
... year=2050, month=1, day=1, tzinfo=datetime.timezone.tz |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that tzinfo=datetime.timezone.tz
should be replaced with tzinfo=datetime.timezone.utc
in the old documentation.
) -> str: | ||
select_string = self._meta.get_full_name(with_alias=False) | ||
|
||
if self._at_time_zone != ZoneInfo("UTC"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There might be other instances where timezone comparisons are performed similarly, and I'll use this one as an example. It seems this type of comparison could lead to unexpected outcomes. The following example might illustrate this unpredictability.
>>> import datetime
>>> from zoneinfo import ZoneInfo
>>> tz1 = datetime.timezone.utc
>>> tz2 = ZoneInfo("UTC")
>>> d1 = datetime.datetime(year=2024, month=4, day=7, tzinfo=tz1)
>>> d2 = datetime.datetime(year=2024, month=4, day=7, tzinfo=tz2)
>>> d1 == d2
True
>>> str(d1) == str(d2)
True
>>> d1.tzinfo, d2.tzinfo
(datetime.timezone.utc, zoneinfo.ZoneInfo(key='UTC'))
>>> d1.tzinfo == d2.tzinfo
False
>>> d1.tzinfo is d2.tzinfo
False
def at_time_zone(self, time_zone: t.Union[ZoneInfo, str]) -> Timestamptz: | ||
""" | ||
By default, the database returns the value in UTC. This lets us get | ||
the value converted to the specified timezone. | ||
""" | ||
time_zone = ( | ||
ZoneInfo(time_zone) if isinstance(time_zone, str) else time_zone | ||
) | ||
instance = self.copy() | ||
instance._at_time_zone = time_zone | ||
return instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding #959, it appears that we are creating a new object by copying self
and then modifying the _at_time_zone
attribute of that object to match the time_zone
variable, without altering self
. Is my understanding correct?
if self._at_time_zone != ZoneInfo("UTC"): | ||
# SQLite doesn't support `AT TIME ZONE`, so we have to do it in | ||
# Python instead (see ``Select.response_handler``). | ||
if self._meta.engine_type in ("postgres", "cockroach"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Occurrences of in
or ==
checks like if self._meta.engine_type in ("postgres", "cockroach"):
seem to be becoming increasingly common. I would recommend using data structures like Enum
to mitigate typos and rely more on auto-completion while coding.
I quite like this |
Based off #959
Created this branch for testing. Experimenting with the
AT TIME ZONE
clause.Remaining tasks:
select
andobjects
queries workingat_time_zone
methodselect
queriesat_time_zone
methodObjects._get_select_query
a bitat_time_zone
method toobjects