Skip to content

Commit a9373df

Browse files
author
Nicholas Car
committed
Alt Rep Data Model for alt Profile RDF
1 parent e778a34 commit a9373df

9 files changed

+82
-67
lines changed

docs/source/exempler_custom_renderer_example.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def __init__(self, request, instance_uri):
1414
['text/html'] + Renderer.RDF_MEDIA_TYPES,
1515
'text/turtle',
1616
languages=['en', 'pl'],
17-
profile_uri='http://test.linked.data.gov.au/def/mt#'
17+
uri='http://test.linked.data.gov.au/def/mt#'
1818
)
1919
}
2020
super(MediaTypeRenderer, self).__init__(

pyldapi/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# -*- coding: latin-1 -*-
22

3-
from pyldapi.exceptions import ViewsFormatsException, PagingError
3+
from pyldapi.exceptions import ProfilesMediatypesException, PagingError
44
from pyldapi.renderer import Renderer
55
from pyldapi.renderer_container import ContainerRenderer, ContainerOfContainersRenderer
66
from pyldapi.profile import Profile
77
from pyldapi.helpers import setup
88

9-
__version__ = '3.5'
9+
__version__ = '3.6'
1010

1111
__all__ = [
1212
'Renderer',
1313
'ContainerRenderer',
1414
'ContainerOfContainersRenderer',
1515
'Profile',
16-
'ViewsFormatsException',
16+
'ProfilesMediatypesException',
1717
'PagingError',
1818
'setup',
1919
'__version__'

pyldapi/exceptions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33

4-
class ViewsFormatsException(ValueError):
4+
class ProfilesMediatypesException(ValueError):
55
"""
66
TODO: Ashley add docstring for documentation
77
"""

pyldapi/profile.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ class Profile:
1616
"""
1717
def __init__(
1818
self,
19+
uri,
1920
label,
2021
comment,
2122
mediatypes,
2223
default_mediatype,
2324
languages=None,
2425
default_language='en',
25-
profile_uri=None
2626
):
2727
"""
2828
Constructor
@@ -39,13 +39,13 @@ def __init__(
3939
:type languages: list
4040
:param default_language: The default language, by default it is 'en' English.
4141
:type default_language: str
42-
:param profile_uri: The namespace URI for the *profile* view.
43-
:type profile_uri: str
42+
:param uri: The namespace URI for the *profile* view.
43+
:type uri: str
4444
"""
4545
self.label = label
4646
self.comment = comment
4747
self.mediatypes = mediatypes
4848
self.default_mediatype = default_mediatype
4949
self.languages = languages if languages is not None else ['en']
5050
self.default_language = default_language
51-
self.namespace = profile_uri
51+
self.uri = uri

pyldapi/renderer.py

+62-47
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from rdflib import Graph, Namespace, URIRef, BNode, Literal, RDF, RDFS, XSD
66
from rdflib.namespace import DCTERMS
77
from pyldapi.profile import Profile
8-
from pyldapi.exceptions import ViewsFormatsException
8+
from pyldapi.exceptions import ProfilesMediatypesException
99
import re
1010
import connegp
1111

@@ -73,12 +73,12 @@ def __init__(self,
7373
self.profiles = profiles
7474
# auto-add in an Alternates profile
7575
self.profiles['alt'] = Profile(
76+
'http://www.w3.org/ns/dx/conneg/altr', # the ConnegP URI for Alt Rep Data Model
7677
'Alternate Representations',
7778
'The representation of the resource that lists all other representations (profiles and Media Types)',
7879
['text/html', 'application/json'] + self.RDF_MEDIA_TYPES,
7980
'text/html',
8081
languages=['en'], # default 'en' only for now
81-
profile_uri='http://www.w3.org/ns/dx/conneg/altr' # the ConnegP URI for RRD Functional Profile
8282
)
8383
self.profile = None
8484

@@ -105,7 +105,7 @@ def __init__(self,
105105
# make headers only if there's no error
106106
if self.vf_error is None:
107107
self.headers = dict()
108-
self.headers['Content-Profile'] = '<' + self.profiles[self.profile].namespace + '>'
108+
self.headers['Content-Profile'] = '<' + self.profiles[self.profile].uri + '>'
109109
self.headers['Content-Type'] = self.mediatype
110110
self.headers['Content-Language'] = self.language
111111

@@ -139,8 +139,8 @@ def _get_profiles_from_qsa(self):
139139
for profile in pqsa.profiles:
140140
if profile['profile'].startswith('<'):
141141
# convert this valid URI/URN to a token
142-
for token, view in self.profiles.items():
143-
if view.namespace == profile['profile'].strip('<>'):
142+
for token, profile in self.profiles.items():
143+
if profile.uri == profile['profile'].strip('<>'):
144144
profiles.append(token)
145145
else:
146146
# it's already a token so just add it
@@ -164,8 +164,8 @@ def _get_profiles_from_http(self):
164164
profiles = []
165165
for profile in ap.profiles:
166166
# convert this valid URI/URN to a token
167-
for token, view in self.profiles.items():
168-
if view.namespace == profile['profile']:
167+
for token, profile in self.profiles.items():
168+
if profile.uri == profile['profile']:
169169
profiles.append(token)
170170
if len(profiles) == 0:
171171
return None
@@ -175,14 +175,14 @@ def _get_profiles_from_http(self):
175175
return None
176176
except Exception:
177177
msg = 'You have requested a profile using an Accept-Profile header that is incorrectly formatted.'
178-
raise ViewsFormatsException(msg)
178+
raise ProfilesMediatypesException(msg)
179179
else:
180180
return None
181181

182182
def _get_available_profiles(self):
183183
uris = {}
184-
for token, view in self.profiles.items():
185-
uris[view.namespace] = token
184+
for token, profile in self.profiles.items():
185+
uris[profile.uri] = token
186186

187187
return uris
188188

@@ -199,7 +199,7 @@ def _get_profile(self):
199199
return self.default_profile_token
200200

201201
# if we have a result from QSA or HTTP, got through each in order and see if there's an available
202-
# view for that token, return first one
202+
# profile for that token, return first one
203203
profiles_available = self._get_available_profiles()
204204
for profile in profiles_requested:
205205
for k, v in profiles_available.items():
@@ -254,7 +254,7 @@ def _get_mediatypes_from_http(self):
254254
# return only the orderd list of mediatypes, not weights
255255
return[x[1] for x in mediatypes]
256256
except Exception:
257-
raise ViewsFormatsException(
257+
raise ProfilesMediatypesException(
258258
'You have requested a Media Type using an Accept header that is incorrectly formatted.')
259259

260260
return None
@@ -321,7 +321,7 @@ def _get_languages_from_http(self):
321321
# return only the orderd list of languages, not weights
322322
return[x[1] for x in languages]
323323
except Exception:
324-
raise ViewsFormatsException(
324+
raise ProfilesMediatypesException(
325325
'You have requested a language using an Accept-Language header that is incorrectly formatted.')
326326

327327
return None
@@ -356,17 +356,17 @@ def _make_header_link_tokens(self):
356356
individual_links = []
357357
link_header_template = '<http://www.w3.org/ns/dx/prof/Profile>; rel="type"; token="{}"; anchor=<{}>, '
358358

359-
for token, view in self.profiles.items():
360-
individual_links.append(link_header_template.format(token, view.namespace))
359+
for token, profile in self.profiles.items():
360+
individual_links.append(link_header_template.format(token, profile.uri))
361361

362362
return ''.join(individual_links).rstrip(', ')
363363

364364
def _make_header_link_list_profiles(self):
365365
individual_links = []
366-
for token, view in self.profiles.items():
366+
for token, profile in self.profiles.items():
367367
# create an individual Link statement per Media Type
368-
for mediatype in view.mediatypes:
369-
# set the rel="self" just for this view & mediatype
368+
for mediatype in profile.mediatypes:
369+
# set the rel="self" just for this profile & mediatype
370370
if mediatype != '_internal':
371371
if token == self.default_profile_token and mediatype == self.profiles[self.profile].default_mediatype:
372372
rel = 'self'
@@ -380,7 +380,7 @@ def _make_header_link_list_profiles(self):
380380
mediatype,
381381
rel,
382382
mediatype,
383-
view.namespace)
383+
profile.uri)
384384
)
385385

386386
# append to, or create, Link header
@@ -392,31 +392,46 @@ def _make_header_link_list_profiles(self):
392392
# making response content
393393
#
394394
def _generate_alt_profiles_rdf(self):
395+
# Alt R Data Model as per https://www.w3.org/TR/dx-prof-conneg/#altr
395396
g = Graph()
396-
ALT = Namespace('http://w3id.org/profile/alt#')
397-
g.bind('alt', ALT)
397+
ALTR = Namespace('http://www.w3.org/ns/dx/conneg/altr#')
398+
g.bind('altr', ALTR)
398399

399400
g.bind('dct', DCTERMS)
400401

401402
PROF = Namespace('http://www.w3.org/ns/prof/')
402403
g.bind('prof', PROF)
403404

404-
for token, v in self.profiles.items():
405-
v_node = BNode()
406-
g.add((v_node, RDF.type, ALT.View))
407-
g.add((v_node, PROF.token, Literal(token, datatype=XSD.token)))
408-
g.add((v_node, RDFS.label, Literal(v.label, datatype=XSD.string)))
409-
g.add((v_node, RDFS.comment, Literal(v.comment, datatype=XSD.string)))
410-
for f in v.mediatypes:
411-
if not str(f).startswith('_'): # ignore mediatypes like `_internal`
412-
g.add((v_node, URIRef(DCTERMS + 'format'), Literal(f)))
413-
g.add((v_node, ALT.hasDefaultFormat, Literal(v.default_mediatype, datatype=XSD.string)))
414-
if v.namespace is not None:
415-
g.add((v_node, DCTERMS.conformsTo, URIRef(v.namespace)))
416-
g.add((URIRef(self.instance_uri), ALT.view, v_node))
417-
418-
if self.default_profile_token == token:
419-
g.add((URIRef(self.instance_uri), ALT.hasDefaultView, v_node))
405+
instance_uri = URIRef(self.instance_uri)
406+
407+
# for each Profile, lis it via its URI and give annotations
408+
for token, p in self.profiles.items():
409+
profile_uri = URIRef(p.uri)
410+
g.add((profile_uri, RDF.type, PROF.Profile))
411+
g.add((profile_uri, PROF.token, Literal(token, datatype=XSD.token)))
412+
g.add((profile_uri, RDFS.label, Literal(p.label, datatype=XSD.string)))
413+
g.add((profile_uri, RDFS.comment, Literal(p.comment, datatype=XSD.string)))
414+
415+
# for each Profile and Media Type, create a Representation
416+
for token, p in self.profiles.items():
417+
for mt in p.mediatypes:
418+
if not str(mt).startswith('_'): # ignore Media Types like `_internal`
419+
rep = BNode()
420+
g.add((rep, RDF.type, ALTR.Representation))
421+
g.add((rep, DCTERMS.conformsTo, URIRef(p.uri)))
422+
g.add((rep, URIRef(DCTERMS + 'format'), Literal(mt)))
423+
424+
# if this is the default format for the Profile, say so
425+
if mt == p.default_mediatype:
426+
g.add((rep, ALTR.isProfilesDefault, Literal(True, datatype=XSD.boolean)))
427+
428+
# link this representation to the instances
429+
g.add((instance_uri, ALTR.hasRepresentation, rep))
430+
431+
# if this is the default Profile and the default Media Type, set it as the instance's default Rep
432+
if token == self.default_profile_token and mt == p.default_mediatype:
433+
g.add((instance_uri, ALTR.hasDefaultRepresentation, rep))
434+
420435
return g
421436

422437
def _make_rdf_response(self, graph, mimetype=None, headers=None, delete_graph=True):
@@ -453,14 +468,14 @@ def _make_rdf_response(self, graph, mimetype=None, headers=None, delete_graph=Tr
453468

454469
def _render_alt_profile_html(self, template_context=None):
455470
profiles = {}
456-
for token, v in self.profiles.items():
471+
for token, profile in self.profiles.items():
457472
profiles[token] = {
458-
'label': str(v.label), 'comment': str(v.comment),
459-
'mediatypes': tuple(f for f in v.mediatypes if not f.startswith('_')),
460-
'default_mediatype': str(v.default_mediatype),
461-
'languages': v.languages if v.languages is not None else ['en'],
462-
'default_language': str(v.default_language),
463-
'namespace': str(v.namespace)
473+
'label': str(profile.label), 'comment': str(profile.comment),
474+
'mediatypes': tuple(f for f in profile.mediatypes if not f.startswith('_')),
475+
'default_mediatype': str(profile.default_mediatype),
476+
'languages': profile.languages if profile.languages is not None else ['en'],
477+
'default_language': str(profile.default_language),
478+
'uri': str(profile.uri)
464479
}
465480
_template_context = {
466481
'uri': self.instance_uri,
@@ -485,7 +500,7 @@ def _render_alt_profile_json(self):
485500
return Response(
486501
json.dumps({
487502
'uri': self.instance_uri,
488-
'views': list(self.profiles.keys()),
503+
'profiles': list(self.profiles.keys()),
489504
'default_profile': self.default_profile_token
490505
}),
491506
mimetype='application/json',
@@ -510,10 +525,10 @@ def _render_alt_profile(self):
510525

511526
def render(self):
512527
"""
513-
Use the received view and mediatype to create a response back to the client.
528+
Use the received profile and mediatype to create a response back to the client.
514529
515530
TODO: Ashley, are you able to update this description with your new changes please?
516-
What is the method for rendering other views now? - Edmond
531+
What is the method for rendering other profiles now? - Edmond
517532
518533
This is an abstract method.
519534

pyldapi/renderer_container.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import json
88
from pyldapi.renderer import Renderer
99
from pyldapi.profile import Profile
10-
from pyldapi.exceptions import ViewsFormatsException, CofCTtlError
10+
from pyldapi.exceptions import ProfilesMediatypesException, CofCTtlError
1111

1212

1313
class ContainerRenderer(Renderer):
@@ -71,16 +71,16 @@ def __init__(self,
7171
profiles = {}
7272
for k, v in profiles.items():
7373
if k == 'mem':
74-
raise ViewsFormatsException(
74+
raise ProfilesMediatypesException(
7575
'You must not manually add a profile with token \'mem\' as this is auto-created'
7676
)
7777
profiles.update({
7878
'mem': Profile(
79+
'https://w3id.org/profile/mem',
7980
'Members Profile',
8081
'A very basic RDF data model-only profile that lists the sub-items (members) of collections (rdf:Bag)',
8182
['text/html'] + Renderer.RDF_MEDIA_TYPES,
82-
'text/html',
83-
profile_uri='https://w3id.org/profile/mem'
83+
'text/html'
8484
)
8585
})
8686
if default_profile_token is None:

tests/example.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
'A profile of my pet.',
4545
['text/html', 'application/json'],
4646
'text/html',
47-
profile_uri='http://example.org/def/mypetprofile')
47+
uri='http://example.org/def/mypetprofile')
4848

4949
app = Flask(__name__)
5050

tests/test_conneg_by_p.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@ def setup():
2121
'A profile of organisations according to the Australian Government Organisations Register',
2222
['text/html'] + Renderer.RDF_MEDIA_TYPES,
2323
'text/turtle',
24-
profile_uri='http://linked.data.gov.au/def/agor'
24+
uri='http://linked.data.gov.au/def/agor'
2525
),
2626
'fake': Profile(
2727
'Fake Profile',
2828
'A fake Profile for testing',
2929
['text/xml'],
3030
'text/xml',
31-
profile_uri='http://fake.com'
31+
uri='http://fake.com'
3232
),
3333
'other': Profile(
3434
'Another Testing Profile',
3535
'Another profile for testing',
3636
['text/html', 'text/xml'],
3737
'text/html',
38-
profile_uri='http://other.com'
38+
uri='http://other.com'
3939
)
4040
# 'alternates' # included by default
4141
# 'all' # included by default

0 commit comments

Comments
 (0)