Skip to content

Commit

Permalink
Merge pull request #23 from Healy-Hyperspatial/core-3.0.0a0
Browse files Browse the repository at this point in the history
Update stac-fastapi.core to v3.0.0a0
  • Loading branch information
jonhealy1 authored May 18, 2024
2 parents 14e51ac + f42767a commit ac940c2
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 170 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0/

## [Unreleased]

### Changed

- Updated sfeos core to v3.0.0a0, fixed datetime functionality. [#23](https://github.com/Healy-Hyperspatial/stac-fastapi-mongo/pull/23)


## [v3.2.1]

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
desc = f.read()

install_requires = [
"stac-fastapi.core==2.4.1",
"stac-fastapi.core==3.0.0a0",
"motor==3.3.2",
"pymongo==4.6.2",
"uvicorn",
"starlette",
"typing_extensions==4.4.0",
"typing_extensions==4.8.0",
]

extra_reqs = {
Expand Down
8 changes: 1 addition & 7 deletions stac_fastapi/mongo/database_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from stac_fastapi.mongo.config import AsyncMongoDBSettings as AsyncSearchSettings
from stac_fastapi.mongo.config import MongoDBSettings as SyncSearchSettings
from stac_fastapi.mongo.utilities import (
convert_obj_datetimes,
decode_token,
encode_token,
parse_datestring,
Expand Down Expand Up @@ -383,7 +382,6 @@ def translate_cql2_to_mongo(cql2_filter: Dict[str, Any]) -> Dict[str, Any]:
Returns:
A MongoDB query as a dictionary.
"""
print("CQL2 filter:", cql2_filter)
op_mapping = {
">": "$gt",
">=": "$gte",
Expand Down Expand Up @@ -505,7 +503,6 @@ def apply_cql2_filter(
mongo_query = DatabaseLogic.translate_cql2_to_mongo(_filter)
search_adapter.add_filter(mongo_query)

print("search adapter: ", search_adapter)
return search_adapter

@staticmethod
Expand Down Expand Up @@ -569,8 +566,6 @@ async def execute_search(

query = {"$and": search.filters} if search and search.filters else {}

print("Query: ", query)

if collection_ids:
query["collection"] = {"$in": collection_ids}

Expand Down Expand Up @@ -654,7 +649,6 @@ async def create_item(self, item: Item, refresh: bool = False):

new_item = item.copy()
new_item["_id"] = item.get("_id", ObjectId())
convert_obj_datetimes(new_item)

existing_item = await items_collection.find_one({"_id": new_item["_id"]})
if existing_item:
Expand Down Expand Up @@ -737,7 +731,7 @@ def sync_prep_create_item(

# Transform item using item_serializer for MongoDB compatibility
mongo_item = self.item_serializer.stac_to_db(item, base_url)
print("mongo item id: ", mongo_item["id"])

if not exist_ok:
existing_item = items_collection.find_one({"id": mongo_item["id"]})
if existing_item:
Expand Down
50 changes: 12 additions & 38 deletions stac_fastapi/mongo/utilities.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""utilities for stac-fastapi.mongo."""

from base64 import urlsafe_b64decode, urlsafe_b64encode
from datetime import timezone

from bson import ObjectId
from dateutil import parser # type: ignore
Expand Down Expand Up @@ -31,48 +32,21 @@ def encode_token(token_value: str) -> str:
return encoded_token


def parse_datestring(str):
"""Parse date string using dateutil.parser.parse() and returns a string formatted \
as ISO 8601 with milliseconds and 'Z' timezone indicator.
def parse_datestring(dt_str: str) -> str:
"""
Normalize various ISO 8601 datetime formats to a consistent format.
Args:
str (str): The date string to parse.
dt_str (str): The datetime string in ISO 8601 format.
Returns:
str: The parsed and formatted date string in the format 'YYYY-MM-DDTHH:MM:SS.ssssssZ'.
str: The normalized datetime string in the format "YYYY-MM-DDTHH:MM:SSZ".
"""
parsed_value = parser.parse(str)
return parsed_value.strftime("%Y-%m-%dT%H:%M:%S.%fZ")


def convert_obj_datetimes(obj):
"""Recursively explores dictionaries and lists, attempting to parse strings as datestrings \
into a specific format.
# Parse the datetime string to datetime object
dt = parser.isoparse(dt_str)

Args:
obj (dict or list): The dictionary or list containing date strings to convert.
# Convert the datetime to UTC and remove microseconds
dt = dt.astimezone(timezone.utc).replace(microsecond=0)

Returns:
dict or list: The converted dictionary or list with date strings in the desired format.
"""
if isinstance(obj, dict):
for key, value in obj.items():
if isinstance(value, dict) or isinstance(value, list):
obj[key] = convert_obj_datetimes(value)
elif isinstance(value, str):
try:
obj[key] = parse_datestring(value)
except ValueError:
pass # If parsing fails, retain the original value
elif value is None:
obj[key] = None # Handle null values
elif isinstance(obj, list):
for i, value in enumerate(obj):
if isinstance(value, str): # Only attempt to parse strings
try:
obj[i] = parse_datestring(value)
except ValueError:
pass # If parsing fails, retain the original value
elif isinstance(value, list):
obj[i] = convert_obj_datetimes(value) # Recursively handle nested lists
return obj
# Format the datetime to the specified format
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
52 changes: 21 additions & 31 deletions stac_fastapi/tests/api/test_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import copy
import uuid
from datetime import datetime, timedelta
from datetime import timedelta

import pytest

Expand Down Expand Up @@ -62,11 +61,11 @@ async def test_router(app):


@pytest.mark.asyncio
async def test_app_transaction_extension(app_client, ctx):
item = copy.deepcopy(ctx.item)
async def test_app_transaction_extension(app_client, ctx, load_test_data):
item = load_test_data("test_item.json")
item["id"] = str(uuid.uuid4())
resp = await app_client.post(f"/collections/{item['collection']}/items", json=item)
assert resp.status_code == 200
assert resp.status_code == 201

await app_client.delete(f"/collections/{item['collection']}/items/{item['id']}")

Expand Down Expand Up @@ -181,8 +180,10 @@ async def test_app_fields_extension_no_null_fields(app_client, ctx, txn_client):


@pytest.mark.asyncio
async def test_app_fields_extension_return_all_properties(app_client, ctx, txn_client):
item = ctx.item
async def test_app_fields_extension_return_all_properties(
app_client, ctx, txn_client, load_test_data
):
item = load_test_data("test_item.json")
resp = await app_client.get(
"/search", params={"collections": ["test-collection"], "fields": "properties"}
)
Expand Down Expand Up @@ -237,16 +238,14 @@ async def test_app_query_extension_limit_10000(app_client):
@pytest.mark.asyncio
async def test_app_sort_extension_get_asc(app_client, txn_client, ctx):
first_item = ctx.item
item_date = datetime.strptime(
first_item["properties"]["datetime"], "%Y-%m-%dT%H:%M:%S.%fZ"
)

second_item = dict(first_item)
second_item["id"] = "another-item"
another_item_date = item_date - timedelta(days=1)
second_item["properties"]["datetime"] = another_item_date.strftime(
"%Y-%m-%dT%H:%M:%S.%fZ"
another_item_date = first_item["properties"]["datetime"] - timedelta(days=1)
second_item["properties"]["datetime"] = another_item_date.isoformat().replace(
"+00:00", "Z"
)

await create_item(txn_client, second_item)

resp = await app_client.get("/search?sortby=+properties.datetime")
Expand All @@ -259,15 +258,12 @@ async def test_app_sort_extension_get_asc(app_client, txn_client, ctx):
@pytest.mark.asyncio
async def test_app_sort_extension_get_desc(app_client, txn_client, ctx):
first_item = ctx.item
item_date = datetime.strptime(
first_item["properties"]["datetime"], "%Y-%m-%dT%H:%M:%S.%fZ"
)

second_item = dict(first_item)
second_item["id"] = "another-item"
another_item_date = item_date - timedelta(days=1)
second_item["properties"]["datetime"] = another_item_date.strftime(
"%Y-%m-%dT%H:%M:%S.%fZ"
another_item_date = first_item["properties"]["datetime"] - timedelta(days=1)
second_item["properties"]["datetime"] = another_item_date.isoformat().replace(
"+00:00", "Z"
)
await create_item(txn_client, second_item)

Expand All @@ -281,15 +277,12 @@ async def test_app_sort_extension_get_desc(app_client, txn_client, ctx):
@pytest.mark.asyncio
async def test_app_sort_extension_post_asc(app_client, txn_client, ctx):
first_item = ctx.item
item_date = datetime.strptime(
first_item["properties"]["datetime"], "%Y-%m-%dT%H:%M:%S.%fZ"
)

second_item = dict(first_item)
second_item["id"] = "another-item"
another_item_date = item_date - timedelta(days=1)
second_item["properties"]["datetime"] = another_item_date.strftime(
"%Y-%m-%dT%H:%M:%S.%fZ"
another_item_date = first_item["properties"]["datetime"] - timedelta(days=1)
second_item["properties"]["datetime"] = another_item_date.isoformat().replace(
"+00:00", "Z"
)
await create_item(txn_client, second_item)

Expand All @@ -307,15 +300,12 @@ async def test_app_sort_extension_post_asc(app_client, txn_client, ctx):
@pytest.mark.asyncio
async def test_app_sort_extension_post_desc(app_client, txn_client, ctx):
first_item = ctx.item
item_date = datetime.strptime(
first_item["properties"]["datetime"], "%Y-%m-%dT%H:%M:%S.%fZ"
)

second_item = dict(first_item)
second_item["id"] = "another-item"
another_item_date = item_date - timedelta(days=1)
second_item["properties"]["datetime"] = another_item_date.strftime(
"%Y-%m-%dT%H:%M:%S.%fZ"
another_item_date = first_item["properties"]["datetime"] - timedelta(days=1)
second_item["properties"]["datetime"] = another_item_date.isoformat().replace(
"+00:00", "Z"
)
await create_item(txn_client, second_item)

Expand Down
Loading

0 comments on commit ac940c2

Please sign in to comment.