Repository: stac-utils/stac-fastapi
Pull request #692 (2024-05-14 20:45:34)

Make `str_to_interval` not return a tuple for single-value input
**Related Issue(s):**

- #688

**Description:**

This is a simple fix to return a single `datetime` object if a single value (rather than an interval) is given. This is already supported by `DateTimeType`:

https://github.com/stac-utils/stac-fastapi/blob/f815c236cde78a3442fe18c103f43ee1db668872/stac_fastapi/types/stac_fastapi/types/rfc3339.py#L15-L20

It then also changes the value of the `datetime` argument [as passed to `get_search`](https://github.com/stac-utils/stac-fastapi/blob/f815c236cde78a3442fe18c103f43ee1db668872/stac_fastapi/types/stac_fastapi/types/core.py#L454-L464), which so far is passed a `tuple` for a single-value datetime as well.

**PR Checklist:**

- [x] `pre-commit` hooks pass locally
- [x] Tests pass (run `make test`)
- [x] Documentation has been updated to reflect changes, if applicable, and docs build successfully (run `make docs`)
- [x] Changes are added to the [CHANGELOG](https://github.com/stac-utils/stac-fastapi/blob/main/CHANGES.md).


vincentsarago: I wrongly `fixed` this in #697, your PR is a better fix 🙏 

Patch:
diff --git a/CHANGES.md b/CHANGES.md
index abff6ce..d26773a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -7,12 +7,15 @@
 * Switch from `fastapi` to `fastapi-slim` to avoid installing unwanted dependencies. ([#687](https://github.com/stac-utils/stac-fastapi/pull/687))
 * Replace Enum with `Literal` for `FilterLang`. ([#686](https://github.com/stac-utils/stac-fastapi/pull/686))
 * Update stac-pydantic requirement to `~3.1` ([#697](https://github.com/stac-utils/stac-fastapi/pull/697))
-* Fix datetime interval for GET Search when passing a single value ([#697](https://github.com/stac-utils/stac-fastapi/pull/697))
 
 ### Removed
 
 * Pystac as it was just used for a datetime to string function. ([#690](https://github.com/stac-utils/stac-fastapi/pull/690))
 
+### Fixed
+
+* Make `str_to_interval` not return a tuple for single-value input (fixing `datetime` argument as passed to `get_search`). ([#692](https://github.com/stac-utils/stac-fastapi/pull/692))
+
 ## [3.0.0a0] - 2024-05-06
 
 ### Added
diff --git a/stac_fastapi/types/stac_fastapi/types/rfc3339.py b/stac_fastapi/types/stac_fastapi/types/rfc3339.py
index 5ba0630..77ec993 100644
--- a/stac_fastapi/types/stac_fastapi/types/rfc3339.py
+++ b/stac_fastapi/types/stac_fastapi/types/rfc3339.py
@@ -94,16 +94,17 @@ def parse_single_date(date_str: str) -> datetime:
 
 def str_to_interval(interval: Optional[str]) -> Optional[DateTimeType]:
     """
-    Extract a tuple of datetime objects from an interval string defined by the OGC API.
-    The interval can either be a single datetime or a range with start and end datetime.
+    Extract a single datetime object or a tuple of datetime objects from an
+    interval string defined by the OGC API. The interval can either be a
+    single datetime or a range with start and end datetime.
 
     Args:
         interval (Optional[str]): The interval string to convert to datetime objects,
         or None if no datetime is specified.
 
     Returns:
-        Optional[DateTimeType]: A tuple of datetime.datetime objects or
-        None if input is None.
+        Optional[DateTimeType]: A single datetime.datetime object, a tuple of
+        datetime.datetime objects, or None if input is None.
 
     Raises:
         HTTPException: If the string is not valid for various reasons such as being empty,
@@ -122,11 +123,11 @@ def str_to_interval(interval: Optional[str]) -> Optional[DateTimeType]:
             detail="Interval string contains more than one forward slash.",
         )
 
-    if len(values) == 1:
-        values = [values[0], values[0]]
-
     try:
         start = parse_single_date(values[0]) if values[0] not in ["..", ""] else None
+        if len(values) == 1:
+            return start
+
         end = (
             parse_single_date(values[1])
             if len(values) > 1 and values[1] not in ["..", ""]


Test patch:
diff --git a/stac_fastapi/types/tests/test_rfc3339.py b/stac_fastapi/types/tests/test_rfc3339.py
index 8d83dbb..dc4c897 100644
--- a/stac_fastapi/types/tests/test_rfc3339.py
+++ b/stac_fastapi/types/tests/test_rfc3339.py
@@ -1,4 +1,4 @@
-from datetime import timezone
+from datetime import datetime, timezone
 
 import pytest
 from fastapi import HTTPException
@@ -86,27 +86,35 @@ def test_parse_valid_str_to_datetime(test_input):
 
 
 @pytest.mark.parametrize("test_input", invalid_intervals)
-def test_parse_invalid_interval_to_datetime(test_input):
+def test_str_to_interval_with_invalid_interval(test_input):
     with pytest.raises(HTTPException) as exc_info:
         str_to_interval(test_input)
     assert (
         exc_info.value.status_code == 400
-    ), "Should return a 400 status code for invalid intervals"
+    ), "str_to_interval should return a 400 status code for invalid interval"
 
 
-@pytest.mark.parametrize("test_input", valid_intervals)
-def test_parse_valid_interval_to_datetime(test_input):
-    assert str_to_interval(test_input)
+@pytest.mark.parametrize("test_input", invalid_datetimes)
+def test_str_to_interval_with_invalid_datetime(test_input):
+    with pytest.raises(HTTPException) as exc_info:
+        str_to_interval(test_input)
+    assert (
+        exc_info.value.status_code == 400
+    ), "str_to_interval should return a 400 status code for invalid datetime"
 
 
-def test_now_functions() -> None:
-    now1 = now_in_utc()
-    now2 = now_in_utc()
+@pytest.mark.parametrize("test_input", valid_intervals)
+def test_str_to_interval_with_valid_interval(test_input):
+    assert isinstance(
+        str_to_interval(test_input), tuple
+    ), "str_to_interval should return tuple for multi-value input"
 
-    assert now1 < now2
-    assert now1.tzinfo == timezone.utc
 
-    rfc3339_str_to_datetime(now_to_rfc3339_str())
+@pytest.mark.parametrize("test_input", valid_datetimes)
+def test_str_to_interval_with_valid_datetime(test_input):
+    assert isinstance(
+        str_to_interval(test_input), datetime
+    ), "str_to_interval should return single datetime for single-value input"
 
 
 def test_str_to_interval_with_none():
@@ -114,3 +122,13 @@ def test_str_to_interval_with_none():
     assert (
         str_to_interval(None) is None
     ), "str_to_interval should return None when input is None"
+
+
+def test_now_functions() -> None:
+    now1 = now_in_utc()
+    now2 = now_in_utc()
+
+    assert now1 < now2
+    assert now1.tzinfo == timezone.utc
+
+    rfc3339_str_to_datetime(now_to_rfc3339_str())