From 4fe35b196c91427d3aec99b3673d18783316b666 Mon Sep 17 00:00:00 2001 From: Glen Robson Date: Fri, 21 Oct 2022 17:50:51 +0100 Subject: [PATCH 1/9] Restricting conversion to String to just URLs --- iiif_prezi3/base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/iiif_prezi3/base.py b/iiif_prezi3/base.py index 3ef59ff..2eca541 100644 --- a/iiif_prezi3/base.py +++ b/iiif_prezi3/base.py @@ -1,23 +1,23 @@ import json -from pydantic import BaseModel - +from pydantic import AnyUrl, BaseModel class Base(BaseModel): class Config: validate_assignment = True copy_on_model_validation = False + allow_population_by_field_name = True def __getattribute__(self, prop): val = super(Base, self).__getattribute__(prop) # __root__ is a custom pydantic thing if hasattr(val, '__root__'): - if type(val.__root__) in [dict, list, float, int]: - return val.__root__ - else: + if type(val.__root__) in [AnyUrl]: # cast it to a string return str(val.__root__) + else: + return val.__root__ else: return val From 21dd8e72f2ae64ef7960ac0b663edb0faddded9d Mon Sep 17 00:00:00 2001 From: Glen Robson Date: Fri, 21 Oct 2022 17:51:30 +0100 Subject: [PATCH 2/9] Returning info.json and adding to Resource --- iiif_prezi3/helpers/set_hwd_from_iiif.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/iiif_prezi3/helpers/set_hwd_from_iiif.py b/iiif_prezi3/helpers/set_hwd_from_iiif.py index 57bd11f..43ef256 100644 --- a/iiif_prezi3/helpers/set_hwd_from_iiif.py +++ b/iiif_prezi3/helpers/set_hwd_from_iiif.py @@ -1,7 +1,7 @@ import requests from ..loader import monkeypatch_schema -from ..skeleton import Canvas +from ..skeleton import Canvas, Resource, ResourceItem class SetHwdFromIIIF: @@ -11,7 +11,8 @@ def set_hwd_from_iiif(self, url): """Set height and width on a Canvas object. Requests IIIF Image information remotely for an - image resource and sets resulting height and width. + image resource and sets resulting height and width. + This method will return the info.json Args: url (str): An HTTP URL for the IIIF image endpoint. @@ -31,5 +32,7 @@ def set_hwd_from_iiif(self, url): resource_info = response.json() self.set_hwd(resource_info.get("height"), resource_info.get("width")) + return resource_info -monkeypatch_schema(Canvas, [SetHwdFromIIIF]) + +monkeypatch_schema([Canvas, Resource, ResourceItem], SetHwdFromIIIF) From 0bc9cb93df2bbcde942db1f4590b40924875e8da Mon Sep 17 00:00:00 2001 From: Glen Robson Date: Fri, 21 Oct 2022 17:52:00 +0100 Subject: [PATCH 3/9] Adding first pass of create canvas from IIIF Image --- iiif_prezi3/helpers/__init__.py | 1 + .../helpers/create_canvas_from_iiif.py | 61 ++++++++ tests/test_create_canvas_from_iiif.py | 130 ++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 iiif_prezi3/helpers/create_canvas_from_iiif.py create mode 100644 tests/test_create_canvas_from_iiif.py diff --git a/iiif_prezi3/helpers/__init__.py b/iiif_prezi3/helpers/__init__.py index 9c5d049..f758f65 100644 --- a/iiif_prezi3/helpers/__init__.py +++ b/iiif_prezi3/helpers/__init__.py @@ -6,6 +6,7 @@ from .auto_fields import * # noqa: F401,F403 from .canvas_helpers import AddImageToCanvas # noqa: F401 from .canvas_sizes import MaxHelper, MinHelper # noqa: F401 +from .create_canvas_from_iiif import CreateCanvasFromIIIF # noqa: F401 from .make_canvas import MakeCanvas # noqa: F401 from .make_collection import MakeCollection # noqa: F401 from .make_manifest import MakeManifest # noqa: F401 diff --git a/iiif_prezi3/helpers/create_canvas_from_iiif.py b/iiif_prezi3/helpers/create_canvas_from_iiif.py new file mode 100644 index 0000000..0325e61 --- /dev/null +++ b/iiif_prezi3/helpers/create_canvas_from_iiif.py @@ -0,0 +1,61 @@ + +from ..loader import monkeypatch_schema +from ..skeleton import (Annotation, AnnotationPage, Canvas, Manifest, + ResourceItem, ServiceItem, ServiceItem1) + + +class CreateCanvasFromIIIF: + # should probably be added to canvas helpers + + def create_canvas_from_iiif(self, url, **kwargs): + """Create a canvas from a IIIF Image URL. + + Creates a canvas from a IIIF Image service passing any + kwargs to the Canvas. Returns a Canvas object + """ + + canvas = Canvas(**kwargs) + + body = ResourceItem(id="http://example.com", type="Image") + infoJson = body.set_hwd_from_iiif(url) + + # Will need to handle IIIF 2... + if 'type' not in infoJson: + # Assume v2 + + # V2 profile contains profile URI plus extra features + profile = '' + for item in infoJson['profile']: + if isinstance(item, str): + profile = item + break + + service = ServiceItem1(_id=infoJson['@id'], profile=profile, _type="ImageService2") + print ('Id type:') + print (type(service._id)) + body.service = [service] + body.id = f'{infoJson["@id"]}/full/full/0/default.jpg' + body.format = "image/jpeg" + else: + service = ServiceItem(id=infoJson['id'], profile=infoJson['profile'], type=infoJson['type']) + body.service = [service] + body.id = f'{infoJson["id"]}/full/max/0/default.jpg' + body.format = "image/jpeg" + + annotation = Annotation(motivation='painting', body=body, target=canvas.id) + + annotationPage = AnnotationPage() + annotationPage.add_item(annotation) + + canvas.add_item(annotationPage) + canvas.set_hwd(infoJson['height'], infoJson['width']) + + return canvas + + def make_canvas_from_iiif(self, url, **kwargs): + canvas = self.create_canvas_from_IIIF(url, **kwargs) + + self.add_item(canvas) + + +monkeypatch_schema(Manifest, [CreateCanvasFromIIIF]) diff --git a/tests/test_create_canvas_from_iiif.py b/tests/test_create_canvas_from_iiif.py new file mode 100644 index 0000000..0b67a3f --- /dev/null +++ b/tests/test_create_canvas_from_iiif.py @@ -0,0 +1,130 @@ +import unittest +from unittest.mock import Mock, patch + +import requests + +from iiif_prezi3 import Canvas, Manifest + + +class CreateCanvasFromIIIFTests(unittest.TestCase): + + def setUp(self): + self.manifest = Manifest(label={'en': ['Manifest label']}) + + @patch('iiif_prezi3.helpers.set_hwd_from_iiif.requests.get') + def test_create_canvas_from_iiif_v3(self, mockrequest_get): + image_id = 'https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen' + image_info_url = f'{image_id}/info.json' + + # successful response with dimensions + mockresponse = Mock(status_code=200) + mockrequest_get.return_value = mockresponse + # set mock to return minimal image api response + mockresponse.json.return_value = { + "@context": "http://iiif.io/api/image/3/context.json", + "extraFormats": ["jpg", "png"], + "extraQualities": ["default", "color", "gray"], + "height": 3024, + "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", + "profile": "level1", + "protocol": "http://iiif.io/api/image", + "tiles": [{ + "height": 512, + "scaleFactors": [1, 2, 4], + "width": 512 + }], + "type": "ImageService3", + "width": 4032 + } + + canvas = self.manifest.create_canvas_from_iiif(image_info_url, label={'en': ['Canvas label']}) + + # Check canvas params made it through + self.assertEqual(canvas.label, + {'en': ['Canvas label']}) + + # check canvas dimensions + self.assertEqual(canvas.height, 3024) + self.assertEqual(canvas.width, 4032) + + # Check annotation + annotation = canvas.items[0].items[0] + self.assertEqual(annotation.motivation, "painting") + + # Check resource + resource = annotation.body + + self.assertEqual(resource.id, f'{image_id}/full/max/0/default.jpg') + self.assertEqual(resource.type, 'Image') + self.assertEqual(resource.format, 'image/jpeg') + self.assertEqual(resource.height, 3024) + self.assertEqual(resource.width, 4032) + + # Check service + service = resource.service[0] + + self.assertEqual(service.id, image_id) + self.assertEqual(service.profile, "level1") + self.assertEqual(service.type, "ImageService3") + + @patch('iiif_prezi3.helpers.set_hwd_from_iiif.requests.get') + def test_create_canvas_from_iiif_v2(self, mockrequest_get): + image_id = 'https://iiif.io/api/image/2.1/example/reference/918ecd18c2592080851777620de9bcb5-gottingen' + image_info_url = f'{image_id}/info.json' + + # successful response with dimensions + mockresponse = Mock(status_code=200) + mockrequest_get.return_value = mockresponse + # set mock to return minimal image api response + mockresponse.json.return_value = { + "@context": "http://iiif.io/api/image/2/context.json", + "@id": "https://iiif.io/api/image/2.1/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", + "height": 3024, + "profile": [ + "http://iiif.io/api/image/2/level1.json", + { + "formats": [ "jpg", "png" ], + "qualities": [ "default", "color", "gray" ] + } + ], + "protocol": "http://iiif.io/api/image", + "tiles": [ + { + "height": 512, + "scaleFactors": [ 1, 2, 4 ], + "width": 512 + } + ], + "width": 4032 + } + + canvas = self.manifest.create_canvas_from_iiif(image_info_url, label={'en': ['Canvas label']}) + + # Check canvas params made it through + self.assertEqual(canvas.label, + {'en': ['Canvas label']}) + + # check canvas dimensions + self.assertEqual(canvas.height, 3024) + self.assertEqual(canvas.width, 4032) + + # Check annotation + annotation = canvas.items[0].items[0] + self.assertEqual(annotation.motivation, "painting") + + # Check resource + resource = annotation.body + + self.assertEqual(resource.id, f'{image_id}/full/full/0/default.jpg') + self.assertEqual(resource.type, 'Image') + self.assertEqual(resource.format, 'image/jpeg') + self.assertEqual(resource.height, 3024) + self.assertEqual(resource.width, 4032) + + # Check service + service = resource.service[0] + + self.assertEqual(service._id, image_id) + self.assertEqual(service.profile, "http://iiif.io/api/image/2/level1.json") + self.assertEqual(service._type, "ImageService2") + From fa7d551194bd01c1045641b26c505a1a5873246c Mon Sep 17 00:00:00 2001 From: Glen Robson Date: Thu, 10 Nov 2022 12:36:52 +0000 Subject: [PATCH 4/9] Fixing basic tests --- tests/test_basic.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index cf2016a..57cdbcc 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -84,10 +84,8 @@ def testLabel(self): """Test setting up a label.""" manifest = Manifest(id='http://iiif.example.org/prezi/Manifest/0', type='Manifest', label={'en': ['default label']}) - print('Manifest label using dict: "{}"'.format(manifest.label)) - - # self.assertTrue('en' in manifest.label, 'Manifest seems to be missing English label') - # self.assertEqual(manifest.label['en'], 'default label', 'Unexpected label for manifest') + self.assertTrue('en' in manifest.label, 'Manifest seems to be missing English label') + self.assertEqual(manifest.label['en'][0], 'default label', 'Unexpected label for manifest') if __name__ == '__main__': From 0af263fae78c7f71fb748007215cf21d8f8ff5a1 Mon Sep 17 00:00:00 2001 From: Glen Robson Date: Thu, 10 Nov 2022 12:39:09 +0000 Subject: [PATCH 5/9] Removing debug statement --- iiif_prezi3/helpers/add_metadata.py | 1 - 1 file changed, 1 deletion(-) diff --git a/iiif_prezi3/helpers/add_metadata.py b/iiif_prezi3/helpers/add_metadata.py index 5b455f1..42affdd 100644 --- a/iiif_prezi3/helpers/add_metadata.py +++ b/iiif_prezi3/helpers/add_metadata.py @@ -30,7 +30,6 @@ def add_metadata(self, label, value): kv = KeyValueString( label=label, value=value ) - print(kv) self.metadata.append(kv) elif type(label) is LngString and type(value) is LngString: From 83a5cb1a21541cd4703f6ff19e8e5d9a55b1f340 Mon Sep 17 00:00:00 2001 From: Glen Robson Date: Fri, 11 Nov 2022 16:47:54 +0000 Subject: [PATCH 6/9] Adding debug --- iiif_prezi3/helpers/create_canvas_from_iiif.py | 1 + 1 file changed, 1 insertion(+) diff --git a/iiif_prezi3/helpers/create_canvas_from_iiif.py b/iiif_prezi3/helpers/create_canvas_from_iiif.py index 0325e61..085b7fc 100644 --- a/iiif_prezi3/helpers/create_canvas_from_iiif.py +++ b/iiif_prezi3/helpers/create_canvas_from_iiif.py @@ -33,6 +33,7 @@ def create_canvas_from_iiif(self, url, **kwargs): service = ServiceItem1(_id=infoJson['@id'], profile=profile, _type="ImageService2") print ('Id type:') print (type(service._id)) + print (service._id.alias) body.service = [service] body.id = f'{infoJson["@id"]}/full/full/0/default.jpg' body.format = "image/jpeg" From 17d37804027ffe3a2615169b47195f56eb43e925 Mon Sep 17 00:00:00 2001 From: Glen Robson Date: Fri, 11 Nov 2022 16:54:03 +0000 Subject: [PATCH 7/9] Using service.id and service.type in anticipation of fix --- iiif_prezi3/helpers/create_canvas_from_iiif.py | 5 +---- tests/test_create_canvas_from_iiif.py | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/iiif_prezi3/helpers/create_canvas_from_iiif.py b/iiif_prezi3/helpers/create_canvas_from_iiif.py index 085b7fc..afdc0db 100644 --- a/iiif_prezi3/helpers/create_canvas_from_iiif.py +++ b/iiif_prezi3/helpers/create_canvas_from_iiif.py @@ -30,10 +30,7 @@ def create_canvas_from_iiif(self, url, **kwargs): profile = item break - service = ServiceItem1(_id=infoJson['@id'], profile=profile, _type="ImageService2") - print ('Id type:') - print (type(service._id)) - print (service._id.alias) + service = ServiceItem1(id=infoJson['@id'], profile=profile, type="ImageService2") body.service = [service] body.id = f'{infoJson["@id"]}/full/full/0/default.jpg' body.format = "image/jpeg" diff --git a/tests/test_create_canvas_from_iiif.py b/tests/test_create_canvas_from_iiif.py index 0b67a3f..f89795a 100644 --- a/tests/test_create_canvas_from_iiif.py +++ b/tests/test_create_canvas_from_iiif.py @@ -124,7 +124,7 @@ def test_create_canvas_from_iiif_v2(self, mockrequest_get): # Check service service = resource.service[0] - self.assertEqual(service._id, image_id) + self.assertEqual(service.id, image_id) self.assertEqual(service.profile, "http://iiif.io/api/image/2/level1.json") - self.assertEqual(service._type, "ImageService2") + self.assertEqual(service.type, "ImageService2") From 418d5dad2fc4481bc1dcab0b9e50575b3a51cad9 Mon Sep 17 00:00:00 2001 From: Glen Robson Date: Fri, 11 Nov 2022 16:54:43 +0000 Subject: [PATCH 8/9] Fixing base and skeleton with service for v2 --- iiif_prezi3/base.py | 6 ++++-- iiif_prezi3/skeleton.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/iiif_prezi3/base.py b/iiif_prezi3/base.py index 29e0b31..67a6d1a 100644 --- a/iiif_prezi3/base.py +++ b/iiif_prezi3/base.py @@ -9,6 +9,8 @@ class Config: validate_all = True copy_on_model_validation = False smart_union = True + # Allow us to use the field name like service.id rather than service.@id + allow_population_by_field_name = True def __getattribute__(self, prop): val = super(Base, self).__getattribute__(prop) @@ -54,9 +56,9 @@ def jsonld(self, **kwargs): pydantic_args = ["include", "exclude", "by_alias", "encoder"] dict_kwargs = dict([(arg, kwargs[arg]) for arg in kwargs.keys() if arg in pydantic_args]) json_kwargs = dict([(arg, kwargs[arg]) for arg in kwargs.keys() if arg not in pydantic_args]) - return json.dumps({"@context": "http://iiif.io/api/presentation/3/context.json", **self.dict(exclude_unset=False, exclude_defaults=False, exclude_none=True, **dict_kwargs)}, **json_kwargs) + return json.dumps({"@context": "http://iiif.io/api/presentation/3/context.json", **self.dict(exclude_unset=False, exclude_defaults=False, exclude_none=True, by_alias=True, **dict_kwargs)}, **json_kwargs) def jsonld_dict(self, **kwargs): pydantic_args = ["include", "exclude", "by_alias", "encoder"] dict_kwargs = dict([(arg, kwargs[arg]) for arg in kwargs.keys() if arg in pydantic_args]) - return {"@context": "http://iiif.io/api/presentation/3/context.json", **self.dict(exclude_unset=False, exclude_defaults=False, exclude_none=True, **dict_kwargs)} + return {"@context": "http://iiif.io/api/presentation/3/context.json", **self.dict(exclude_unset=False, exclude_defaults=False, exclude_none=True, by_alias=True, **dict_kwargs)} diff --git a/iiif_prezi3/skeleton.py b/iiif_prezi3/skeleton.py index a423bc6..e101d5a 100644 --- a/iiif_prezi3/skeleton.py +++ b/iiif_prezi3/skeleton.py @@ -320,8 +320,8 @@ class ServiceItem(Class): class ServiceItem1(Base): - _id: Id = Field(..., alias='@id') - _type: str = Field(..., alias='@type') + id: Id = Field(..., alias='@id') + type: str = Field(..., alias='@type') profile: Optional[str] = None service: Optional[Service] = None From 326da5a58759f329c4d7bab122678bfd06066614 Mon Sep 17 00:00:00 2001 From: Glen Robson Date: Fri, 11 Nov 2022 17:26:38 +0000 Subject: [PATCH 9/9] Linting fixes --- iiif_prezi3/helpers/create_canvas_from_iiif.py | 4 ++-- iiif_prezi3/helpers/set_hwd_from_iiif.py | 4 ++-- tests/test_create_canvas_from_iiif.py | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/iiif_prezi3/helpers/create_canvas_from_iiif.py b/iiif_prezi3/helpers/create_canvas_from_iiif.py index afdc0db..a270176 100644 --- a/iiif_prezi3/helpers/create_canvas_from_iiif.py +++ b/iiif_prezi3/helpers/create_canvas_from_iiif.py @@ -10,10 +10,10 @@ class CreateCanvasFromIIIF: def create_canvas_from_iiif(self, url, **kwargs): """Create a canvas from a IIIF Image URL. - Creates a canvas from a IIIF Image service passing any + Creates a canvas from a IIIF Image service passing any kwargs to the Canvas. Returns a Canvas object - """ + """ canvas = Canvas(**kwargs) body = ResourceItem(id="http://example.com", type="Image") diff --git a/iiif_prezi3/helpers/set_hwd_from_iiif.py b/iiif_prezi3/helpers/set_hwd_from_iiif.py index 43ef256..b1ab792 100644 --- a/iiif_prezi3/helpers/set_hwd_from_iiif.py +++ b/iiif_prezi3/helpers/set_hwd_from_iiif.py @@ -11,8 +11,8 @@ def set_hwd_from_iiif(self, url): """Set height and width on a Canvas object. Requests IIIF Image information remotely for an - image resource and sets resulting height and width. - This method will return the info.json + image resource and sets resulting height and width. + This method will return the info.json Args: url (str): An HTTP URL for the IIIF image endpoint. diff --git a/tests/test_create_canvas_from_iiif.py b/tests/test_create_canvas_from_iiif.py index f89795a..58a638c 100644 --- a/tests/test_create_canvas_from_iiif.py +++ b/tests/test_create_canvas_from_iiif.py @@ -1,9 +1,7 @@ import unittest from unittest.mock import Mock, patch -import requests - -from iiif_prezi3 import Canvas, Manifest +from iiif_prezi3 import Manifest class CreateCanvasFromIIIFTests(unittest.TestCase):