Repository: epikulski/digitalarchive

Issue:
Accept ISO 639-3 language codes when searching for documents by language
Currently, users must pass an entire language object to limit searches by language. Adding the ability to simply pass a 639-3 language string would make searches easier.



Comments:

Patch:
diff --git a/digitalarchive/exceptions.py b/digitalarchive/exceptions.py
index 8f0f3da..94f222d 100644
--- a/digitalarchive/exceptions.py
+++ b/digitalarchive/exceptions.py
@@ -14,3 +14,6 @@ class NoSuchResourceError(Exception):
 
 class APIServerError(Exception):
     """DA API returned a non-200 code for attempted operation."""
+
+class MalformedLanguageSearch(InvalidSearchFieldError):
+    """User passed a langauge search term that was not an instance of models.Language or an ISO 639-2/B string."""
diff --git a/digitalarchive/models.py b/digitalarchive/models.py
index d887150..bb8a4b7 100644
--- a/digitalarchive/models.py
+++ b/digitalarchive/models.py
@@ -663,7 +663,8 @@ class Document(_MatchableResource, _HydrateableResource, _TimestampedResource):
                 authors include all of the passed contributors.
             donors (list(:class:`digitalarchive.models.Donor`)) Restrict results to Documents who were obtained or
                 translated with support from all of the passed donors.
-            language (:class:`digitalarchive.models.Language`) Restrict results to Documents by original language.
+            languages (:class:`digitalarchive.models.Language` or str) Restrict results to Documents by language of
+                original document. If passing a string, you must pass an ISO 639-2/B language code.
             translation (:class:`digitalarchive.models.Translation`) Restrict results to Documents for which there
                 is a translation available in the passed Language.
             theme (:class:`digitalarchive.models.Theme`) Restrict results to Documents belonging to the passed Theme.
@@ -693,6 +694,10 @@ class Document(_MatchableResource, _HydrateableResource, _TimestampedResource):
         if any(key in kwargs.keys() for key in ["start_date", "end_date"]):
             kwargs = Document._process_date_searches(kwargs)
 
+        # Process language searches if they are present.
+        if "languages" in kwargs.keys():
+            kwargs = Document._process_language_search(kwargs)
+
         # Process any related model searches.
         if any(
             key in kwargs.keys()
@@ -896,6 +901,35 @@ class Document(_MatchableResource, _HydrateableResource, _TimestampedResource):
         # Return the reformatted query.
         return query
 
+    @staticmethod
+    def _process_language_search(query: dict) -> dict:
+        """
+        Process a language search
+
+        Looks up the DA's language ID# for user provided ISO 639-2/B language codes and updates the query.
+
+        Args:
+            query (dict): A ResourceMatcher query.
+
+        Returns:
+            dict: A query dict with a ISO 639-2/B string replaced with appropriate Language object.
+        """
+        parsed_languages = []
+        for language in query["languages"]:
+            # Check if ID# is instance of language, bail on yes.
+            if isinstance(language, Language):
+                parsed_languages.append(language)
+
+            # If str, lookup ID# of language
+            elif isinstance(language, str) and len(language) == 3:
+                parsed_languages.append(Language(id=language))
+
+            else:
+                raise exceptions.MalformedLanguageSearch
+
+            # Replace kwarg with Langauge object.
+            query["languages"] = parsed_languages
+            return query
 
 @dataclass(eq=False)
 class Theme(_HydrateableResource):


Test patch:
diff --git a/tests/integration/test_models.py b/tests/integration/test_models.py
index 144d889..8ae297a 100644
--- a/tests/integration/test_models.py
+++ b/tests/integration/test_models.py
@@ -260,6 +260,12 @@ class TestDocument:
         for doc in docs.all():
             assert language in doc.languages
 
+    def test_search_by_language_by_iso_code(self):
+        all_docs = digitalarchive.Document.match()
+        german_docs = digitalarchive.Document.match(languages=["ger"])
+        assert german_docs.count > 0
+        assert german_docs.count < all_docs.count
+
     def test_search_by_translation(self):
         language = digitalarchive.models.Language(id="chi")
         docs = digitalarchive.Document.match(translations=[language])
diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py
index c943d72..fb84a01 100644
--- a/tests/unit/test_models.py
+++ b/tests/unit/test_models.py
@@ -192,6 +192,10 @@ class TestDocument:
         # mock_matching.assert_called_with(models.Document, collection[]= mock_collections, model="Record"})
         assert "collection[]" in mock_matching.call_args[1].keys()
 
+    def test_invalid_language_search(self):
+        with pytest.raises(exceptions.MalformedLanguageSearch):
+            models.Document.match(languages=["invalid"])
+
     @unittest.mock.patch("digitalarchive.models.api")
     def test_process_date_search_only_end_date(self, mock_api):
         test_date = date(1989, 4, 15)