diff --git a/CHANGELOG.md b/CHANGELOG.md
index 295aed9..bd49b6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,7 @@
# Changelog
+## v1.6.3 1/27/25
+- Add capability to pull cloudLibrary events by the millisecond
+
## v1.6.2 12/2/24
- Add record_num capability to patron_data_helper
diff --git a/src/nypl_py_utils/classes/cloudlibrary_client.py b/src/nypl_py_utils/classes/cloudlibrary_client.py
index 1c8c191..f871d0e 100644
--- a/src/nypl_py_utils/classes/cloudlibrary_client.py
+++ b/src/nypl_py_utils/classes/cloudlibrary_client.py
@@ -3,7 +3,7 @@
import hmac
import requests
-from datetime import datetime, timedelta, timezone
+from datetime import datetime, timezone
from nypl_py_utils.functions.log_helper import create_log
from requests.adapters import HTTPAdapter, Retry
@@ -35,28 +35,25 @@ def get_library_events(self, start_date=None,
optional timeframe. Pulls past 24 hours of events by default.
start_date and end_date are optional parameters, and must be
- formatted either YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS
+ formatted either YYYY-MM-DD, YYYY-MM-DD:SS, or YYYY-MM-DDTHH:MM:SS.fff
"""
- date_format = "%Y-%m-%dT%H:%M:%S"
- today = datetime.now(timezone.utc)
- yesterday = today - timedelta(1)
- start_date = datetime.strftime(
- yesterday, date_format) if start_date is None else start_date
- end_date = datetime.strftime(
- today, date_format) if end_date is None else end_date
-
- if (datetime.strptime(start_date, date_format) >
- datetime.strptime(end_date, date_format)):
- error_message = (f"Start date {start_date} greater than end date "
- f"{end_date}, cannot retrieve library events")
- self.logger.error(error_message)
- raise CloudLibraryClientError(error_message)
-
- self.logger.info(
- (f"Fetching all library events in "
- f"time frame {start_date} to {end_date}..."))
+ path = "data/cloudevents"
+ if None not in (start_date, end_date):
+ if (self._parse_event_date(start_date) >
+ self._parse_event_date(end_date)):
+ error_message = (f"Start date {start_date} greater than end "
+ f"date {end_date}, cannot retrieve library "
+ f"events")
+ self.logger.error(error_message)
+ raise CloudLibraryClientError(error_message)
+
+ path += f"?startdate={start_date}&enddate={end_date}"
+ self.logger.info(f"Fetching all library events in "
+ f"time frame {start_date} to {end_date}...")
+ else:
+ self.logger.info("Fetching all library events "
+ "from the past day...")
- path = f"data/cloudevents?startdate={start_date}&enddate={end_date}"
response = self.request(path=path, method_type="GET")
return response
@@ -66,7 +63,7 @@ def create_request_body(self, request_type,
Helper function to generate request body when performing item
and/or patron-specific functions (ex. checking out a title).
"""
- request_template = "<%(request_type)s>%(item_id)s%(patron_id)s%(request_type)s>" # noqa
+ request_template = "<%(request_type)s>%(item_id)s%(patron_id)s%(request_type)s>" # noqa
return request_template % {
"request_type": request_type,
"item_id": item_id,
@@ -113,6 +110,16 @@ def request(self, path, method_type="GET",
return response
+ def _parse_event_date(self, event_date) -> datetime:
+ for fmt in ("%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S.%f"):
+ try:
+ return datetime.strptime(event_date, fmt)
+ except ValueError:
+ pass
+ error_message = (f"Invalid date format found: {event_date}")
+ self.logger.error(error_message)
+ raise CloudLibraryClientError(error_message)
+
def _build_headers(self, method_type, path) -> dict:
time, authorization = self._build_authorization(
method_type, path)
diff --git a/tests/test_cloudlibrary_client.py b/tests/test_cloudlibrary_client.py
index 84866f9..188c6ef 100644
--- a/tests/test_cloudlibrary_client.py
+++ b/tests/test_cloudlibrary_client.py
@@ -41,17 +41,13 @@ def test_instance(self):
def test_get_library_events_success_no_args(
self, test_instance, mocker):
- start = "2024-11-10T10:00:00"
- end = "2024-11-11T10:00:00"
mock_request = mocker.patch(
- "nypl_py_utils.classes.cloudlibrary_client.CloudLibraryClient.request", # noqa
- return_value=_TEST_LIBRARY_EVENTS_RESPONSE)
- response = test_instance.get_library_events()
+ "nypl_py_utils.classes.cloudlibrary_client.CloudLibraryClient.request") # noqa
+ test_instance.get_library_events()
mock_request.assert_called_once_with(
- path=f"data/cloudevents?startdate={start}&enddate={end}",
+ path="data/cloudevents",
method_type="GET")
- assert response == _TEST_LIBRARY_EVENTS_RESPONSE
def test_get_library_events_success_with_start_and_end_date(
self, test_instance, mocker):
@@ -67,20 +63,6 @@ def test_get_library_events_success_with_start_and_end_date(
method_type="GET")
assert response == _TEST_LIBRARY_EVENTS_RESPONSE
- def test_get_library_events_success_with_no_end_date(
- self, test_instance, mocker):
- start = "2024-11-01T09:00:00"
- end = "2024-11-11T10:00:00"
- mock_request = mocker.patch(
- "nypl_py_utils.classes.cloudlibrary_client.CloudLibraryClient.request", # noqa
- return_value=_TEST_LIBRARY_EVENTS_RESPONSE)
- response = test_instance.get_library_events(start)
-
- mock_request.assert_called_once_with(
- path=f"data/cloudevents?startdate={start}&enddate={end}",
- method_type="GET")
- assert response == _TEST_LIBRARY_EVENTS_RESPONSE
-
def test_get_library_events_exception_when_start_date_greater_than_end(
self, test_instance, caplog):
start = "2024-11-11T09:00:00"
@@ -103,7 +85,7 @@ def test_get_library_events_exception_when_connection_timeout(
url, exc=ConnectTimeout)
with pytest.raises(CloudLibraryClientError):
- test_instance.get_library_events()
+ test_instance.get_library_events(start, end)
assert (f"Failed to retrieve response from {url}") in caplog.text
def test_get_request_success(self, test_instance, requests_mock):