Skip to content

Commit

Permalink
ImageInfo/auth_rules updates
Browse files Browse the repository at this point in the history
1. ImageInfo - rename extra to auth_rules
2. Don't process logo/attribution/... out of extra["extraInfo"], but
allow passing them into ImageInfo as parameters.
3. In the SimpleHttpResolver, only try to fetch rules.json if
use_auth_rules is True.
4. In resolvers, use_auth_rules is now False by default (this will
require anyone using rules.json to update their configuration).
  • Loading branch information
bcail committed Jan 2, 2020
1 parent ac6e1b3 commit 66977fe
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 84 deletions.
34 changes: 13 additions & 21 deletions loris/img_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,22 @@ def default(self, obj):

class ImageInfo(JP2Extractor):
'''Info about the image.
See: <http://iiif.io/api/image/>
See: <http://iiif.io/api/image/>, <https://iiif.io/api/image/2.1/#complete-response>
Slots:
width (int)
height (int)
scaleFactors [(int)]
sizes [(str)]: the optimal sizes of the image to request
tiles: [{}]
service (dict): services associated with the image
profile (Profile): Features supported by the server/available for
this image
service [{}]: optional - services associated with the image
attribution [{}]: optional - text that must be shown when content obtained from the Image API service is
displayed or used
license []: optional - link to an external resource that describes the license or rights statement under which
content obtained from the Image API service may be used
logo {}: optional - small image that represents an individual or organization associated with the content
src_img_fp (str): the absolute path on the file system [non IIIF]
src_format (str): the format of the source image file [non IIIF]
Expand All @@ -80,27 +85,14 @@ class ImageInfo(JP2Extractor):
'attribution', 'logo', 'license', 'auth_rules',
'src_format', 'src_img_fp', 'color_profile_bytes')

def __init__(self, app=None, src_img_fp="", src_format="", extra={}):
def __init__(self, app=None, src_img_fp="", src_format="", attribution=None, logo=None, license=None, service=None, auth_rules=None):
self.src_img_fp = src_img_fp
self.src_format = src_format
self.attribution = None
self.logo = None
self.license = None
self.service = {}
self.auth_rules = extra

# The extraInfo parameter can be used to override specific attributes.
# If there are extra attributes, drop an error.
bad_attrs = []
for (k, v) in extra.get('extraInfo', {}).items():
try:
setattr(self, k, v)
except AttributeError:
bad_attrs.append(k)
if bad_attrs:
raise ImageInfoException(
"Invalid parameters in extraInfo: %s." % ', '.join(bad_attrs)
)
self.attribution = attribution
self.logo = logo
self.license = license
self.service = service or {}
self.auth_rules = auth_rules or {}

# If constructed from JSON, the pixel info will already be processed
if app:
Expand Down
61 changes: 28 additions & 33 deletions loris/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
logger = getLogger(__name__)


class _AbstractResolver(object):
class _AbstractResolver:

def __init__(self, config):
self.config = config
if config:
self.auth_rules_ext = self.config.get('auth_rules_ext', 'rules.json')
self.use_extra_info = self.config.get('use_extra_info', True)
self.use_auth_rules = self.config.get('use_auth_rules', False)

def is_resolvable(self, ident):
"""
Expand Down Expand Up @@ -65,12 +65,10 @@ def resolve(self, app, ident, base_uri):
cn = self.__class__.__name__
raise NotImplementedError('resolve() not implemented for %s' % (cn,))

def get_extra_info(self, ident, source_fp):
def get_auth_rules(self, ident, source_fp):
"""
Given the identifier and any resolved source file, find the associated
extra information to include in info.json, plus any additional authorizer
specific information. It might end up there after being copied by a
caching implementation, or live there permanently.
Given the identifier and any resolved source file (ie. on the filesystem), grab the associated
authentication rules from the filesystem (if they exist).
Args:
ident (str):
Expand All @@ -80,11 +78,12 @@ def get_extra_info(self, ident, source_fp):
Returns:
dict: The dict of information to embed in info.json
"""
if not self.use_auth_rules:
return {}
xjsfp = source_fp.rsplit('.', 1)[0] + "." + self.auth_rules_ext
if exists(xjsfp):
fh = open(xjsfp)
xjs = json.load(fh)
fh.close()
with open(xjsfp) as fh:
xjs = json.load(fh)
return xjs
else:
return {}
Expand Down Expand Up @@ -130,14 +129,13 @@ def is_resolvable(self, ident):
return not self.source_file_path(ident) is None

def resolve(self, app, ident, base_uri):

if not self.is_resolvable(ident):
self.raise_404_for_ident(ident)

source_fp = self.source_file_path(ident)
format_ = self.format_from_ident(ident)
extra = self.get_extra_info(ident, source_fp)
return ImageInfo(app, source_fp, format_, extra)
auth_rules = self.get_auth_rules(ident, source_fp)
return ImageInfo(app, source_fp, format_, auth_rules=auth_rules)


class ExtensionNormalizingFSResolver(SimpleFSResolver):
Expand Down Expand Up @@ -347,19 +345,19 @@ def copy_to_cache(self, ident):
# These files are < 2k in size, so fetch in one go.
# Assumes that the rules will be next to the image
# cache_dir is image specific, so this is easy

bits = split(source_url)
fn = bits[1].rsplit('.', 1)[0] + "." + self.auth_rules_ext
rules_url = bits[0] + '/' + fn
try:
resp = requests.get(rules_url)
if resp.status_code == 200:
local_rules_fp = join(cache_dir, "loris_cache." + self.auth_rules_ext)
if not exists(local_rules_fp):
with open(local_rules_fp, 'w') as fh:
fh.write(resp.text)
except requests.exceptions.RequestException:
pass
if self.use_auth_rules:
bits = split(source_url)
fn = bits[1].rsplit('.', 1)[0] + "." + self.auth_rules_ext
rules_url = bits[0] + '/' + fn
try:
resp = requests.get(rules_url)
if resp.status_code == 200:
local_rules_fp = join(cache_dir, "loris_cache." + self.auth_rules_ext)
if not exists(local_rules_fp):
with open(local_rules_fp, 'w') as fh:
fh.write(resp.text)
except requests.exceptions.RequestException:
pass

return local_fp

Expand All @@ -368,11 +366,8 @@ def resolve(self, app, ident, base_uri):
if not cached_file_path:
cached_file_path = self.copy_to_cache(ident)
format_ = self.get_format(cached_file_path, None)
if self.use_extra_info:
extra = self.get_extra_info(ident, cached_file_path)
else:
extra = {}
return ImageInfo(app, cached_file_path, format_, extra)
auth_rules = self.get_auth_rules(ident, cached_file_path)
return ImageInfo(app, cached_file_path, format_, auth_rules=auth_rules)


class TemplateHTTPResolver(SimpleHTTPResolver):
Expand Down Expand Up @@ -553,5 +548,5 @@ def resolve(self, app, ident, base_uri):

cache_fp = self.cache_file_path(ident)
format_ = self.format_from_ident(ident)
extra = self.get_extra_info(ident, cache_fp)
return ImageInfo(app, cache_fp, format_, extra)
auth_rules = self.get_auth_rules(ident, cache_fp)
return ImageInfo(app, cache_fp, format_, auth_rules=auth_rules)
29 changes: 4 additions & 25 deletions tests/img_info_t.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,45 +262,24 @@ def test_info_from_json(self):
self.assertEqual(info.tiles, self.test_jp2_color_tiles)
self.assertEqual(info.sizes, self.test_jp2_color_sizes)

def test_extrainfo_appears_in_iiif_json(self):
def test_rights_licensing_properties_in_iiif_json(self):
info = ImageInfo(
src_img_fp=self.test_jpeg_fp,
src_format=self.test_jpeg_fmt,
extra={'extraInfo': {
'license': 'CC-BY',
'logo': 'logo.png',
'service': {'@id': 'my_service'},
'attribution': 'Author unknown',
}}
license='CC-BY',
logo='logo.png',
attribution='Author unknown',
)
info.from_image_file()

iiif_json = json.loads(info.to_iiif_json(base_uri='http://localhost/1234'))
assert iiif_json['license'] == 'CC-BY'
assert iiif_json['logo'] == 'logo.png'
assert iiif_json['service'] == {'@id': 'my_service'}
assert iiif_json['attribution'] == 'Author unknown'


class TestImageInfo:

def test_extrainfo_can_override_attributes(self):
info = ImageInfo(extra={'extraInfo': {
'license': 'CC-BY',
'logo': 'logo.png',
'service': {'@id': 'my_service'},
'attribution': 'Author unknown',
}})
assert info.license == 'CC-BY'
assert info.logo == 'logo.png'
assert info.service == {'@id': 'my_service'}
assert info.attribution == 'Author unknown'

def test_invalid_extra_info_is_imageinfoexception(self):
with pytest.raises(ImageInfoException) as exc:
ImageInfo(extra={'extraInfo': {'foo': 'bar', 'baz': 'bat'}})
assert 'Invalid parameters in extraInfo' in str(exc.value)

@pytest.mark.parametrize('src_format', ['', None, 'imgX'])
def test_invalid_src_format_is_error(self, src_format):
info = ImageInfo(src_format=src_format)
Expand Down
5 changes: 3 additions & 2 deletions tests/resolver_t.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,11 @@ def test_get_extra_info(self):
with open(os.path.join(tmp, rules_filename), 'wb') as f:
f.write(json.dumps({'rule': 1}).encode('utf8'))
config = {
'src_img_roots' : [tmp]
'src_img_roots' : [tmp],
'use_auth_rules': True
}
resolver = SimpleFSResolver(config=config)
assert resolver.get_extra_info(ident, source_fp) == {'rule': 1}
assert resolver.get_auth_rules(ident, source_fp) == {'rule': 1}


class Test_SourceImageCachingResolver(loris_t.LorisTest):
Expand Down
6 changes: 3 additions & 3 deletions tests/simple_http_resolver_ut.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def test_config_assigned_to_resolver(self):
'uri_resolvable': True,
'user': 'TestUser',
'pw': 'TestPW',
'use_extra_info': False,
'use_auth_rules': True,
}

resolver = SimpleHTTPResolver(config)
Expand All @@ -186,7 +186,7 @@ def test_config_assigned_to_resolver(self):
self.assertEqual(resolver.uri_resolvable, True)
self.assertEqual(resolver.user, 'TestUser')
self.assertEqual(resolver.pw, 'TestPW')
self.assertEqual(resolver.use_extra_info, False)
self.assertEqual(resolver.use_auth_rules, True)

def test_barebones_config(self):
config = {
Expand All @@ -203,4 +203,4 @@ def test_barebones_config(self):
self.assertEqual(resolver.uri_resolvable, True)
self.assertEqual(resolver.user, None)
self.assertEqual(resolver.pw, None)
self.assertEqual(resolver.use_extra_info, True)
self.assertEqual(resolver.use_auth_rules, False)

0 comments on commit 66977fe

Please sign in to comment.