From d952501c4d84a61eceb7e2654c9081b80b9946f9 Mon Sep 17 00:00:00 2001 From: Dan Allan Date: Fri, 10 Jan 2025 10:21:57 -0500 Subject: [PATCH 1/9] Avoid deprecated datetime.utcnow() --- tiled/server/authentication.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tiled/server/authentication.py b/tiled/server/authentication.py index 6b325fc2c..c3e0528d8 100644 --- a/tiled/server/authentication.py +++ b/tiled/server/authentication.py @@ -3,7 +3,7 @@ import secrets import uuid as uuid_module import warnings -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from pathlib import Path from typing import Optional @@ -83,7 +83,7 @@ def utcnow(): "UTC now with second resolution" - return datetime.utcnow().replace(microsecond=0) + return datetime.now(UTC).replace(microsecond=0) class Mode(enum.Enum): @@ -1038,7 +1038,11 @@ async def slide_session(refresh_token, settings, db): now = utcnow() # This token is *signed* so we know that the information came from us. # If the Session is forgotten or revoked or expired, do not allow refresh. - if (session is None) or session.revoked or (session.expiration_time < now): + if ( + (session is None) + or session.revoked + or (session.expiration_time.replace(tzinfo=UTC) < now) + ): # Do not leak (to a potential attacker) whether this has been *revoked* # specifically. Give the same error as if it had expired. raise HTTPException( From 845defa33d540a23275a25272fb68fe946f31828 Mon Sep 17 00:00:00 2001 From: Dan Allan Date: Fri, 10 Jan 2025 10:39:05 -0500 Subject: [PATCH 2/9] More datetime.utcnow() --- tiled/authn_database/core.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tiled/authn_database/core.py b/tiled/authn_database/core.py index c03d87b58..248ddc45d 100644 --- a/tiled/authn_database/core.py +++ b/tiled/authn_database/core.py @@ -1,6 +1,6 @@ import hashlib import uuid as uuid_module -from datetime import datetime +from datetime import UTC, datetime from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select @@ -77,12 +77,12 @@ async def purge_expired(db, cls): """ Remove expired entries. """ - now = datetime.utcnow() + now = datetime.now(UTC) num_expired = 0 statement = ( select(cls) .filter(cls.expiration_time.is_not(None)) - .filter(cls.expiration_time < now) + .filter(cls.expiration_time.replace(tzinfo=UTC) < now) ) result = await db.execute(statement) for obj in result.scalars(): @@ -144,10 +144,9 @@ async def lookup_valid_session(db, session_id): ).scalar() if session is None: return None - if ( - session.expiration_time is not None - and session.expiration_time < datetime.utcnow() - ): + if session.expiration_time is not None and session.expiration_time.replace( + tzinfo=UTC + ) < datetime.now(UTC): await db.delete(session) await db.commit() return None @@ -171,7 +170,7 @@ async def lookup_valid_pending_session_by_device_code(db, device_code): return None if ( pending_session.expiration_time is not None - and pending_session.expiration_time < datetime.utcnow() + and pending_session.expiration_time.replace(tzinfo=UTC) < datetime.now(UTC) ): await db.delete(pending_session) await db.commit() @@ -189,7 +188,7 @@ async def lookup_valid_pending_session_by_user_code(db, user_code): return None if ( pending_session.expiration_time is not None - and pending_session.expiration_time < datetime.utcnow() + and pending_session.expiration_time.replace(tzinfo=UTC) < datetime.now(UTC) ): await db.delete(pending_session) await db.commit() @@ -228,7 +227,7 @@ async def lookup_valid_api_key(db, secret): Look up an API key. Ensure that it is valid. """ - now = datetime.utcnow() + now = datetime.now(UTC) hashed_secret = hashlib.sha256(secret).digest() api_key = ( await db.execute( @@ -244,7 +243,9 @@ async def lookup_valid_api_key(db, secret): if api_key is None: # No match validated_api_key = None - elif (api_key.expiration_time is not None) and (api_key.expiration_time < now): + elif (api_key.expiration_time is not None) and ( + api_key.expiration_time.replace(tzinfo=UTC) < now + ): # Match is expired. Delete it. await db.delete(api_key) await db.commit() From 22c1930378541fd89c34bf875f9b9048d63a46b3 Mon Sep 17 00:00:00 2001 From: Dan Allan Date: Fri, 10 Jan 2025 11:00:44 -0500 Subject: [PATCH 3/9] Address the rest of datetime.now() --- tiled/adapters/mapping.py | 6 +++--- tiled/server/core.py | 4 ++-- tiled/server/router.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tiled/adapters/mapping.py b/tiled/adapters/mapping.py index 52b5b55f9..b066ffd65 100644 --- a/tiled/adapters/mapping.py +++ b/tiled/adapters/mapping.py @@ -4,7 +4,7 @@ import operator import sys from collections import Counter -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from typing import ( TYPE_CHECKING, Any, @@ -286,7 +286,7 @@ def metadata_stale_at(self) -> Optional[datetime]: """ if self.metadata_stale_after is None: return None - return self.metadata_stale_after + datetime.utcnow() + return self.metadata_stale_after + datetime.now(UTC) @property def entries_stale_at(self) -> Optional[datetime]: @@ -298,7 +298,7 @@ def entries_stale_at(self) -> Optional[datetime]: """ if self.entries_stale_after is None: return None - return self.entries_stale_after + datetime.utcnow() + return self.entries_stale_after + datetime.now(UTC) def new_variation( self, diff --git a/tiled/server/core.py b/tiled/server/core.py index 88e3d20da..6d84fcd18 100644 --- a/tiled/server/core.py +++ b/tiled/server/core.py @@ -8,7 +8,7 @@ import types import uuid from collections import defaultdict -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from hashlib import md5 from typing import Any @@ -225,7 +225,7 @@ async def construct_entries_response( keys = tree.keys()[offset : offset + limit] # noqa: E203 items = [(key, None) for key in keys] # This value will not leak out. It just used to seed comparisons. - metadata_stale_at = datetime.utcnow() + timedelta(days=1_000_000) + metadata_stale_at = datetime.now(UTC) + timedelta(days=1_000_000) must_revalidate = getattr(tree, "must_revalidate", True) for key, entry in items: resource = await construct_resource( diff --git a/tiled/server/router.py b/tiled/server/router.py index 050baf62f..7fb935ba6 100644 --- a/tiled/server/router.py +++ b/tiled/server/router.py @@ -3,7 +3,7 @@ import os import re import warnings -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from functools import partial from pathlib import Path from typing import Any, List, Optional @@ -151,7 +151,7 @@ async def about( }, meta={"root_path": request.scope.get("root_path") or "" + "/api"}, ).model_dump(), - expires=datetime.utcnow() + timedelta(seconds=600), + expires=datetime.now(UTC) + timedelta(seconds=600), ) From 4c9d842b120a3b64d522551748ccc28bc56898c6 Mon Sep 17 00:00:00 2001 From: Dan Allan Date: Fri, 10 Jan 2025 11:21:00 -0500 Subject: [PATCH 4/9] Use arrays rather than lists. --- tiled/_tests/test_catalog.py | 4 ++-- tiled/_tests/test_export.py | 4 +++- tiled/_tests/test_inlined_contents.py | 4 ++-- tiled/_tests/test_sync.py | 4 +++- tiled/_tests/test_writing.py | 18 ++++++++++++++---- tiled/client/container.py | 7 ++++--- 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/tiled/_tests/test_catalog.py b/tiled/_tests/test_catalog.py index d40dc5548..7c5eb0a39 100644 --- a/tiled/_tests/test_catalog.py +++ b/tiled/_tests/test_catalog.py @@ -312,8 +312,8 @@ def test_write_dataframe_internal_via_client(client): def test_write_xarray_dataset(client): ds = xarray.Dataset( - {"temp": (["time"], [101, 102, 103])}, - coords={"time": (["time"], [1, 2, 3])}, + {"temp": (["time"], numpy.array([101, 102, 103]))}, + coords={"time": (["time"], numpy.array([1, 2, 3]))}, ) dsc = write_xarray_dataset(client, ds, key="test_xarray_dataset") assert set(dsc) == {"temp", "time"} diff --git a/tiled/_tests/test_export.py b/tiled/_tests/test_export.py index 99ae2af6c..44963bd71 100644 --- a/tiled/_tests/test_export.py +++ b/tiled/_tests/test_export.py @@ -53,7 +53,9 @@ coords={ "lon": (["x", "y"], lon), "lat": (["x", "y"], lat), - "time": [1, 2, 3], # using ints here so HDF5 can export + "time": numpy.array( + [1, 2, 3] + ), # using ints here so HDF5 can export }, ) ), diff --git a/tiled/_tests/test_inlined_contents.py b/tiled/_tests/test_inlined_contents.py index 82dbe9577..5fc6224e2 100644 --- a/tiled/_tests/test_inlined_contents.py +++ b/tiled/_tests/test_inlined_contents.py @@ -20,8 +20,8 @@ { "dataset": DatasetAdapter.from_dataset( xarray.Dataset( - data_vars={"temperature": ("time", [100, 99, 98])}, - coords={"time": [1, 2, 3]}, + data_vars={"temperature": ("time", numpy.array([100, 99, 98]))}, + coords={"time": numpy.array([1, 2, 3])}, ) ), }, diff --git a/tiled/_tests/test_sync.py b/tiled/_tests/test_sync.py index 468054618..e5f9e5445 100644 --- a/tiled/_tests/test_sync.py +++ b/tiled/_tests/test_sync.py @@ -65,7 +65,9 @@ def populate_internal(client): awkward.Array([1, [2, 3]]), key="d", metadata={"color": "red"}, specs=["alpha"] ) # sparse - coo = sparse.COO(coords=[[2, 5]], data=[1.3, 7.5], shape=(10,)) + coo = sparse.COO( + coords=numpy.array([[2, 5]]), data=numpy.array([1.3, 7.5]), shape=(10,) + ) client.write_sparse(key="e", coords=coo.coords, data=coo.data, shape=coo.shape) # nested diff --git a/tiled/_tests/test_writing.py b/tiled/_tests/test_writing.py index 731f55ab0..035243c20 100644 --- a/tiled/_tests/test_writing.py +++ b/tiled/_tests/test_writing.py @@ -253,8 +253,14 @@ def test_write_dataframe_dict(tree): @pytest.mark.parametrize( "coo", [ - sparse.COO(coords=[[2, 5]], data=[1.3, 7.5], shape=(10,)), - sparse.COO(coords=[[0, 1], [2, 3]], data=[3.8, 4.0], shape=(4, 4)), + sparse.COO( + coords=numpy.array([[2, 5]]), data=numpy.array([1.3, 7.5]), shape=(10,) + ), + sparse.COO( + coords=numpy.array([[0, 1], [2, 3]]), + data=numpy.array([3.8, 4.0]), + shape=(4, 4), + ), ], ) def test_write_sparse_full(tree, coo): @@ -317,7 +323,9 @@ def test_write_sparse_chunked(tree): assert numpy.array_equal( result_array.todense(), sparse.COO( - coords=[[2, 4, N + 0, N + 1]], data=[3.1, 2.8, 6.7, 1.2], shape=(10,) + coords=numpy.array([[2, 4, N + 0, N + 1]]), + data=numpy.array([3.1, 2.8, 6.7, 1.2]), + shape=(10,), ).todense(), ) @@ -543,7 +551,9 @@ async def test_write_in_container(tree): client.delete("a") a = client.create_container("a") - coo = sparse.COO(coords=[[2, 5]], data=[1.3, 7.5], shape=(10,)) + coo = sparse.COO( + coords=numpy.array([[2, 5]]), data=numpy.array([1.3, 7.5]), shape=(10,) + ) b = a.write_sparse(coords=coo.coords, data=coo.data, shape=coo.shape, key="b") b.read() a.delete("b") diff --git a/tiled/client/container.py b/tiled/client/container.py index a521ef2a9..baeadfbc2 100644 --- a/tiled/client/container.py +++ b/tiled/client/container.py @@ -879,7 +879,8 @@ def write_sparse( Write a sparse.COO array. >>> import sparse - >>> coo = sparse.COO(coords=[[2, 5]], data=[1.3, 7.5], shape=(10,)) + >>> from numpy import array + >>> coo = sparse.COO(coords=array([[2, 5]]), data=array([1.3, 7.5]), shape=(10,)) >>> c.write_sparse(coords=coo.coords, data=coo.data, shape=coo.shape) This only supports a single chunk. For chunked upload, use lower-level methods. @@ -891,8 +892,8 @@ def write_sparse( >>> x = c.new("sparse", [data_source]) # Upload the data in each chunk. # Coords are given with in the reference frame of each chunk. - >>> x.write_block(coords=[[2, 4]], data=[3.1, 2.8], block=(0,)) - >>> x.write_block(coords=[[0, 1]], data=[6.7, 1.2], block=(1,)) + >>> x.write_block(coords=array([[2, 4]]), data=array([3.1, 2.8]), block=(0,)) + >>> x.write_block(coords=array([[0, 1]]), data=array([6.7, 1.2]), block=(1,)) """ from ..structures.sparse import COOStructure From 6daaea607ac3c22286a1837554ff5b8243bf39a0 Mon Sep 17 00:00:00 2001 From: Dan Allan Date: Fri, 10 Jan 2025 11:29:27 -0500 Subject: [PATCH 5/9] Remove deprecated pandas parameter --- tiled/examples/xdi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiled/examples/xdi.py b/tiled/examples/xdi.py index 828ad2c52..89e5b03c4 100644 --- a/tiled/examples/xdi.py +++ b/tiled/examples/xdi.py @@ -80,7 +80,7 @@ def read_xdi(data_uri, structure=None, metadata=None, specs=None, access_policy= # TODO validate - df = pd.read_table(file, delim_whitespace=True, names=col_labels) + df = pd.read_table(file, sep=r"\s+", names=col_labels) return DataFrameAdapter.from_pandas( df, From 5475e4e8fb030f7c5d53b3b959fc720d012b6d53 Mon Sep 17 00:00:00 2001 From: Dan Allan Date: Fri, 10 Jan 2025 11:29:40 -0500 Subject: [PATCH 6/9] Update usage of TemplateResponse --- tiled/server/app.py | 2 +- tiled/server/authentication.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tiled/server/app.py b/tiled/server/app.py index 1f991d666..65a67ecb8 100644 --- a/tiled/server/app.py +++ b/tiled/server/app.py @@ -270,9 +270,9 @@ async def index( principal=Security(get_current_principal, scopes=[]), ): return templates.TemplateResponse( + request, "index.html", { - "request": request, # This is used to construct the link to the React UI. "root_url": get_root_url(request), # If defined, this adds a Binder link to the page. diff --git a/tiled/server/authentication.py b/tiled/server/authentication.py index c3e0528d8..2feefc8d6 100644 --- a/tiled/server/authentication.py +++ b/tiled/server/authentication.py @@ -587,9 +587,9 @@ async def route( f"{get_base_url(request)}/auth/provider/{provider}/device_code?code={code}" ) return templates.TemplateResponse( + request, "device_code_form.html", { - "request": request, "code": code, "action": action, }, @@ -627,9 +627,9 @@ async def route( if pending_session is None: message = "Invalid user code. It may have been mistyped, or the pending request may have expired." return templates.TemplateResponse( + request, "device_code_form.html", { - "request": request, "code": code, "action": action, "message": message, @@ -639,9 +639,9 @@ async def route( user_session_state = await authenticator.authenticate(request) if not user_session_state: return templates.TemplateResponse( + request, "device_code_failure.html", { - "request": request, "message": ( "User code was correct but authentication with third party failed. " "Ask administrator to see logs for details." @@ -660,9 +660,9 @@ async def route( db.add(pending_session) await db.commit() return templates.TemplateResponse( + request, "device_code_success.html", { - "request": request, "interval": DEVICE_CODE_POLLING_INTERVAL, }, ) From 602110d119c2d4bd69aca5998d55144958608530 Mon Sep 17 00:00:00 2001 From: Dan Allan Date: Fri, 10 Jan 2025 11:45:19 -0500 Subject: [PATCH 7/9] Update deprecated usage of FileResponse. --- tiled/server/app.py | 1 - tiled/server/router.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tiled/server/app.py b/tiled/server/app.py index 65a67ecb8..c0fe0c8ac 100644 --- a/tiled/server/app.py +++ b/tiled/server/app.py @@ -251,7 +251,6 @@ async def lookup_file(path, try_app=True): return FileResponse( full_path, stat_result=stat_result, - method="GET", status_code=HTTP_200_OK, ) diff --git a/tiled/server/router.py b/tiled/server/router.py index 7fb935ba6..8ad5128a7 100644 --- a/tiled/server/router.py +++ b/tiled/server/router.py @@ -1646,7 +1646,6 @@ async def get_asset( return FileResponseWithRange( full_path, stat_result=stat_result, - method="GET", status_code=status_code, headers={"Content-Disposition": f'attachment; filename="{filename}"'}, range=range, From fc80f79293d12a6ed4a5b374b4836f9090c24c9c Mon Sep 17 00:00:00 2001 From: Dan Allan Date: Fri, 10 Jan 2025 11:54:51 -0500 Subject: [PATCH 8/9] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f445121d4..b1428c5ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ Write the date in place of the "Unreleased" in the case a new version is release # Changelog +## Unreleased + +### Maintenance + +- Addressed DeprecationWarnings from Python and dependencies + ## v0.1.0-b13 (2024-01-09) ### Added From a364e226659436fb9057aedd688c4d4404f0db38 Mon Sep 17 00:00:00 2001 From: Dan Allan Date: Fri, 10 Jan 2025 12:18:08 -0500 Subject: [PATCH 9/9] Use UTC compatible with py<3.12 --- tiled/adapters/mapping.py | 6 +++--- tiled/authn_database/core.py | 20 +++++++++++--------- tiled/server/authentication.py | 6 +++--- tiled/server/core.py | 4 ++-- tiled/server/router.py | 4 ++-- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/tiled/adapters/mapping.py b/tiled/adapters/mapping.py index b066ffd65..cc528e94e 100644 --- a/tiled/adapters/mapping.py +++ b/tiled/adapters/mapping.py @@ -4,7 +4,7 @@ import operator import sys from collections import Counter -from datetime import UTC, datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import ( TYPE_CHECKING, Any, @@ -286,7 +286,7 @@ def metadata_stale_at(self) -> Optional[datetime]: """ if self.metadata_stale_after is None: return None - return self.metadata_stale_after + datetime.now(UTC) + return self.metadata_stale_after + datetime.now(timezone.utc) @property def entries_stale_at(self) -> Optional[datetime]: @@ -298,7 +298,7 @@ def entries_stale_at(self) -> Optional[datetime]: """ if self.entries_stale_after is None: return None - return self.entries_stale_after + datetime.now(UTC) + return self.entries_stale_after + datetime.now(timezone.utc) def new_variation( self, diff --git a/tiled/authn_database/core.py b/tiled/authn_database/core.py index 248ddc45d..7eaee7804 100644 --- a/tiled/authn_database/core.py +++ b/tiled/authn_database/core.py @@ -1,6 +1,6 @@ import hashlib import uuid as uuid_module -from datetime import UTC, datetime +from datetime import datetime, timezone from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select @@ -77,12 +77,12 @@ async def purge_expired(db, cls): """ Remove expired entries. """ - now = datetime.now(UTC) + now = datetime.now(timezone.utc) num_expired = 0 statement = ( select(cls) .filter(cls.expiration_time.is_not(None)) - .filter(cls.expiration_time.replace(tzinfo=UTC) < now) + .filter(cls.expiration_time.replace(tzinfo=timezone.utc) < now) ) result = await db.execute(statement) for obj in result.scalars(): @@ -145,8 +145,8 @@ async def lookup_valid_session(db, session_id): if session is None: return None if session.expiration_time is not None and session.expiration_time.replace( - tzinfo=UTC - ) < datetime.now(UTC): + tzinfo=timezone.utc + ) < datetime.now(timezone.utc): await db.delete(session) await db.commit() return None @@ -170,7 +170,8 @@ async def lookup_valid_pending_session_by_device_code(db, device_code): return None if ( pending_session.expiration_time is not None - and pending_session.expiration_time.replace(tzinfo=UTC) < datetime.now(UTC) + and pending_session.expiration_time.replace(tzinfo=timezone.utc) + < datetime.now(timezone.utc) ): await db.delete(pending_session) await db.commit() @@ -188,7 +189,8 @@ async def lookup_valid_pending_session_by_user_code(db, user_code): return None if ( pending_session.expiration_time is not None - and pending_session.expiration_time.replace(tzinfo=UTC) < datetime.now(UTC) + and pending_session.expiration_time.replace(tzinfo=timezone.utc) + < datetime.now(timezone.utc) ): await db.delete(pending_session) await db.commit() @@ -227,7 +229,7 @@ async def lookup_valid_api_key(db, secret): Look up an API key. Ensure that it is valid. """ - now = datetime.now(UTC) + now = datetime.now(timezone.utc) hashed_secret = hashlib.sha256(secret).digest() api_key = ( await db.execute( @@ -244,7 +246,7 @@ async def lookup_valid_api_key(db, secret): # No match validated_api_key = None elif (api_key.expiration_time is not None) and ( - api_key.expiration_time.replace(tzinfo=UTC) < now + api_key.expiration_time.replace(tzinfo=timezone.utc) < now ): # Match is expired. Delete it. await db.delete(api_key) diff --git a/tiled/server/authentication.py b/tiled/server/authentication.py index 2feefc8d6..811322469 100644 --- a/tiled/server/authentication.py +++ b/tiled/server/authentication.py @@ -3,7 +3,7 @@ import secrets import uuid as uuid_module import warnings -from datetime import UTC, datetime, timedelta +from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Optional @@ -83,7 +83,7 @@ def utcnow(): "UTC now with second resolution" - return datetime.now(UTC).replace(microsecond=0) + return datetime.now(timezone.utc).replace(microsecond=0) class Mode(enum.Enum): @@ -1041,7 +1041,7 @@ async def slide_session(refresh_token, settings, db): if ( (session is None) or session.revoked - or (session.expiration_time.replace(tzinfo=UTC) < now) + or (session.expiration_time.replace(tzinfo=timezone.utc) < now) ): # Do not leak (to a potential attacker) whether this has been *revoked* # specifically. Give the same error as if it had expired. diff --git a/tiled/server/core.py b/tiled/server/core.py index 6d84fcd18..65be00c5a 100644 --- a/tiled/server/core.py +++ b/tiled/server/core.py @@ -8,7 +8,7 @@ import types import uuid from collections import defaultdict -from datetime import UTC, datetime, timedelta +from datetime import datetime, timedelta, timezone from hashlib import md5 from typing import Any @@ -225,7 +225,7 @@ async def construct_entries_response( keys = tree.keys()[offset : offset + limit] # noqa: E203 items = [(key, None) for key in keys] # This value will not leak out. It just used to seed comparisons. - metadata_stale_at = datetime.now(UTC) + timedelta(days=1_000_000) + metadata_stale_at = datetime.now(timezone.utc) + timedelta(days=1_000_000) must_revalidate = getattr(tree, "must_revalidate", True) for key, entry in items: resource = await construct_resource( diff --git a/tiled/server/router.py b/tiled/server/router.py index 8ad5128a7..ac8e9799d 100644 --- a/tiled/server/router.py +++ b/tiled/server/router.py @@ -3,7 +3,7 @@ import os import re import warnings -from datetime import UTC, datetime, timedelta +from datetime import datetime, timedelta, timezone from functools import partial from pathlib import Path from typing import Any, List, Optional @@ -151,7 +151,7 @@ async def about( }, meta={"root_path": request.scope.get("root_path") or "" + "/api"}, ).model_dump(), - expires=datetime.now(UTC) + timedelta(seconds=600), + expires=datetime.now(timezone.utc) + timedelta(seconds=600), )