From 8056bcd8c1e16c3481f41f7763e64c3099e835a6 Mon Sep 17 00:00:00 2001 From: Matvey Arye Date: Wed, 8 Nov 2023 10:20:59 -0500 Subject: [PATCH] accept dates in string format for UUIDTmeRange --- nbs/00_vector.ipynb | 74 ++++++++++++++++++++----------------- timescale_vector/_modidx.py | 4 +- timescale_vector/client.py | 70 ++++++++++++++++++----------------- 3 files changed, 80 insertions(+), 68 deletions(-) diff --git a/nbs/00_vector.ipynb b/nbs/00_vector.ipynb index a37ab54..b4c49ae 100644 --- a/nbs/00_vector.ipynb +++ b/nbs/00_vector.ipynb @@ -296,12 +296,46 @@ "source": [ "#| export\n", "class UUIDTimeRange:\n", - " def __init__(self, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None, time_delta: Optional[timedelta] = None, start_inclusive=True, end_inclusive=False):\n", + " \n", + " @staticmethod\n", + " def _parse_datetime(input_datetime: Union[datetime, str]):\n", + " \"\"\"\n", + " Parse a datetime object or string representation of a datetime.\n", + "\n", + " Args:\n", + " input_datetime (datetime or str): Input datetime or string.\n", + "\n", + " Returns:\n", + " datetime: Parsed datetime object.\n", + "\n", + " Raises:\n", + " ValueError: If the input cannot be parsed as a datetime.\n", + " \"\"\"\n", + " if input_datetime is None or input_datetime == \"None\":\n", + " return None\n", + " \n", + " if isinstance(input_datetime, datetime):\n", + " # If input is already a datetime object, return it as is\n", + " return input_datetime\n", + "\n", + " if isinstance(input_datetime, str):\n", + " try:\n", + " # Attempt to parse the input string into a datetime\n", + " return datetime.fromisoformat(input_datetime)\n", + " except ValueError:\n", + " raise ValueError(\"Invalid datetime string format: {}\".format(input_datetime))\n", + "\n", + " raise ValueError(\"Input must be a datetime object or string\")\n", + "\n", + " def __init__(self, start_date: Optional[Union[datetime, str]] = None, end_date: Optional[Union[datetime, str]] = None, time_delta: Optional[timedelta] = None, start_inclusive=True, end_inclusive=False):\n", " \"\"\"\n", " A UUIDTimeRange is a time range predicate on the UUID Version 1 timestamps. \n", " \n", " Note that naive datetime objects are interpreted as local time on the python client side and converted to UTC before being sent to the database.\n", " \"\"\"\n", + " start_date = UUIDTimeRange._parse_datetime(start_date)\n", + " end_date = UUIDTimeRange._parse_datetime(end_date)\n", + "\n", " if start_date is not None and end_date is not None:\n", " if start_date > end_date:\n", " raise Exception(\"start_date must be before end_date\")\n", @@ -726,36 +760,6 @@ " raise ValueError(\"Unknown filter type: {filter_type}\".format(filter_type=type(filter)))\n", "\n", " return (where, params)\n", - " \n", - " def _parse_datetime(self, input_datetime):\n", - " \"\"\"\n", - " Parse a datetime object or string representation of a datetime.\n", - "\n", - " Args:\n", - " input_datetime (datetime or str): Input datetime or string.\n", - "\n", - " Returns:\n", - " datetime: Parsed datetime object.\n", - "\n", - " Raises:\n", - " ValueError: If the input cannot be parsed as a datetime.\n", - " \"\"\"\n", - " if input_datetime is None:\n", - " return None\n", - " \n", - " if isinstance(input_datetime, datetime):\n", - " # If input is already a datetime object, return it as is\n", - " return input_datetime\n", - "\n", - " if isinstance(input_datetime, str):\n", - " try:\n", - " # Attempt to parse the input string into a datetime\n", - " return datetime.fromisoformat(input_datetime)\n", - " except ValueError:\n", - " raise ValueError(\"Invalid datetime string format\")\n", - "\n", - " raise ValueError(\"Input must be a datetime object or string\")\n", - "\n", "\n", " def search_query(\n", " self, \n", @@ -785,8 +789,8 @@ " if self.infer_filters:\n", " if uuid_time_filter is None and isinstance(filter, dict):\n", " if \"__start_date\" in filter or \"__end_date\" in filter:\n", - " start_date = self._parse_datetime(filter.get(\"__start_date\"))\n", - " end_date = self._parse_datetime(filter.get(\"__end_date\"))\n", + " start_date = UUIDTimeRange._parse_datetime(filter.get(\"__start_date\"))\n", + " end_date = UUIDTimeRange._parse_datetime(filter.get(\"__end_date\"))\n", " \n", " uuid_time_filter = UUIDTimeRange(start_date, end_date)\n", " \n", @@ -1506,6 +1510,8 @@ " #using uuid_time_filter\n", " rec = await vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(start_date, end_date))\n", " assert len(rec) == expected\n", + " rec = await vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(str(start_date), str(end_date)))\n", + " assert len(rec) == expected\n", " \n", " #using filters\n", " filter = {}\n", @@ -2248,6 +2254,8 @@ " #using uuid_time_filter\n", " rec = vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(start_date, end_date))\n", " assert len(rec) == expected\n", + " rec = vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(str(start_date), str(end_date)))\n", + " assert len(rec) == expected\n", " \n", " #using filters\n", " filter = {}\n", diff --git a/timescale_vector/_modidx.py b/timescale_vector/_modidx.py index 63b0a4c..deab21f 100644 --- a/timescale_vector/_modidx.py +++ b/timescale_vector/_modidx.py @@ -78,8 +78,6 @@ 'timescale_vector/client.py'), 'timescale_vector.client.QueryBuilder._get_embedding_index_name': ( 'vector.html#querybuilder._get_embedding_index_name', 'timescale_vector/client.py'), - 'timescale_vector.client.QueryBuilder._parse_datetime': ( 'vector.html#querybuilder._parse_datetime', - 'timescale_vector/client.py'), 'timescale_vector.client.QueryBuilder._quote_ident': ( 'vector.html#querybuilder._quote_ident', 'timescale_vector/client.py'), 'timescale_vector.client.QueryBuilder._where_clause_for_filter': ( 'vector.html#querybuilder._where_clause_for_filter', @@ -153,6 +151,8 @@ 'timescale_vector/client.py'), 'timescale_vector.client.UUIDTimeRange.__str__': ( 'vector.html#uuidtimerange.__str__', 'timescale_vector/client.py'), + 'timescale_vector.client.UUIDTimeRange._parse_datetime': ( 'vector.html#uuidtimerange._parse_datetime', + 'timescale_vector/client.py'), 'timescale_vector.client.UUIDTimeRange.build_query': ( 'vector.html#uuidtimerange.build_query', 'timescale_vector/client.py'), 'timescale_vector.client.uuid_from_time': ( 'vector.html#uuid_from_time', diff --git a/timescale_vector/client.py b/timescale_vector/client.py index ff0c32f..eac89fc 100644 --- a/timescale_vector/client.py +++ b/timescale_vector/client.py @@ -200,12 +200,46 @@ def create_index_query(self, table_name_quoted:str, column_name_quoted: str, ind # %% ../nbs/00_vector.ipynb 11 class UUIDTimeRange: - def __init__(self, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None, time_delta: Optional[timedelta] = None, start_inclusive=True, end_inclusive=False): + + @staticmethod + def _parse_datetime(input_datetime: Union[datetime, str]): + """ + Parse a datetime object or string representation of a datetime. + + Args: + input_datetime (datetime or str): Input datetime or string. + + Returns: + datetime: Parsed datetime object. + + Raises: + ValueError: If the input cannot be parsed as a datetime. + """ + if input_datetime is None or input_datetime == "None": + return None + + if isinstance(input_datetime, datetime): + # If input is already a datetime object, return it as is + return input_datetime + + if isinstance(input_datetime, str): + try: + # Attempt to parse the input string into a datetime + return datetime.fromisoformat(input_datetime) + except ValueError: + raise ValueError("Invalid datetime string format: {}".format(input_datetime)) + + raise ValueError("Input must be a datetime object or string") + + def __init__(self, start_date: Optional[Union[datetime, str]] = None, end_date: Optional[Union[datetime, str]] = None, time_delta: Optional[timedelta] = None, start_inclusive=True, end_inclusive=False): """ A UUIDTimeRange is a time range predicate on the UUID Version 1 timestamps. Note that naive datetime objects are interpreted as local time on the python client side and converted to UTC before being sent to the database. """ + start_date = UUIDTimeRange._parse_datetime(start_date) + end_date = UUIDTimeRange._parse_datetime(end_date) + if start_date is not None and end_date is not None: if start_date > end_date: raise Exception("start_date must be before end_date") @@ -615,36 +649,6 @@ def _where_clause_for_filter(self, params: List, filter: Optional[Union[Dict[str raise ValueError("Unknown filter type: {filter_type}".format(filter_type=type(filter))) return (where, params) - - def _parse_datetime(self, input_datetime): - """ - Parse a datetime object or string representation of a datetime. - - Args: - input_datetime (datetime or str): Input datetime or string. - - Returns: - datetime: Parsed datetime object. - - Raises: - ValueError: If the input cannot be parsed as a datetime. - """ - if input_datetime is None: - return None - - if isinstance(input_datetime, datetime): - # If input is already a datetime object, return it as is - return input_datetime - - if isinstance(input_datetime, str): - try: - # Attempt to parse the input string into a datetime - return datetime.fromisoformat(input_datetime) - except ValueError: - raise ValueError("Invalid datetime string format") - - raise ValueError("Input must be a datetime object or string") - def search_query( self, @@ -674,8 +678,8 @@ def search_query( if self.infer_filters: if uuid_time_filter is None and isinstance(filter, dict): if "__start_date" in filter or "__end_date" in filter: - start_date = self._parse_datetime(filter.get("__start_date")) - end_date = self._parse_datetime(filter.get("__end_date")) + start_date = UUIDTimeRange._parse_datetime(filter.get("__start_date")) + end_date = UUIDTimeRange._parse_datetime(filter.get("__end_date")) uuid_time_filter = UUIDTimeRange(start_date, end_date)