diff --git a/.flake8 b/.flake8 index 28639c5..ad669af 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,4 @@ [flake8] per-file-ignores = - tests/*:E501, W503 \ No newline at end of file + tests/*:E501, W503 + tests/test_bookops_worldcat.py: F401 \ No newline at end of file diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index f070ffb..ce80861 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version}} diff --git a/bookops_worldcat/query.py b/bookops_worldcat/query.py index cac785a..568ebb4 100644 --- a/bookops_worldcat/query.py +++ b/bookops_worldcat/query.py @@ -6,7 +6,7 @@ from typing import Union, Tuple, TYPE_CHECKING import sys -from requests.models import PreparedRequest +from requests import PreparedRequest from requests.exceptions import ConnectionError, HTTPError, Timeout, RetryError from .errors import WorldcatRequestError diff --git a/dev-requirements.txt b/dev-requirements.txt index a9ce8c4..f287921 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -518,6 +518,9 @@ six==1.16.0 ; python_version >= "3.8" and python_version < "4.0" \ tomli==2.0.1 ; python_version >= "3.8" and python_full_version <= "3.11.0a6" \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f +types-pyyaml==6.0.12.20240917 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570 \ + --hash=sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587 types-requests==2.31.0.20240125 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:03a28ce1d7cd54199148e043b2079cdded22d6795d19a2c2a6791a4b2b5e2eb5 \ --hash=sha256:9592a9a4cb92d6d75d9b491a41477272b710e021011a2a3061157e2fb1f1a5d1 diff --git a/poetry.lock b/poetry.lock index fb7f8ad..7397a53 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1122,6 +1122,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "types-pyyaml" +version = "6.0.12.20240917" +description = "Typing stubs for PyYAML" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, + {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, +] + [[package]] name = "types-requests" version = "2.31.0.20240125" @@ -1249,4 +1260,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "e29863e4f33eb0046c65ae158f498bc02ceb3e5f1cc830220ba087a261857db9" +content-hash = "9175a4a2b44bc49e980b8679325995fb25fc9445ac53ca46943766741818b91c" diff --git a/pyproject.toml b/pyproject.toml index 8db391e..ce4557a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ types-requests = "^2.31.0.20240125" mkdocs-material = "^9.5.13" mkdocstrings = "^0.24.1" mkdocstrings-python = "^1.9.0" +types-pyyaml = "^6.0.12.20240917" [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/tests/conftest.py b/tests/conftest.py index e02bfcf..01cc096 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,18 +3,16 @@ import datetime import json import os - +from typing import Dict, Generator, Union import pytest import requests -from requests import Response - from bookops_worldcat import WorldcatAccessToken, MetadataSession @pytest.fixture def live_keys(): - if os.name == "nt": + if os.name == "nt" and not os.getenv("GITHUB_ACTIONS"): fh = os.path.join(os.environ["USERPROFILE"], ".oclc/nyp_wc_test.json") with open(fh, "r") as file: data = json.load(file) @@ -24,25 +22,20 @@ def live_keys(): @pytest.fixture -def stub_marc_xml(): +def stub_marc_xml() -> str: stub_marc_xml = "00000nam a2200000 a 4500120827s2012 nyua 000 0 eng d 63011276 ocn850940548OCWMSengOCWMSOCLC Developer NetworkTest RecordFOR OCLC DEVELOPER NETWORK DOCUMENTATION" return stub_marc_xml @pytest.fixture -def stub_holding_xml(): +def stub_holding_xml() -> str: stub_holding_xml = "00000nx a2200000zi 4500312010zu1103280p 0 4001uueng0210908OCWMSEASTEAST-STACKS879456" return stub_holding_xml @pytest.fixture -def stub_marc21(): - fh = os.path.join( - os.environ["USERPROFILE"], "github/bookops-worldcat/tests/test.mrc" - ) - with open(fh, "rb") as stub: - stub_marc21 = stub.read() - return stub_marc21 +def stub_marc21() -> bytes: + return b"00266nam a2200097 a 4500008004100000010001700041040002200058100002700080245001600107500004500123\x1e120827s2012 nyua 000 0 eng d\x1e \x1fa 63011276 \x1e \x1faOCWMS\x1fbeng\x1fcOCWMS\x1e0 \x1faOCLC Developer Network\x1e10\x1faTest Record\x1e \x1faFOR OCLC DEVELOPER NETWORK DOCUMENTATION\x1e\x1d" class FakeUtcNow(datetime.datetime): @@ -59,10 +52,10 @@ def mock_now(monkeypatch): class MockAuthServerResponseSuccess: """Simulates auth server response to successful token request""" - def __init__(self): + def __init__(self) -> None: self.status_code = 200 - def json(self): + def json(self) -> Dict[str, str]: expires_at = datetime.datetime.strftime( datetime.datetime.now() + datetime.timedelta(0, 1199), "%Y-%m-%d %H:%M:%SZ", @@ -84,11 +77,11 @@ def json(self): class MockAuthServerResponseFailure: """Simulates auth server response to successful token request""" - def __init__(self): + def __init__(self) -> None: self.status_code = 403 self.content = b"" - def json(self): + def json(self) -> Dict[str, Union[str, int]]: return { "code": 403, "message": "Invalid scope(s): invalid (invalid) [Invalid service specified, Not on key]", @@ -98,38 +91,38 @@ def json(self): class MockServiceErrorResponse: """Simulates web service error responses""" - def __init__(self, code, json_response, url): + def __init__(self, code, json_response, url) -> None: self.status_code = code self.msg = json_response self.url = url self.text = f"{json_response}" - def json(self): + def json(self) -> Dict[str, str]: return self.msg class MockUnexpectedException: - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: raise Exception class MockTimeout: - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: raise requests.exceptions.Timeout class MockConnectionError: - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: raise requests.exceptions.ConnectionError class MockRetryError: - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: raise requests.exceptions.RetryError -class MockHTTPSessionResponse(Response): - def __init__(self, http_code): +class MockHTTPSessionResponse(requests.Response): + def __init__(self, http_code) -> None: self.status_code = http_code self.reason = "'foo'" self.url = "https://foo.bar?query" @@ -156,7 +149,7 @@ def mock_api_response(*args, http_code=http_code, **kwargs): @pytest.fixture -def mock_credentials(): +def mock_credentials() -> Dict[str, str]: return { "key": "my_WSkey", "secret": "my_WSsecret", @@ -165,7 +158,9 @@ def mock_credentials(): @pytest.fixture -def mock_oauth_server_response(mock_now, *args, **kwargs): +def mock_oauth_server_response( + mock_now, *args, **kwargs +) -> MockAuthServerResponseSuccess: return MockAuthServerResponseSuccess() @@ -214,18 +209,20 @@ def mock_retry_error(monkeypatch): @pytest.fixture -def mock_token(mock_credentials, mock_successful_post_token_response): +def mock_token( + mock_credentials, mock_successful_post_token_response +) -> WorldcatAccessToken: return WorldcatAccessToken(**mock_credentials) @pytest.fixture -def stub_session(mock_token): +def stub_session(mock_token) -> Generator[MetadataSession, None, None]: with MetadataSession(authorization=mock_token) as session: yield session @pytest.fixture -def stub_retry_session(mock_token): +def stub_retry_session(mock_token) -> Generator[MetadataSession, None, None]: with MetadataSession( authorization=mock_token, totalRetries=3, @@ -234,34 +231,3 @@ def stub_retry_session(mock_token): allowedMethods=["GET", "POST", "PUT"], ) as session: yield session - - -@pytest.fixture -def mock_400_response(monkeypatch): - def mock_api_response(*args, **kwargs): - msg = { - "type": "MISSING_QUERY_PARAMETER", - "title": "Validation Failure", - "detail": "details here", - } - url = "https://test.org/some_endpoint" - return MockServiceErrorResponse(code=400, json_response=msg, url=url) - - monkeypatch.setattr(requests.Session, "get", mock_api_response) - monkeypatch.setattr(requests.Session, "post", mock_api_response) - monkeypatch.setattr(requests.Session, "delete", mock_api_response) - - -@pytest.fixture -def mock_409_response(monkeypatch): - def mock_api_response(*args, **kwargs): - msg = { - "code": {"value": "WS-409", "type": "application"}, - "message": "Trying to set hold while holding already exists", - "detail": None, - } - url = "https://test.org/some_endpoint" - return MockServiceErrorResponse(code=409, json_response=msg, url=url) - - monkeypatch.setattr(requests.Session, "post", mock_api_response) - monkeypatch.setattr(requests.Session, "delete", mock_api_response) diff --git a/tests/test.mrc b/tests/test.mrc deleted file mode 100644 index 5b061b0..0000000 --- a/tests/test.mrc +++ /dev/null @@ -1 +0,0 @@ -00266nam a2200097 a 4500008004100000010001700041040002200058100002700080245001600107500004500123120827s2012 nyua 000 0 eng d a 63011276  aOCWMSbengcOCWMS0 aOCLC Developer Network10aTest Record aFOR OCLC DEVELOPER NETWORK DOCUMENTATION \ No newline at end of file