diff --git a/descarteslabs/core/catalog/attributes.py b/descarteslabs/core/catalog/attributes.py index 4b006afa..3496819e 100644 --- a/descarteslabs/core/catalog/attributes.py +++ b/descarteslabs/core/catalog/attributes.py @@ -16,11 +16,10 @@ import re from collections.abc import Iterable, Mapping, MutableMapping, MutableSequence -from datetime import datetime +from datetime import datetime, timezone from enum import Enum from strenum import StrEnum -from pytz import utc from ..common.shapely_support import ( geometry_like_to_shapely, @@ -35,12 +34,12 @@ def parse_iso_datetime(date_str): if len(date_str) > 27: # len(YYYY-MM-DDTHH:MM:SS.mmmmmmZ) == 27 date_str = date_str[0:26] + date_str[-1] date = datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%fZ") - return date.replace(tzinfo=utc) + return date.replace(tzinfo=timezone.utc) except ValueError: # it's possible that a utc formatted time string from the server won't have # a fractional seconds component date = datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%SZ") - return date.replace(tzinfo=utc) + return date.replace(tzinfo=timezone.utc) def serialize_datetime(value): @@ -585,7 +584,7 @@ def deserialize(self, value, validate=True): return value elif isinstance(value, datetime): if value.tzinfo is None: - return value.replace(tzinfo=utc) + return value.replace(tzinfo=timezone.utc) else: return value else: diff --git a/descarteslabs/core/catalog/tests/test_attributes.py b/descarteslabs/core/catalog/tests/test_attributes.py index a8b4fc48..ee26876d 100644 --- a/descarteslabs/core/catalog/tests/test_attributes.py +++ b/descarteslabs/core/catalog/tests/test_attributes.py @@ -16,7 +16,7 @@ import unittest import textwrap from copy import deepcopy -from datetime import datetime +from datetime import datetime, timezone from enum import Enum from strenum import StrEnum @@ -27,7 +27,6 @@ TypedAttribute, Timestamp, EnumAttribute, - utc, MappingAttribute, ListAttribute, DocumentState, @@ -76,16 +75,17 @@ def test_immutabletimestamp(self): assert date.deserialize(None) is None assert ( - date.deserialize("2019-02-01T00:00:00.0000Z", validate=False).tzinfo == utc + date.deserialize("2019-02-01T00:00:00.0000Z", validate=False).tzinfo + == timezone.utc ) assert date.deserialize( "2019-02-01T00:00:00.0000Z", validate=False - ) == datetime(2019, 2, 1, tzinfo=utc) + ) == datetime(2019, 2, 1, tzinfo=timezone.utc) date = Timestamp(readonly=True) assert date.deserialize( datetime(2013, 12, 31, 23, 59, 59), validate=False - ) == datetime(2013, 12, 31, 23, 59, 59, tzinfo=utc) + ) == datetime(2013, 12, 31, 23, 59, 59, tzinfo=timezone.utc) value = date.deserialize(datetime(2013, 12, 31, 23, 59, 59), validate=False) assert date.serialize(value) == "2013-12-31T23:59:59+00:00" @@ -94,7 +94,7 @@ def test_mutable_timestamp(self): assert mutable_date.deserialize(None) is None assert ( mutable_date.deserialize("2019-02-01T00:00:00.0000Z", validate=False).tzinfo - == utc + == timezone.utc ) class TimeObj(CatalogObject): @@ -154,7 +154,9 @@ def test_mapping_attributes(self): model_object = FakeCatalogObject(id="id", mapping=mapping) assert model_object.mapping.nested.foo == "foo" - assert model_object.mapping.nested.dt == datetime(2019, 2, 1, tzinfo=utc) + assert model_object.mapping.nested.dt == datetime( + 2019, 2, 1, tzinfo=timezone.utc + ) assert model_object.mapping is mapping assert model_object.mapping.nested is nested @@ -183,12 +185,14 @@ def test_mapping_change_tracking(self): # assigning a new attribute value to the model does propagate state changes new_mapping = Mapping( - nested=Nested(foo="bar", dt=datetime(2019, 3, 1, tzinfo=utc)) + nested=Nested(foo="bar", dt=datetime(2019, 3, 1, tzinfo=timezone.utc)) ) model_object.mapping = new_mapping assert model_object.is_modified assert model_object.mapping.nested.foo == "bar" - assert model_object.mapping.nested.dt == datetime(2019, 3, 1, tzinfo=utc) + assert model_object.mapping.nested.dt == datetime( + 2019, 3, 1, tzinfo=timezone.utc + ) assert model_object.mapping is new_mapping assert len(mapping._model_objects) == 0 @@ -306,8 +310,12 @@ def test_list_attributes(self): assert model_object.listmapping[0].nested.foo == "zap" assert model_object.listmapping[1].nested.foo == "zip" - assert model_object.listmapping[0].nested.dt == datetime(2019, 2, 1, tzinfo=utc) - assert model_object.listmapping[1].nested.dt == datetime(2019, 2, 2, tzinfo=utc) + assert model_object.listmapping[0].nested.dt == datetime( + 2019, 2, 1, tzinfo=timezone.utc + ) + assert model_object.listmapping[1].nested.dt == datetime( + 2019, 2, 2, tzinfo=timezone.utc + ) assert model_object.listmapping is not [mapping1, mapping2] assert model_object.listmapping[0] is mapping1 assert model_object.listattribute[0] == 12 @@ -356,12 +364,14 @@ def test_listattribute_change_tracking(self): # assigning a new attribute value to the model does propagate state changes new_mapping = Mapping( - nested=Nested(foo="meep", dt=datetime(2019, 3, 1, tzinfo=utc)) + nested=Nested(foo="meep", dt=datetime(2019, 3, 1, tzinfo=timezone.utc)) ) model_object.listmapping = [new_mapping] assert model_object.is_modified assert model_object.listmapping[0].nested.foo == "meep" - assert model_object.listmapping[0].nested.dt == datetime(2019, 3, 1, tzinfo=utc) + assert model_object.listmapping[0].nested.dt == datetime( + 2019, 3, 1, tzinfo=timezone.utc + ) def test_listattribute_deserialization(self): # Creation with valid enum should be fine diff --git a/descarteslabs/core/catalog/tests/test_catalog_base.py b/descarteslabs/core/catalog/tests/test_catalog_base.py index 7bde4daf..375aadb6 100644 --- a/descarteslabs/core/catalog/tests/test_catalog_base.py +++ b/descarteslabs/core/catalog/tests/test_catalog_base.py @@ -14,8 +14,8 @@ import pytest import responses -from datetime import datetime -from pytz import utc +from datetime import datetime, timezone + from descarteslabs.exceptions import ( NotFoundError, @@ -537,7 +537,7 @@ def test_update(self): assert c.is_modified def test_update_immutable_attr(self): - timestamp = datetime.now(utc) + timestamp = datetime.now(timezone.utc) c = CatalogObject(id="id", created=timestamp, _saved=True) assert not c.is_modified assert c.state == DocumentState.SAVED diff --git a/descarteslabs/core/catalog/tests/test_image.py b/descarteslabs/core/catalog/tests/test_image.py index d7677d6d..2fd15344 100644 --- a/descarteslabs/core/catalog/tests/test_image.py +++ b/descarteslabs/core/catalog/tests/test_image.py @@ -25,7 +25,6 @@ import responses import shapely.geometry from unittest.mock import patch -from pytz import utc from ...common.geo import AOI from ...common.property_filtering import Properties @@ -256,7 +255,7 @@ def test_nanosecond_timestamp(self): assert ( datetime.datetime.strptime( "2019-08-20T08:08:16.123456Z", "%Y-%m-%dT%H:%M:%S.%fZ" - ).replace(tzinfo=utc) + ).replace(tzinfo=datetime.timezone.utc) == i.acquired ) diff --git a/descarteslabs/core/catalog/tests/test_image_upload.py b/descarteslabs/core/catalog/tests/test_image_upload.py index 8207e198..ac425b2d 100644 --- a/descarteslabs/core/catalog/tests/test_image_upload.py +++ b/descarteslabs/core/catalog/tests/test_image_upload.py @@ -15,7 +15,7 @@ import responses from unittest.mock import patch -from datetime import datetime +from datetime import datetime, timezone from .base import ClientTestCase from ..catalog_base import DocumentState @@ -29,7 +29,6 @@ ImageUploadEventSeverity, ) from ..image import Image -from ..attributes import utc class TestImageUpload(ClientTestCase): @@ -222,8 +221,8 @@ def test_save(self): u.save() assert u.id == "1" - assert u.created == datetime(2020, 1, 1, 0, 0, 0, tzinfo=utc) - assert u.modified == datetime(2020, 1, 1, 0, 0, 0, tzinfo=utc) + assert u.created == datetime(2020, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + assert u.modified == datetime(2020, 1, 1, 0, 0, 0, tzinfo=timezone.utc) assert u.product_id == "product_id" assert u.image_id == "product_id:image_name" assert u.image_upload_options.upload_type == ImageUploadType.FILE @@ -239,7 +238,9 @@ def test_save(self): assert u.status == ImageUploadStatus.PENDING assert u.state == DocumentState.SAVED assert len(u.events) == 1 - assert u.events[0].event_datetime == datetime(2020, 1, 1, 0, 0, 0, tzinfo=utc) + assert u.events[0].event_datetime == datetime( + 2020, 1, 1, 0, 0, 0, tzinfo=timezone.utc + ) assert u.events[0].event_type == ImageUploadEventType.QUEUE assert u.events[0].severity == ImageUploadEventSeverity.INFO @@ -248,7 +249,9 @@ def test_save(self): assert u.status == ImageUploadStatus.CANCELED assert u.state == DocumentState.SAVED assert len(u.events) == 2 - assert u.events[1].event_datetime == datetime(2020, 1, 1, 0, 0, 0, tzinfo=utc) + assert u.events[1].event_datetime == datetime( + 2020, 1, 1, 0, 0, 0, tzinfo=timezone.utc + ) assert u.events[1].event_type == ImageUploadEventType.CANCEL assert u.events[1].severity == ImageUploadEventSeverity.INFO diff --git a/descarteslabs/core/common/client/tests/test_attributes.py b/descarteslabs/core/common/client/tests/test_attributes.py index e32055e9..cdae0f3a 100644 --- a/descarteslabs/core/common/client/tests/test_attributes.py +++ b/descarteslabs/core/common/client/tests/test_attributes.py @@ -15,7 +15,7 @@ import unittest from datetime import datetime, timezone -import pytz +from zoneinfo import ZoneInfo from .. import Attribute, DatetimeAttribute, Document, DocumentState, ListAttribute @@ -163,11 +163,11 @@ def test_deleted(self): class TestDatetimeAttribute(unittest.TestCase): def test_local_time(self): class TzTest(Document): - date: datetime = DatetimeAttribute(timezone=pytz.timezone("MST")) + date: datetime = DatetimeAttribute(timezone=ZoneInfo("MST")) now = datetime.utcnow() doc = TzTest(date=now.isoformat()) - assert doc.date.tzinfo == pytz.timezone("MST") + assert doc.date.tzinfo == ZoneInfo("MST") assert doc.date.astimezone(tz=timezone.utc) == now.replace(tzinfo=timezone.utc) assert doc.to_dict()["date"] == now.replace(tzinfo=timezone.utc).isoformat() @@ -181,7 +181,7 @@ class TrailingTest(Document): doc.date == now.replace(tzinfo=timezone.utc) def test_assign_instance(self): - tz = pytz.timezone("MST") + tz = ZoneInfo("MST") class InstanceTest(Document): date: datetime = DatetimeAttribute(timezone=tz) diff --git a/setup.py b/setup.py index eed8149b..988f9c6f 100644 --- a/setup.py +++ b/setup.py @@ -108,7 +108,6 @@ def do_setup(): "Pillow>=9.2.0", "pyarrow>=14.0.1", "pydantic>=2.4.0", - "pytz>=2021.1,<2024.2", "requests>=2.32.3,<3", # It is not obvious but dynaconf requires pkg_resources from setuptools. "setuptools>=70.0.0",