diff --git a/bookops_worldcat/__init__.py b/bookops_worldcat/__init__.py index 15d26cf..edbf411 100644 --- a/bookops_worldcat/__init__.py +++ b/bookops_worldcat/__init__.py @@ -1,4 +1,4 @@ -from .__version__ import __title__, __version__ +from .__version__ import __title__, __version__ # noqa: F401 -from .authorize import WorldcatAccessToken -from .metadata_api import MetadataSession +from .authorize import WorldcatAccessToken # noqa: F401 +from .metadata_api import MetadataSession # noqa: F401 diff --git a/bookops_worldcat/_session.py b/bookops_worldcat/_session.py index 601fa60..487f3cf 100644 --- a/bookops_worldcat/_session.py +++ b/bookops_worldcat/_session.py @@ -11,7 +11,6 @@ from . import __title__, __version__ from .authorize import WorldcatAccessToken -from .errors import WorldcatSessionError, WorldcatAuthorizationError class WorldcatSession(requests.Session): @@ -21,7 +20,7 @@ def __init__( self, authorization: WorldcatAccessToken, agent: Optional[str] = None, - timeout: Union[int, float, Tuple[int, int], Tuple[float, float]] = ( + timeout: Union[int, float, Tuple[int, int], Tuple[float, float], None] = ( 5, 5, ), @@ -39,7 +38,7 @@ def __init__( self.authorization = authorization if not isinstance(self.authorization, WorldcatAccessToken): - raise WorldcatSessionError( + raise TypeError( "Argument 'authorization' must be 'WorldcatAccessToken' object." ) @@ -48,7 +47,7 @@ def __init__( elif agent and isinstance(agent, str): self.headers.update({"User-Agent": agent}) else: - raise WorldcatSessionError("Argument 'agent' must be a string.") + raise ValueError("Argument 'agent' must be a string.") self.timeout = timeout @@ -59,11 +58,8 @@ def _get_new_access_token(self) -> None: Allows to continue sending request with new access token after the previous one expired """ - try: - self.authorization._request_token() - self._update_authorization() - except WorldcatAuthorizationError as exc: - raise WorldcatSessionError(exc) + self.authorization._request_token() + self._update_authorization() def _update_authorization(self) -> None: self.headers.update({"Authorization": f"Bearer {self.authorization.token_str}"}) diff --git a/bookops_worldcat/authorize.py b/bookops_worldcat/authorize.py index e71a26a..4b3d3bb 100644 --- a/bookops_worldcat/authorize.py +++ b/bookops_worldcat/authorize.py @@ -99,41 +99,43 @@ def __init__( self.token_type = "" # default bookops-worldcat request header - if not self.agent: - self.agent = f"{__title__}/{__version__}" + if isinstance(self.agent, str): + if not self.agent.strip(): + self.agent = f"{__title__}/{__version__}" else: - if not isinstance(self.agent, str): - raise WorldcatAuthorizationError("Argument 'agent' must be a string.") + raise TypeError("Argument 'agent' must be a string.") # asure passed arguments are valid - if not self.key: - raise WorldcatAuthorizationError("Argument 'key' is required.") + if isinstance(self.key, str): + if not self.key.strip(): + raise ValueError("Argument 'key' cannot be an empty string.") else: - if not isinstance(self.key, str): - raise WorldcatAuthorizationError("Argument 'key' must be a string.") + raise TypeError("Argument 'key' must be a string.") - if not self.secret: - raise WorldcatAuthorizationError("Argument 'secret' is required.") + if isinstance(self.secret, str): + if not self.secret.strip(): + raise ValueError("Argument 'secret' cannot be an empty string.") else: - if not isinstance(self.secret, str): - raise WorldcatAuthorizationError("Argument 'secret' must be a string.") + raise TypeError("Argument 'secret' must be a string.") - if not self.principal_id: - raise WorldcatAuthorizationError( - "Argument 'principal_id' is required for read/write endpoint of " - "Metadata API." - ) - if not self.principal_idns: - raise WorldcatAuthorizationError( - "Argument 'principal_idns' is required for read/write endpoint of " - "Metadata API." - ) + if isinstance(self.principal_id, str): + if not self.principal_id.strip(): + raise ValueError("Argument 'principal_id' cannot be an empty string.") + else: + raise TypeError("Argument 'principal_id' must be a string.") + + if isinstance(self.principal_idns, str): + if not self.principal_idns.strip(): + raise ValueError("Argument 'principal_idns' cannot be an empty string.") + else: + raise TypeError("Argument 'principal_idns' must be a string.") # validate passed scopes - if not self.scopes: - raise WorldcatAuthorizationError("Argument 'scopes' is required.") - elif not isinstance(self.scopes, str): - raise WorldcatAuthorizationError("Argument 'scopes' must a string.") + if isinstance(self.scopes, str): + if not self.scopes.strip(): + raise ValueError("Argument 'scopes' cannot be an empty string.") + else: + raise TypeError("Argument 'scopes' must a string.") self.scopes = self.scopes.strip() # assign default value for timout @@ -242,7 +244,10 @@ def is_expired(self) -> bool: else: return False else: - raise TypeError + raise TypeError( + "Attribue 'WorldcatAccessToken.token_expires_at' is of invalid type. " + "Expected `datetime.datetime` object." + ) def __repr__(self): return ( diff --git a/bookops_worldcat/errors.py b/bookops_worldcat/errors.py index 242cfea..7b563ce 100644 --- a/bookops_worldcat/errors.py +++ b/bookops_worldcat/errors.py @@ -19,15 +19,7 @@ class WorldcatAuthorizationError(BookopsWorldcatError): pass -class WorldcatSessionError(BookopsWorldcatError): - """ - Exception raised during WorlCat session - """ - - pass - - -class WorldcatRequestError(WorldcatSessionError): +class WorldcatRequestError(BookopsWorldcatError): """ Exceptions raised on HTTP errors returned by web service """ diff --git a/bookops_worldcat/metadata_api.py b/bookops_worldcat/metadata_api.py index 0466004..4ed5af3 100644 --- a/bookops_worldcat/metadata_api.py +++ b/bookops_worldcat/metadata_api.py @@ -4,16 +4,12 @@ This module provides MetadataSession class for requests to WorldCat Metadata API. """ -from typing import Callable, Dict, List, Optional, Tuple, Union +from typing import Callable, Dict, Iterator, List, Optional, Tuple, Union from requests import Request, Response from ._session import WorldcatSession from .authorize import WorldcatAccessToken -from .errors import ( - WorldcatSessionError, - InvalidOclcNumber, -) from .query import Query from .utils import verify_oclc_number, verify_oclc_numbers @@ -25,9 +21,7 @@ def __init__( self, authorization: WorldcatAccessToken, agent: Optional[str] = None, - timeout: Optional[ - Union[int, float, Tuple[int, int], Tuple[float, float]] - ] = None, + timeout: Union[int, float, Tuple[int, int], Tuple[float, float], None] = None, ) -> None: """ Args: @@ -41,7 +35,7 @@ def __init__( def _split_into_legal_volume( self, oclc_numbers: List[str] = [], n: int = 50 - ) -> List[str]: + ) -> Iterator[str]: """ OCLC requries that no more than 50 numbers are passed for batch processing @@ -54,7 +48,7 @@ def _split_into_legal_volume( """ for i in range(0, len(oclc_numbers), n): - yield ",".join(oclc_numbers[i : i + n]) + yield ",".join(oclc_numbers[i : i + n]) # noqa: E203 def _url_base(self) -> str: return "https://worldcat.org" @@ -124,7 +118,7 @@ def _url_bib_holdings_multi_institution_batch_action(self) -> str: def get_brief_bib( self, oclcNumber: Union[int, str], hooks: Optional[Dict[str, Callable]] = None - ) -> Response: + ) -> Optional[Response]: """ Retrieve specific brief bibliographic resource. Uses /brief-bibs/{oclcNumber} endpoint. @@ -139,11 +133,7 @@ def get_brief_bib( Returns: `requests.Response` instance """ - - try: - oclcNumber = verify_oclc_number(oclcNumber) - except InvalidOclcNumber: - raise WorldcatSessionError("Invalid OCLC # was passed as an argument") + oclcNumber = verify_oclc_number(oclcNumber) header = {"Accept": "application/json"} url = self._url_brief_bib_oclc_number(oclcNumber) @@ -162,7 +152,7 @@ def get_full_bib( oclcNumber: Union[int, str], response_format: Optional[str] = None, hooks: Optional[Dict[str, Callable]] = None, - ) -> Response: + ) -> Optional[Response]: """ Send a GET request for a full bibliographic resource. Uses /bib/data/{oclcNumber} endpoint. @@ -177,10 +167,7 @@ def get_full_bib( Returns: `requests.Response` object """ - try: - oclcNumber = verify_oclc_number(oclcNumber) - except InvalidOclcNumber: - raise WorldcatSessionError("Invalid OCLC # was passed as an argument.") + oclcNumber = verify_oclc_number(oclcNumber) url = self._url_bib_oclc_number(oclcNumber) if not response_format: @@ -205,7 +192,7 @@ def holding_get_status( instSymbol: Optional[str] = None, response_format: Optional[str] = "application/atom+json", hooks: Optional[Dict[str, Callable]] = None, - ) -> Response: + ) -> Optional[Response]: """ Retrieves Worlcat holdings status of a record with provided OCLC number. The service automatically recognizes institution based on the issued access @@ -228,10 +215,7 @@ def holding_get_status( Returns: `requests.Response` object """ - try: - oclcNumber = verify_oclc_number(oclcNumber) - except InvalidOclcNumber as exc: - raise WorldcatSessionError(exc) + oclcNumber = verify_oclc_number(oclcNumber) url = self._url_bib_holdings_check() header = {"Accept": response_format} @@ -255,7 +239,7 @@ def holding_set( classificationScheme: Optional[str] = None, response_format: str = "application/atom+json", hooks: Optional[Dict[str, Callable]] = None, - ) -> Response: + ) -> Optional[Response]: """ Sets institution's Worldcat holding on an individual record. Uses /ih/data endpoint. @@ -279,11 +263,7 @@ def holding_set( Returns: `requests.Response` object """ - - try: - oclcNumber = verify_oclc_number(oclcNumber) - except InvalidOclcNumber as exc: - raise WorldcatSessionError(exc) + oclcNumber = verify_oclc_number(oclcNumber) url = self._url_bib_holdings_action() header = {"Accept": response_format} @@ -314,7 +294,7 @@ def holding_unset( classificationScheme: Optional[str] = None, response_format: str = "application/atom+json", hooks: Optional[Dict[str, Callable]] = None, - ) -> Response: + ) -> Optional[Response]: """ Deletes institution's Worldcat holding on an individual record. Uses /ih/data endpoint. @@ -344,11 +324,7 @@ def holding_unset( Returns: `requests.Response` object """ - - try: - oclcNumber = verify_oclc_number(oclcNumber) - except InvalidOclcNumber as exc: - raise WorldcatSessionError(exc) + oclcNumber = verify_oclc_number(oclcNumber) url = self._url_bib_holdings_action() header = {"Accept": response_format} @@ -377,7 +353,7 @@ def holdings_set( instSymbol: Optional[str] = None, response_format: str = "application/atom+json", hooks: Optional[Dict[str, Callable]] = None, - ) -> List[Response]: + ) -> List[Optional[Response]]: """ Set institution holdings for multiple OCLC numbers Uses /ih/datalist endpoint. @@ -401,11 +377,7 @@ def holdings_set( list of `requests.Response` objects """ responses = [] - - try: - vetted_numbers = verify_oclc_numbers(oclcNumbers) - except InvalidOclcNumber as exc: - raise WorldcatSessionError(exc) + vetted_numbers = verify_oclc_numbers(oclcNumbers) url = self._url_bib_holdings_batch_action() header = {"Accept": response_format} @@ -437,7 +409,7 @@ def holdings_unset( instSymbol: Optional[str] = None, response_format: str = "application/atom+json", hooks: Optional[Dict[str, Callable]] = None, - ) -> List[Response]: + ) -> List[Optional[Response]]: """ Set institution holdings for multiple OCLC numbers Uses /ih/datalist endpoint. @@ -466,11 +438,7 @@ def holdings_unset( list of `requests.Response` objects """ responses = [] - - try: - vetted_numbers = verify_oclc_numbers(oclcNumbers) - except InvalidOclcNumber as exc: - raise WorldcatSessionError(exc) + vetted_numbers = verify_oclc_numbers(oclcNumbers) url = self._url_bib_holdings_batch_action() header = {"Accept": response_format} @@ -501,7 +469,7 @@ def holdings_set_multi_institutions( instSymbols: str, response_format: str = "application/atom+json", hooks: Optional[Dict[str, Callable]] = None, - ) -> Response: + ) -> Optional[Response]: """ Batch sets intitution holdings for multiple intitutions @@ -520,10 +488,7 @@ def holdings_set_multi_institutions( Returns: `requests.Response` object """ - try: - oclcNumber = verify_oclc_number(oclcNumber) - except InvalidOclcNumber: - raise WorldcatSessionError("Invalid OCLC # was passed as an argument") + oclcNumber = verify_oclc_number(oclcNumber) url = self._url_bib_holdings_multi_institution_batch_action() header = {"Accept": response_format} @@ -548,7 +513,7 @@ def holdings_unset_multi_institutions( cascade: str = "0", response_format: str = "application/atom+json", hooks: Optional[Dict[str, Callable]] = None, - ) -> Response: + ) -> Optional[Response]: """ Batch unsets intitution holdings for multiple intitutions @@ -572,10 +537,7 @@ def holdings_unset_multi_institutions( Returns: `requests.Response` object """ - try: - oclcNumber = verify_oclc_number(oclcNumber) - except InvalidOclcNumber: - raise WorldcatSessionError("Invalid OCLC # was passed as an argument") + oclcNumber = verify_oclc_number(oclcNumber) url = self._url_bib_holdings_multi_institution_batch_action() header = {"Accept": response_format} @@ -624,7 +586,7 @@ def search_brief_bib_other_editions( limit: Optional[int] = None, orderBy: Optional[str] = None, hooks: Optional[Dict[str, Callable]] = None, - ) -> Response: + ) -> Optional[Response]: """ Retrieve other editions related to bibliographic resource with provided OCLC #. @@ -699,10 +661,7 @@ def search_brief_bib_other_editions( Returns: `requests.Response` object """ - try: - oclcNumber = verify_oclc_number(oclcNumber) - except InvalidOclcNumber: - raise WorldcatSessionError("Invalid OCLC # was passed as an argument") + oclcNumber = verify_oclc_number(oclcNumber) url = self._url_brief_bib_other_editions(oclcNumber) header = {"Accept": "application/json"} @@ -765,7 +724,7 @@ def search_brief_bibs( offset: Optional[int] = None, limit: Optional[int] = None, hooks: Optional[Dict[str, Callable]] = None, - ) -> Response: + ) -> Optional[Response]: """ Send a GET request for brief bibliographic resources. Uses /brief-bibs endpoint. @@ -840,7 +799,7 @@ def search_brief_bibs( """ if not q: - raise WorldcatSessionError("Argument 'q' is requried to construct query.") + raise TypeError("Argument 'q' is requried to construct query.") url = self._url_brief_bib_search() header = {"Accept": "application/json"} @@ -880,7 +839,7 @@ def search_current_control_numbers( oclcNumbers: Union[str, List[Union[str, int]]], response_format: str = "application/atom+json", hooks: Optional[Dict[str, Callable]] = None, - ) -> Response: + ) -> Optional[Response]: """ Retrieve current OCLC control numbers Uses /bib/checkcontrolnumbers endpoint. @@ -900,10 +859,7 @@ def search_current_control_numbers( `requests.Response` object """ - try: - vetted_numbers = verify_oclc_numbers(oclcNumbers) - except InvalidOclcNumber as exc: - raise WorldcatSessionError(exc) + vetted_numbers = verify_oclc_numbers(oclcNumbers) header = {"Accept": response_format} url = self._url_bib_check_oclc_numbers() @@ -920,7 +876,7 @@ def search_current_control_numbers( def search_general_holdings( self, - oclcNumber: Union[int, str] = None, + oclcNumber: Union[int, str, None] = None, isbn: Optional[str] = None, issn: Optional[str] = None, holdingsAllEditions: Optional[bool] = None, @@ -935,7 +891,7 @@ def search_general_holdings( offset: Optional[int] = None, limit: Optional[int] = None, hooks: Optional[Dict[str, Callable]] = None, - ) -> Response: + ) -> Optional[Response]: """ Given a known item gets summary of holdings. Uses /bibs-summary-holdings endpoint. @@ -973,15 +929,12 @@ def search_general_holdings( `requests.Response` object """ if not any([oclcNumber, isbn, issn]): - raise WorldcatSessionError( + raise TypeError( "Missing required argument. " "One of the following args are required: oclcNumber, issn, isbn" ) if oclcNumber is not None: - try: - oclcNumber = verify_oclc_number(oclcNumber) - except InvalidOclcNumber: - raise WorldcatSessionError("Invalid OCLC # was passed as an argument") + oclcNumber = verify_oclc_number(oclcNumber) url = self._url_member_general_holdings() header = {"Accept": "application/json"} @@ -1013,7 +966,7 @@ def search_general_holdings( def search_shared_print_holdings( self, - oclcNumber: Union[int, str] = None, + oclcNumber: Union[int, str, None] = None, isbn: Optional[str] = None, issn: Optional[str] = None, heldByGroup: Optional[str] = None, @@ -1023,7 +976,7 @@ def search_shared_print_holdings( offset: Optional[int] = None, limit: Optional[int] = None, hooks: Optional[Dict[str, Callable]] = None, - ) -> Response: + ) -> Optional[Response]: """ Finds member shared print holdings for specified item. Uses /bibs-retained-holdings endpoint. @@ -1051,16 +1004,13 @@ def search_shared_print_holdings( `requests.Response` object """ if not any([oclcNumber, isbn, issn]): - raise WorldcatSessionError( + raise TypeError( "Missing required argument. " "One of the following args are required: oclcNumber, issn, isbn" ) if oclcNumber is not None: - try: - oclcNumber = verify_oclc_number(oclcNumber) - except InvalidOclcNumber: - raise WorldcatSessionError("Invalid OCLC # was passed as an argument") + oclcNumber = verify_oclc_number(oclcNumber) url = self._url_member_shared_print_holdings() header = {"Accept": "application/json"} diff --git a/bookops_worldcat/query.py b/bookops_worldcat/query.py index 21ec2f5..1a99722 100644 --- a/bookops_worldcat/query.py +++ b/bookops_worldcat/query.py @@ -47,7 +47,7 @@ def __init__( """ if not isinstance(prepared_request, PreparedRequest): - raise AttributeError("Invalid type for argument 'prepared_request'.") + raise TypeError("Invalid type for argument 'prepared_request'.") # make sure access token is still valid and if not request a new one if session.authorization.is_expired(): @@ -58,7 +58,7 @@ def __init__( try: self.response = session.send(prepared_request, timeout=timeout) - if "/ih/data" in prepared_request.url: + if "/ih/data" in prepared_request.url: # type: ignore if self.response.status_code == 409: # HTTP 409 code returns when trying to set/unset # holdings on already set/unset record @@ -72,7 +72,8 @@ def __init__( except HTTPError as exc: raise WorldcatRequestError( - f"{exc}. Server response: {self.response.content}" + f"{exc}. Server response: " # type: ignore + f"{self.response.content.decode('utf-8')}" ) except (Timeout, ConnectionError): raise WorldcatRequestError(f"Connection Error: {sys.exc_info()[0]}") diff --git a/tests/test_authorize.py b/tests/test_authorize.py index 4e96d41..f9b1f79 100644 --- a/tests/test_authorize.py +++ b/tests/test_authorize.py @@ -19,17 +19,17 @@ class TestWorldcatAccessToken: [ ( None, - pytest.raises(WorldcatAuthorizationError), - "Argument 'key' is required.", + pytest.raises(TypeError), + "Argument 'key' must be a string.", ), ( "", - pytest.raises(WorldcatAuthorizationError), - "Argument 'key' is required.", + pytest.raises(ValueError), + "Argument 'key' cannot be an empty string.", ), ( 124, - pytest.raises(WorldcatAuthorizationError), + pytest.raises(TypeError), "Argument 'key' must be a string.", ), ], @@ -43,24 +43,24 @@ def test_key_exceptions(self, argm, expectation, msg): principal_id="my_principalID", principal_idns="my_principalIDNS", ) - assert msg in str(exp.value) + assert msg in str(exp.value) @pytest.mark.parametrize( "argm,expectation,msg", [ ( None, - pytest.raises(WorldcatAuthorizationError), - "Argument 'secret' is required.", + pytest.raises(TypeError), + "Argument 'secret' must be a string.", ), ( "", - pytest.raises(WorldcatAuthorizationError), - "Argument 'secret' is required.", + pytest.raises(ValueError), + "Argument 'secret' cannot be an empty string.", ), ( 123, - pytest.raises(WorldcatAuthorizationError), + pytest.raises(TypeError), "Argument 'secret' must be a string.", ), ], @@ -74,10 +74,10 @@ def test_secret_exceptions(self, argm, expectation, msg): principal_id="my_principalID", principal_idns="my_principalIDNS", ) - assert msg in str(exp.value) + assert msg in str(exp.value) def test_agent_exceptions(self): - with pytest.raises(WorldcatAuthorizationError) as exp: + with pytest.raises(TypeError) as exp: WorldcatAccessToken( key="my_key", secret="my_secret", @@ -86,7 +86,7 @@ def test_agent_exceptions(self): principal_idns="my_principalIDNS", agent=124, ) - assert "Argument 'agent' must be a string." in str(exp.value) + assert "Argument 'agent' must be a string." in str(exp.value) def test_agent_default_values(self, mock_successful_post_token_response): token = WorldcatAccessToken( @@ -103,13 +103,13 @@ def test_agent_default_values(self, mock_successful_post_token_response): [ ( None, - pytest.raises(WorldcatAuthorizationError), - "Argument 'principal_id' is required for read/write endpoint of Metadata API.", + pytest.raises(TypeError), + "Argument 'principal_id' must be a string.", ), ( "", - pytest.raises(WorldcatAuthorizationError), - "Argument 'principal_id' is required for read/write endpoint of Metadata API.", + pytest.raises(ValueError), + "Argument 'principal_id' cannot be an empty string.", ), ], ) @@ -122,20 +122,20 @@ def test_principal_id_exception(self, arg, expectation, msg): principal_id=arg, principal_idns="my_principalIDNS", ) - assert msg in str(exc.value) + assert msg in str(exc.value) @pytest.mark.parametrize( "arg,expectation,msg", [ ( None, - pytest.raises(WorldcatAuthorizationError), - "Argument 'principal_idns' is required for read/write endpoint of Metadata API.", + pytest.raises(TypeError), + "Argument 'principal_idns' must be a string.", ), ( "", - pytest.raises(WorldcatAuthorizationError), - "Argument 'principal_idns' is required for read/write endpoint of Metadata API.", + pytest.raises(ValueError), + "Argument 'principal_idns' cannot be an empty string.", ), ], ) @@ -148,29 +148,29 @@ def test_principal_idns_exception(self, arg, expectation, msg): principal_id="my_principalID", principal_idns=arg, ) - assert msg in str(exc.value) + assert msg in str(exc.value) @pytest.mark.parametrize( "argm,expectation,msg", [ ( None, - pytest.raises(WorldcatAuthorizationError), + pytest.raises(TypeError), "Argument 'scopes' must a string.", ), ( 123, - pytest.raises(WorldcatAuthorizationError), + pytest.raises(TypeError), "Argument 'scopes' must a string.", ), ( " ", - pytest.raises(WorldcatAuthorizationError), - "Argument 'scopes' is required.", + pytest.raises(ValueError), + "Argument 'scopes' cannot be an empty string.", ), ( ["", ""], - pytest.raises(WorldcatAuthorizationError), + pytest.raises(TypeError), "Argument 'scopes' is required.", ), ], diff --git a/tests/test_metadata_api.py b/tests/test_metadata_api.py index a6d615d..dd8c884 100644 --- a/tests/test_metadata_api.py +++ b/tests/test_metadata_api.py @@ -9,7 +9,11 @@ from bookops_worldcat import MetadataSession, WorldcatAccessToken -from bookops_worldcat.errors import WorldcatSessionError, WorldcatRequestError +from bookops_worldcat.errors import ( + WorldcatRequestError, + WorldcatAuthorizationError, + InvalidOclcNumber, +) @contextmanager @@ -36,7 +40,7 @@ def test_missing_authorization(self): def test_invalid_authorizaiton(self): err_msg = "Argument 'authorization' must be 'WorldcatAccessToken' object." - with pytest.raises(WorldcatSessionError) as exc: + with pytest.raises(TypeError) as exc: MetadataSession(authorization="my_token") assert err_msg in str(exc.value) @@ -54,7 +58,7 @@ def test_get_new_access_token(self, mock_token, mock_now): assert session.authorization.is_expired() is False def test_get_new_access_token_exceptions(self, stub_session, mock_timeout): - with pytest.raises(WorldcatSessionError): + with pytest.raises(WorldcatAuthorizationError): stub_session._get_new_access_token() @pytest.mark.parametrize( @@ -196,7 +200,7 @@ def test_get_brief_bib_no_oclcNumber_passed(self, stub_session): stub_session.get_brief_bib() def test_get_brief_bib_None_oclcNumber_passed(self, stub_session): - with pytest.raises(WorldcatSessionError): + with pytest.raises(InvalidOclcNumber): stub_session.get_brief_bib(oclcNumber=None) @pytest.mark.http_code(200) @@ -228,7 +232,7 @@ def test_get_brief_bib_404_error_response( stub_session.get_brief_bib(12345) assert ( - "404 Client Error: 'foo' for url: https://foo.bar?query. Server response: b'spam'" + "404 Client Error: 'foo' for url: https://foo.bar?query. Server response: spam" in (str(exc.value)) ) @@ -241,7 +245,7 @@ def test_get_full_bib_no_oclcNumber_passed(self, stub_session): stub_session.get_full_bib() def test_get_full_bib_None_oclcNumber_passed(self, stub_session): - with pytest.raises(WorldcatSessionError): + with pytest.raises(InvalidOclcNumber): stub_session.get_full_bib(oclcNumber=None) @pytest.mark.http_code(200) @@ -267,7 +271,7 @@ def test_holding_get_status_no_oclcNumber_passed(self, stub_session): stub_session.holding_get_status() def test_holding_get_status_None_oclcNumber_passed(self, stub_session): - with pytest.raises(WorldcatSessionError): + with pytest.raises(InvalidOclcNumber): stub_session.holding_get_status(oclcNumber=None) @pytest.mark.http_code(200) @@ -294,7 +298,7 @@ def test_holding_set_no_oclcNumber_passed(self, stub_session): stub_session.holding_set() def test_holding_set_None_oclcNumber_passed(self, stub_session): - with pytest.raises(WorldcatSessionError): + with pytest.raises(InvalidOclcNumber): stub_session.holding_set(oclcNumber=None) @pytest.mark.http_code(201) @@ -319,7 +323,7 @@ def test_holding_unset_no_oclcNumber_passed(self, stub_session): stub_session.holding_unset() def test_holding_unset_None_oclcNumber_passed(self, stub_session): - with pytest.raises(WorldcatSessionError): + with pytest.raises(InvalidOclcNumber): stub_session.holding_unset(oclcNumber=None) @pytest.mark.http_code(200) @@ -340,9 +344,9 @@ def test_holding_unset_stale_token( @pytest.mark.parametrize( "argm,expectation", [ - (None, pytest.raises(WorldcatSessionError)), - ([], pytest.raises(WorldcatSessionError)), - (["bt2111111111"], pytest.raises(WorldcatSessionError)), + (None, pytest.raises(InvalidOclcNumber)), + ([], pytest.raises(InvalidOclcNumber)), + (["bt2111111111"], pytest.raises(InvalidOclcNumber)), (["850940548"], does_not_raise()), (["ocn850940548"], does_not_raise()), ("850940548,850940552, 850940554", does_not_raise()), @@ -377,9 +381,9 @@ def test_holdings_set_stale_token( @pytest.mark.parametrize( "argm,expectation", [ - (None, pytest.raises(WorldcatSessionError)), - ([], pytest.raises(WorldcatSessionError)), - (["bt2111111111"], pytest.raises(WorldcatSessionError)), + (None, pytest.raises(InvalidOclcNumber)), + ([], pytest.raises(InvalidOclcNumber)), + (["bt2111111111"], pytest.raises(InvalidOclcNumber)), (["850940548"], does_not_raise()), (["ocn850940548"], does_not_raise()), ("850940548,850940552, 850940554", does_not_raise()), @@ -429,7 +433,7 @@ def test_holdings_set_multi_institutions_missing_inst_symbols(self, stub_session stub_session.holdings_set_multi_institutions(oclcNumber=123) def test_holdings_set_multi_institutions_invalid_oclc_number(self, stub_session): - with pytest.raises(WorldcatSessionError): + with pytest.raises(InvalidOclcNumber): stub_session.holdings_set_multi_institutions( oclcNumber="odn1234", instSymbols="NYP,BKL" ) @@ -461,7 +465,7 @@ def test_holdings_set_multi_institutions_permission_error( ) assert ( - "403 Client Error: 'foo' for url: https://foo.bar?query. Server response: b'spam'" + "403 Client Error: 'foo' for url: https://foo.bar?query. Server response: spam" in str(exc.value) ) @@ -483,7 +487,7 @@ def test_holdings_unset_multi_institutions_missing_inst_symbols(self, stub_sessi stub_session.holdings_unset_multi_institutions(oclcNumber=123) def test_holdings_unset_multi_institutions_invalid_oclc_number(self, stub_session): - with pytest.raises(WorldcatSessionError): + with pytest.raises(InvalidOclcNumber): stub_session.holdings_unset_multi_institutions( oclcNumber="odn1234", instSymbols="NYP,BKL" ) @@ -527,8 +531,8 @@ def test_search_brief_bibs_other_editions_stale_token( assert response.status_code == 200 def test_search_brief_bibs_other_editions_invalid_oclc_number(self, stub_session): - msg = "Invalid OCLC # was passed as an argument" - with pytest.raises(WorldcatSessionError) as exc: + msg = "Argument 'oclcNumber' does not look like real OCLC #." + with pytest.raises(InvalidOclcNumber) as exc: stub_session.search_brief_bib_other_editions("odn12345") assert msg in str(exc.value) @@ -538,7 +542,7 @@ def test_seach_brief_bibs(self, stub_session, mock_session_response): @pytest.mark.parametrize("argm", [(None), ("")]) def test_search_brief_bibs_missing_query(self, stub_session, argm): - with pytest.raises(WorldcatSessionError) as exc: + with pytest.raises(TypeError) as exc: stub_session.search_brief_bibs(argm) assert "Argument 'q' is requried to construct query." in str(exc.value) @@ -580,7 +584,7 @@ def test_seach_current_control_numbers_passed_as_str( @pytest.mark.parametrize("argm", [(None), (""), ([])]) def test_search_current_control_numbers_missing_numbers(self, stub_session, argm): err_msg = "Argument 'oclcNumbers' must be a list or comma separated string of valid OCLC #s." - with pytest.raises(WorldcatSessionError) as exc: + with pytest.raises(InvalidOclcNumber) as exc: stub_session.search_current_control_numbers(argm) assert err_msg in str(exc.value) @@ -605,13 +609,13 @@ def test_search_general_holdings(self, stub_session, mock_session_response): def test_search_general_holdings_missing_arguments(self, stub_session): msg = "Missing required argument. One of the following args are required: oclcNumber, issn, isbn" - with pytest.raises(WorldcatSessionError) as exc: + with pytest.raises(TypeError) as exc: stub_session.search_general_holdings(holdingsAllEditions=True, limit=20) assert msg in str(exc.value) def test_search_general_holdings_invalid_oclc_number(self, stub_session): - msg = "Invalid OCLC # was passed as an argument" - with pytest.raises(WorldcatSessionError) as exc: + msg = "Argument 'oclcNumber' does not look like real OCLC #." + with pytest.raises(InvalidOclcNumber) as exc: stub_session.search_general_holdings(oclcNumber="odn12345") assert msg in str(exc.value) @@ -639,15 +643,15 @@ def test_search_shared_print_holdings(self, stub_session, mock_session_response) def test_search_shared_print_holdings_missing_arguments(self, stub_session): msg = "Missing required argument. One of the following args are required: oclcNumber, issn, isbn" - with pytest.raises(WorldcatSessionError) as exc: + with pytest.raises(TypeError) as exc: stub_session.search_shared_print_holdings(heldInState="NY", limit=20) assert msg in str(exc.value) def test_search_shared_print_holdings_with_invalid_oclc_number_passsed( self, stub_session ): - msg = "Invalid OCLC # was passed as an argument" - with pytest.raises(WorldcatSessionError) as exc: + msg = "Argument 'oclcNumber' does not look like real OCLC #." + with pytest.raises(InvalidOclcNumber) as exc: stub_session.search_shared_print_holdings(oclcNumber="odn12345") assert msg in str(exc.value) diff --git a/tests/test_query.py b/tests/test_query.py index 50b62cf..7725145 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -38,7 +38,7 @@ def test_query_live(live_keys): def test_query_not_prepared_request(stub_session): - with pytest.raises(AttributeError) as exc: + with pytest.raises(TypeError) as exc: req = Request("GET", "https://foo.org") Query(stub_session, req, timeout=2) assert "Invalid type for argument 'prepared_request'." in str(exc.value) @@ -106,7 +106,10 @@ def test_query_http_404_response(stub_session, mock_session_response): with pytest.raises(WorldcatRequestError) as exc: Query(stub_session, prepped) - assert "404 Client Error: 'foo' for url: https://foo.bar?query" in str(exc.value) + assert ( + "404 Client Error: 'foo' for url: https://foo.bar?query. Server response: spam" + in str(exc.value) + ) @pytest.mark.http_code(500) @@ -116,7 +119,10 @@ def test_query_http_500_response(stub_session, mock_session_response): with pytest.raises(WorldcatRequestError) as exc: Query(stub_session, prepped) - assert "500 Server Error: 'foo' for url: https://foo.bar?query" in str(exc.value) + assert ( + "500 Server Error: 'foo' for url: https://foo.bar?query. Server response: spam" + in str(exc.value) + ) def test_query_timeout_exception(stub_session, mock_timeout): diff --git a/tests/test_session.py b/tests/test_session.py index 5242072..26f8539 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -5,7 +5,6 @@ from bookops_worldcat._session import WorldcatSession from bookops_worldcat.__version__ import __title__, __version__ -from bookops_worldcat.errors import WorldcatSessionError class TestWorldcatSession: @@ -28,7 +27,7 @@ def test_custom_user_agent_header(self, mock_token): [123, {}, (), ""], ) def test_invalid_user_agent_arguments(self, arg, mock_token): - with pytest.raises(WorldcatSessionError) as exc: + with pytest.raises(ValueError) as exc: WorldcatSession(mock_token, agent=arg) assert "Argument 'agent' must be a string." in str(exc.value)