Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DE-175: Create general cloudLibrary client #38

Merged
merged 11 commits into from
Nov 20, 2024
Merged
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Changelog
## v1.5.0 11/4/24
## v1.5.0 11/19/24
- Added cloudLibrary client

## v1.4.0 9/23/24
Expand Down
4 changes: 2 additions & 2 deletions README.md
fatimarahman marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ kinesis_client = KinesisClient(...)
# Do not use any version below 1.0.0
# All available optional dependencies can be found in pyproject.toml.
# See the "Managing dependencies" section below for more details.
nypl-py-utils[kinesis-client,config-helper]==1.4.0
nypl-py-utils[kinesis-client,config-helper]==1.5.0
```

## Developing locally
Expand All @@ -64,7 +64,7 @@ The optional dependency sets also give the developer the option to manually list
### Using PostgreSQLClient in an AWS Lambda
Because `psycopg` requires a statically linked version of the `libpq` library, the `PostgreSQLClient` cannot be installed as-is in an AWS Lambda function. Instead, it must be packaged as follows:
```bash
pip install --target ./package nypl-py-utils[postgresql-client]==1.4.0
pip install --target ./package nypl-py-utils[postgresql-client]==1.5.0

pip install \
--platform manylinux2014_x86_64 \
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
fatimarahman marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "nypl_py_utils"
version = "1.4.0"
version = "1.5.0"
authors = [
{ name="Aaron Friedman", email="[email protected]" },
]
Expand Down
5 changes: 4 additions & 1 deletion src/nypl_py_utils/classes/cloudlibrary_client.py
fatimarahman marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def get_library_events(self, start_date=None,
end_date=None) -> requests.Response:
"""
Retrieves all the events related to library-owned items within the
optional timeframe. Pulls yesterday's events by default.
optional timeframe. Pulls past 24 hours of events by default.

start_date and end_date are optional parameters, and must be
fatimarahman marked this conversation as resolved.
Show resolved Hide resolved
formatted either YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS
Expand Down Expand Up @@ -79,6 +79,9 @@ def request(self, path, method_type="GET",
Use this method to call specific paths in the cloudLibrary API.
This method is necessary for building headers/authorization.
Example usage of this method is in the get_library_events function.

Returns Response object by default -- you will need to parse this
object to retrieve response text, status codes, etc.
"""
extended_path = f"/cirrus/library/{self.library_id}/{path}"
headers = self._build_headers(method_type, extended_path)
Expand Down
92 changes: 67 additions & 25 deletions tests/test_cloudlibrary_client.py
fatimarahman marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def test_instance(self):
"library_id", "account_id", "account_key")

def test_get_library_events_success_no_args(
self, test_instance, mocker, caplog):
self, test_instance, mocker):
start = "2024-11-10T10:00:00"
end = "2024-11-11T10:00:00"
mock_request = mocker.patch(
Expand All @@ -52,11 +52,9 @@ def test_get_library_events_success_no_args(
path=f"data/cloudevents?startdate={start}&enddate={end}",
method_type="GET")
assert response == _TEST_LIBRARY_EVENTS_RESPONSE
assert (f"Fetching all library events in time frame "
f"{start} to {end}...") in caplog.text

def test_get_library_events_success_with_start_and_end_date(
self, test_instance, mocker, caplog):
self, test_instance, mocker):
start = "2024-11-01T10:00:00"
end = "2024-11-05T10:00:00"
mock_request = mocker.patch(
Expand All @@ -68,11 +66,9 @@ def test_get_library_events_success_with_start_and_end_date(
path=f"data/cloudevents?startdate={start}&enddate={end}",
method_type="GET")
assert response == _TEST_LIBRARY_EVENTS_RESPONSE
assert (f"Fetching all library events in time frame "
f"{start} to {end}...") in caplog.text

def test_get_library_events_success_with_no_end_date(
self, test_instance, mocker, caplog):
self, test_instance, mocker):
start = "2024-11-01T09:00:00"
end = "2024-11-11T10:00:00"
mock_request = mocker.patch(
Expand All @@ -84,18 +80,14 @@ def test_get_library_events_success_with_no_end_date(
path=f"data/cloudevents?startdate={start}&enddate={end}",
method_type="GET")
assert response == _TEST_LIBRARY_EVENTS_RESPONSE
assert (f"Fetching all library events in time frame "
f"{start} to {end}...") in caplog.text

def test_get_library_events_exception_when_start_date_greater_than_end(
fatimarahman marked this conversation as resolved.
Show resolved Hide resolved
self, test_instance, caplog):
self, test_instance):
start = "2024-11-11T09:00:00"
end = "2024-11-01T10:00:00"

with pytest.raises(CloudLibraryClientError):
test_instance.get_library_events(start, end)
assert (f"Start date {start} greater than end date "
f"{end}, cannot retrieve library events") in caplog.text

def test_get_library_events_exception_when_connection_timeout(
self, test_instance, requests_mock):
Expand All @@ -111,25 +103,75 @@ def test_get_library_events_exception_when_connection_timeout(
with pytest.raises(CloudLibraryClientError):
test_instance.get_library_events()

def test_request_success(self, test_instance, mocker):
def test_get_request_success(self, test_instance, requests_mock):
start = "2024-11-10T10:00:00"
end = "2024-11-11T10:00:00"
expected_headers = {'3mcl-Datetime': 'Mon, 11 Nov 2024 10:00:00 GMT',
'3mcl-Authorization': '3MCLAUTH account_id:KipNmbVsmsT2xPjP4oHAaR3n00JgcszfF6mQRffBoRk=', # noqa
'3mcl-APIVersion': '3.0.2',
'Accept': 'application/xml'}
mock_get = mocker.patch("requests.sessions.Session.get")
test_instance.request(
url = f"{_API_URL}{test_instance.library_id}/data/cloudevents?startdate={start}&enddate={end}" # noqa
expected_headers = {"3mcl-Datetime": "Mon, 11 Nov 2024 10:00:00 GMT",
"3mcl-Authorization": "3MCLAUTH account_id:KipNmbVsmsT2xPjP4oHAaR3n00JgcszfF6mQRffBoRk=", # noqa
"3mcl-APIVersion": "3.0.2",
"Accept": "application/xml"}
requests_mock.get(
url=url, text=_TEST_LIBRARY_EVENTS_RESPONSE)

response = test_instance.request(
path=f"data/cloudevents?startdate={start}&enddate={end}",
method_type="GET")

mock_get.assert_called_once_with(
url=f"{_API_URL}library_id/data/cloudevents?startdate={start}&enddate={end}", # noqa
data=None,
headers=expected_headers,
timeout=60)
assert response.text == _TEST_LIBRARY_EVENTS_RESPONSE
assert requests_mock.request_history[0].method == "GET"
assert requests_mock.request_history[0].url == url
assert requests_mock.request_history[0].body is None
assert expected_headers.items() <= dict(
requests_mock.request_history[0].headers).items()

def test_put_request_success(self, test_instance, requests_mock):
start = "2024-11-10T10:00:00"
end = "2024-11-11T10:00:00"
url = f"{_API_URL}{test_instance.library_id}/data/cloudevents?startdate={start}&enddate={end}" # noqa
expected_headers = {"3mcl-Datetime": "Mon, 11 Nov 2024 10:00:00 GMT",
"3mcl-Authorization": "3MCLAUTH account_id:3M773C6ZVWmB/ISoSjQy9iBp48T4tUWhoNOwXaseMtE=", # noqa
"3mcl-APIVersion": "3.0.2",
"Content-Type": "application/xml"}
requests_mock.put(
url=url, text=_TEST_LIBRARY_EVENTS_RESPONSE)

response = test_instance.request(
path=f"data/cloudevents?startdate={start}&enddate={end}",
method_type="PUT",
body={"test": "test"})

assert response.text == _TEST_LIBRARY_EVENTS_RESPONSE
assert requests_mock.request_history[0].method == "PUT"
assert requests_mock.request_history[0].url == url
assert requests_mock.request_history[0].body == "test=test"
assert expected_headers.items() <= dict(
requests_mock.request_history[0].headers).items()

def test_post_request_success(self, test_instance, requests_mock):
start = "2024-11-10T10:00:00"
end = "2024-11-11T10:00:00"
url = f"{_API_URL}{test_instance.library_id}/data/cloudevents?startdate={start}&enddate={end}" # noqa
expected_headers = {"3mcl-Datetime": "Mon, 11 Nov 2024 10:00:00 GMT",
"3mcl-Authorization": "3MCLAUTH account_id:vF0zI6ee1w1PbTLQ9EVvtxRly2vpCRxdBdAHb8DZQ4E=", # noqa
"3mcl-APIVersion": "3.0.2",
"Content-Type": "application/xml"}
requests_mock.post(
url=url, text=_TEST_LIBRARY_EVENTS_RESPONSE)

response = test_instance.request(
path=f"data/cloudevents?startdate={start}&enddate={end}",
method_type="POST",
body={"test": "test"})

assert response.text == _TEST_LIBRARY_EVENTS_RESPONSE
assert requests_mock.request_history[0].method == "POST"
assert requests_mock.request_history[0].url == url
assert requests_mock.request_history[0].body == "test=test"
assert expected_headers.items() <= dict(
requests_mock.request_history[0].headers).items()

def test_request_failure(self, test_instance, requests_mock, caplog):
def test_request_failure(self, test_instance, requests_mock):
start = "2024-11-10T10:00:00"
end = "2024-11-11T10:00:00"
requests_mock.get(
Expand Down