diff --git a/lib/galaxy/schema/schema.py b/lib/galaxy/schema/schema.py index b863546c343..cbbad2608ea 100644 --- a/lib/galaxy/schema/schema.py +++ b/lib/galaxy/schema/schema.py @@ -39,7 +39,10 @@ from galaxy.schema.fields import ( EncodedDatabaseIdField, ModelClassField, ) -from galaxy.schema.types import RelativeUrl +from galaxy.schema.types import ( + OffsetNaiveDatetime, + RelativeUrl, +) USER_MODEL_CLASS_NAME = "User" GROUP_MODEL_CLASS_NAME = "Group" @@ -1112,8 +1115,8 @@ class JobIndexQueryPayload(Model): user_id: Optional[DecodedDatabaseIdField] = None tool_ids: Optional[List[str]] = None tool_ids_like: Optional[List[str]] = None - date_range_min: Optional[Union[datetime, date]] = None - date_range_max: Optional[Union[datetime, date]] = None + date_range_min: Optional[Union[OffsetNaiveDatetime, date]] = None + date_range_max: Optional[Union[OffsetNaiveDatetime, date]] = None history_id: Optional[DecodedDatabaseIdField] = None workflow_id: Optional[DecodedDatabaseIdField] = None invocation_id: Optional[DecodedDatabaseIdField] = None diff --git a/lib/galaxy/schema/types.py b/lib/galaxy/schema/types.py index cf39c391d3a..49b42126907 100644 --- a/lib/galaxy/schema/types.py +++ b/lib/galaxy/schema/types.py @@ -1,3 +1,6 @@ +from datetime import datetime + +from pydantic.datetime_parse import parse_datetime from typing_extensions import Literal # Relative URLs cannot be validated with AnyUrl, they need a scheme. @@ -5,3 +8,14 @@ from typing_extensions import Literal RelativeUrl = str LatestLiteral = Literal["latest"] + + +class OffsetNaiveDatetime(datetime): + @classmethod + def __get_validators__(cls): + yield cls.validate + + @classmethod + def validate(cls, v): + v = parse_datetime(v) + return v.replace(tzinfo=None) - v.utcoffset() if v.tzinfo else v diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py index d88ba5c40f8..1eb13167b58 100644 --- a/lib/galaxy/webapps/galaxy/api/jobs.py +++ b/lib/galaxy/webapps/galaxy/api/jobs.py @@ -38,6 +38,7 @@ from galaxy.managers.jobs import ( ) from galaxy.schema.fields import EncodedDatabaseIdField from galaxy.schema.schema import JobIndexSortByEnum +from galaxy.schema.types import OffsetNaiveDatetime from galaxy.util import listify from galaxy.web import ( expose_api, @@ -103,13 +104,13 @@ ToolIdLikeQueryParam: Optional[str] = Query( description="Limit listing of jobs to those that match one of the included tool ID sql-like patterns. If none, all are returned", ) -DateRangeMinQueryParam: Optional[Union[datetime, date]] = Query( +DateRangeMinQueryParam: Optional[Union[OffsetNaiveDatetime, date]] = Query( default=None, title="Date Range Minimum", description="Limit listing of jobs to those that are updated after specified date (e.g. '2014-01-01')", ) -DateRangeMaxQueryParam: Optional[Union[datetime, date]] = Query( +DateRangeMaxQueryParam: Optional[Union[OffsetNaiveDatetime, date]] = Query( default=None, title="Date Range Maximum", description="Limit listing of jobs to those that are updated before specified date (e.g. '2014-01-01')", diff --git a/test/unit/webapps/api/test_datetime_parsing.py b/test/unit/webapps/api/test_datetime_parsing.py new file mode 100644 index 00000000000..29ea26b14ff --- /dev/null +++ b/test/unit/webapps/api/test_datetime_parsing.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel + +from galaxy.schema.types import OffsetNaiveDatetime + + +class Time(BaseModel): + time: OffsetNaiveDatetime + + +def test_naive_datetime_parsing(): + with_tz = Time(time="2022-08-15T11:29:32.853974+02:00") + without_tz = Time(time="2022-08-15T09:29:32.853974") + assert with_tz.time == without_tz.time + assert with_tz.time.tzinfo is None