From 9c38f3a09d78b31e59a0f8b0b0fad074dcdba2b9 Mon Sep 17 00:00:00 2001 From: Alexandra Hobbs <ahobbs@contractor.usgs.gov> Date: Mon, 2 Dec 2024 09:43:46 -0700 Subject: [PATCH] merging in master --- geomagio/metadata/Metadata.py | 9 +- test/db/MetadataDatabaseFactory_test.py | 143 ++++++++++++++++++++---- 2 files changed, 131 insertions(+), 21 deletions(-) diff --git a/geomagio/metadata/Metadata.py b/geomagio/metadata/Metadata.py index e1a7bd18..bfc6a35f 100644 --- a/geomagio/metadata/Metadata.py +++ b/geomagio/metadata/Metadata.py @@ -2,7 +2,7 @@ from datetime import timezone from typing import Dict, Optional from obspy import UTCDateTime -from pydantic import field_serializer, BaseModel, Field +from pydantic import field_validator, field_serializer, BaseModel, Field from .MetadataCategory import MetadataCategory from ..pydantic_utcdatetime import CustomUTCDateTimeType @@ -52,7 +52,7 @@ class Metadata(BaseModel): metadata_id: Optional[int] = None # author created_by: Optional[str] = None - created_time: CustomUTCDateTimeType = Field(default_factory=lambda: UTCDateTime()) + created_time: Optional[CustomUTCDateTimeType] = None # editor updated_by: Optional[str] = None updated_time: Optional[CustomUTCDateTimeType] = None @@ -104,3 +104,8 @@ class Metadata(BaseModel): if endtime is not None: endtime = endtime.datetime.replace(tzinfo=timezone.utc) return endtime + + @field_validator("created_time") + @classmethod + def set_default_created_time(cls, created_time: UTCDateTime = None) -> UTCDateTime: + return created_time or UTCDateTime() diff --git a/test/db/MetadataDatabaseFactory_test.py b/test/db/MetadataDatabaseFactory_test.py index 045d297e..61e60299 100644 --- a/test/db/MetadataDatabaseFactory_test.py +++ b/test/db/MetadataDatabaseFactory_test.py @@ -7,16 +7,14 @@ from databases import Database from obspy import UTCDateTime from geomagio.api.db import MetadataDatabaseFactory -from geomagio.metadata import Metadata, MetadataCategory +from geomagio.metadata import Metadata, MetadataCategory, MetadataQuery class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase): @patch("databases.Database.execute", new_callable=AsyncMock) async def test_create_metadata_defaults(self, mock_execute): - now = UTCDateTime() test_data = Metadata( - created_time=now, category=MetadataCategory.INSTRUMENT, created_by="test_metadata.py", network="NT", @@ -50,16 +48,6 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase): # assert data_valid, priority, and status are set to the correct defaults expected_values = { - "created_time": datetime.datetime( - year=now.year, - month=now.month, - day=now.day, - hour=now.hour, - minute=now.minute, - second=now.second, - microsecond=now.microsecond, - tzinfo=tz.tzutc(), - ), "category": "instrument", "created_by": "test_metadata.py", "network": "NT", @@ -96,12 +84,10 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase): assert called_params == expected_values @patch("databases.Database.execute", new_callable=AsyncMock) - async def test_create_metadata_with_ids(self, mock_execute): + async def test_create_metadata_created_time(self, mock_execute): now = UTCDateTime() test_data = Metadata( - id=1234, created_time=now, - metadata_id=5678, category=MetadataCategory.INSTRUMENT, created_by="test_metadata.py", network="NT", @@ -133,7 +119,7 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase): await MetadataDatabaseFactory(database=db).create_metadata(test_data) - # assert id and metadata_id are removed + # assert data_valid, priority, and status are set to the correct defaults expected_values = { "created_time": datetime.datetime( year=now.year, @@ -181,8 +167,9 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase): assert called_params == expected_values @patch("databases.Database.execute", new_callable=AsyncMock) - async def test_create_metadata_without_created_time(self, mock_execute): + async def test_create_metadata_with_ids(self, mock_execute): test_data = Metadata( + id=1234, metadata_id=5678, category=MetadataCategory.INSTRUMENT, created_by="test_metadata.py", @@ -215,10 +202,42 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase): await MetadataDatabaseFactory(database=db).create_metadata(test_data) + # assert id and metadata_id are removed + expected_values = { + "category": "instrument", + "created_by": "test_metadata.py", + "network": "NT", + "station": "BDT", + "metadata": { + "type": "FGE", + "channels": { + "U": [{"channel": "U_Volt", "offset": 0, "scale": 313.2}], + "V": [{"channel": "V_Volt", "offset": 0, "scale": 312.3}], + "W": [{"channel": "W_Volt", "offset": 0, "scale": 312.0}], + }, + "electronics": { + "serial": "E0542", + "x-scale": 313.2, + "y-scale": 312.3, + "z-scale": 312.0, + "temperature-scale": 0.01, + }, + "sensor": { + "serial": "S0419", + "x-constant": 36958, + "y-constant": 36849, + "z-constant": 36811, + }, + }, + "data_valid": True, + "priority": 1, + "status": "new", + } + mock_execute.assert_called_once() called_params = mock_execute.call_args.args[0].compile().params - assert called_params["created_time"] is not None + assert called_params == expected_values @patch("databases.Database.execute", new_callable=AsyncMock) async def test_create_metadata_with_starttime_and_endtime(self, mock_execute): @@ -336,3 +355,89 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase): microsecond=e.microsecond, tzinfo=tz.tzutc(), ) + + @patch("databases.Database.execute", new_callable=AsyncMock) + @patch("databases.Database.fetch_all", new_callable=AsyncMock) + async def test_update_metadata_defaults(self, mock_fetch_all, mock_execute): + test_data = Metadata( + category=MetadataCategory.INSTRUMENT, + network="NT", + station="BDT", + metadata={ + "type": "FGE", + "channels": { + "U": [{"channel": "U_Volt", "offset": 0, "scale": 313.2}], + "V": [{"channel": "V_Volt", "offset": 0, "scale": 312.3}], + "W": [{"channel": "W_Volt", "offset": 0, "scale": 312.0}], + }, + "electronics": { + "serial": "E0542", + "x-scale": 313.2, + "y-scale": 312.3, + "z-scale": 312.0, + "temperature-scale": 0.01, + }, + "sensor": { + "serial": "S0419", + "x-constant": 36958, + "y-constant": 36849, + "z-constant": 36811, + }, + }, + ) + + db = Database("sqlite:///:memory:") + yesterday = datetime.datetime(2024, 11, 1, 8, 15, tzinfo=tz.tzutc()) + + mock_fetch_all.return_value = ( + { + "id": 1234, + "created_time": yesterday, + "category": "instrument", + "network": "NT", + "station": "BDT", + "metadata": { + "foo": "bar", + }, + }, + ) + + await MetadataDatabaseFactory(database=db).update_metadata( + meta=test_data, updated_by="test_user" + ) + + assert mock_fetch_all.call_count == 2 + assert mock_execute.call_count == 2 + + first_called_params = mock_execute.call_args_list[0].args[0].compile().params + second_called_params = mock_execute.call_args_list[1].args[0].compile().params + + assert first_called_params["metadata_id"] == 1234 + assert first_called_params["created_time"] == yesterday + assert first_called_params["metadata"] == {"foo": "bar"} + + assert second_called_params["updated_by"] == "test_user" + assert second_called_params["updated_time"] is not None + assert second_called_params["metadata"] == test_data.metadata + + @patch("databases.Database.fetch_all", new_callable=AsyncMock) + async def test_get_metadata(self, mock_fetch_all): + test_query = MetadataQuery( + category=MetadataCategory.INSTRUMENT, + station="BSL", + starttime=UTCDateTime(2020, 1, 20), + ) + + db = Database("sqlite:///:memory:") + + await MetadataDatabaseFactory(database=db).get_metadata(params=test_query) + + mock_fetch_all.assert_called_once() + + called_params = mock_fetch_all.call_args.args[0].compile().params + + assert called_params["category_1"] == "instrument" + assert called_params["station_1"] == "BSL" + assert called_params["endtime_1"] == datetime.datetime( + 2020, 1, 20, tzinfo=tz.tzutc() + ) -- GitLab