def _get_languages_from_http(self): """ Reads an Accept HTTP header and returns an array of Media Type string in descending weighted order :return: List of URIs of accept profiles in descending request order :rtype: list """ if hasattr(self.request, 'headers'): if self.request.headers.get('Accept-Language') is not None: try: # split the header into individual URIs, with weights still attached languages = self.request.headers['Accept-Language'].split(',') # remove \s languages = [x.strip() for x in languages] # split off any weights and sort by them with default weight = 1 languages = [ (float(x.split(';')[1].replace('q=', '')) if len(x.split(';')) == 2 else 1, x.split(';')[0]) for x in languages ] # sort profiles by weight, heaviest first languages.sort(reverse=True) # return only the orderd list of languages, not weights return[x[1] for x in languages] except Exception: raise ViewsFormatsException( 'You have requested a language using an Accept-Language header that is incorrectly formatted.') return None
def _get_accept_profiles_in_order(self): """ Reads an Accept-Profile HTTP header and returns an array of Profile URIs in descending weighted order :return: List of URIs of accept profiles in descending request order :rtype: list """ try: # split the header into individual URIs, with weights still attached profiles = self.request.headers['Accept-Profile'].split(',') # remove <, >, and \s profiles = [ x.replace('<', '').replace('>', '').replace(' ', '').strip() for x in profiles ] # split off any weights and sort by them with default weight = 1 profiles = [(float(x.split(';')[1].replace('q=', '')) if len( x.split(';')) == 2 else 1, x.split(';')[0]) for x in profiles] # sort profiles by weight, heaviest first profiles.sort(reverse=True) return [x[1] for x in profiles] except Exception as e: raise ViewsFormatsException( 'You have requested a profile using an Accept-Profile header that is incorrectly formatted.' )
def _get_mediatypes_from_http(self): """Returns a list of Media Type tokens from an Accept HTTP header in descending weighted order :return: List of URIs of accept profiles in descending request order :rtype: list """ if hasattr(self.request, 'headers'): if self.request.headers.get('Accept') is not None: try: # Chrome breaking Accept header variable by adding v=b3 # Issue https://github.com/RDFLib/pyLDAPI/issues/21 mediatypes_string = re.sub('v=(.*);', '', self.request.headers['Accept']) # split the header into individual URIs, with weights still attached mediatypes = mediatypes_string.split(',') # remove \s mediatypes = [x.strip() for x in mediatypes] # split off any weights and sort by them with default weight = 1 mediatypes = [ (float(x.split(';')[1].replace('q=', '')) if ";q=" in x else 1, x.split(';')[0]) for x in mediatypes ] # sort profiles by weight, heaviest first mediatypes.sort(reverse=True) # return only the orderd list of mediatypes, not weights return[x[1] for x in mediatypes] except Exception: raise ViewsFormatsException( 'You have requested a Media Type using an Accept header that is incorrectly formatted.') return None
def _get_profiles_from_http(self): """ Reads an Accept-Profile HTTP header and returns a list of Profile tokens in descending weighted order Ref: https://www.w3.org/TR/dx-prof-conneg/#http-getresourcebyprofile :return: List of URIs of accept profiles in descending request order :rtype: list """ if self.request.headers.get('Accept-Profile') is not None: try: ap = connegp.AcceptProfileHeaderParser(self.request.headers.get('Accept-Profile')) if ap.valid: profiles = [] for profile in ap.profiles: # convert this valid URI/URN to a token for token, view in self.profiles.items(): if view.namespace == profile['profile']: profiles.append(token) if len(profiles) == 0: return None else: return profiles else: return None except Exception: msg = 'You have requested a profile using an Accept-Profile header that is incorrectly formatted.' raise ViewsFormatsException(msg) else: return None
def _get_requested_view(self): # if a particular _view is requested, if it's available, return it # the _view selector, coming first (before profile neg) will override profile neg, if both are set # if nothing is set, return default view (not HTTP 406) if self.request.values.get('_view') is not None: if self.views.get(self.request.values.get('_view')) is not None: return self.request.values.get('_view') else: raise ViewsFormatsException( 'The requested view is not available for the resource for which it was requested' ) elif hasattr(self.request, 'headers'): if self.request.headers.get('Accept-Profile') is not None: h = self._get_best_accept_profile() return h if h is not None else self.default_view_token return self.default_view_token
def _get_accept_profiles_in_order(self): """ Reads an Accept-Profile HTTP header and returns an array of Profile URIs in descending weighted order :return: List of URIs of accept profiles in descending request order :rtype: list """ try: # split the header into individual URIs, with weights still attached profiles = self.request.headers['Accept-Profile'].split(',') # remove <, >, and \s profiles = [ x.replace('<', '').replace('>', '').replace(' ', '').strip() for x in profiles ] # split off any weights and sort by them with default weight = 1 weighted_profiles = [] for mtype in profiles: mtype_parts = iter(mtype.split(";")) mimetype = next(mtype_parts) try: qweight = 0.0 while True: part = next(mtype_parts) if part.startswith("q="): qweight = float(part.replace("q=", "")) break except StopIteration: qweight = 1.0 weighted_profiles.append((qweight, mimetype)) # sort profiles by weight, heaviest first weighted_profiles.sort(reverse=True) return [x[1] for x in weighted_profiles] except Exception as e: raise ViewsFormatsException( 'You have requested a profile using an Accept-Profile header that is incorrectly formatted.' )
def _get_requested_view(self): # if a particular _view is requested, if it's available, return it # the _view selector, coming first (before profile neg) will override profile neg, if both are set # if nothing is set, return default view (not HTTP 406) query_view = self.request.values.get('_view', None) if query_view is not None: requested_view = str(query_view).replace(' ', '+') if requested_view == "_internal": return requested_view if self.views.get(requested_view, None) is not None: return requested_view else: # TODO: determine whether or not to # silently return default view raise ViewsFormatsException( 'The requested view is not available for the resource for which it was requested') elif hasattr(self.request, 'headers'): if self.request.headers.get('Accept-Profile') is not None: h = self._get_best_accept_profile() return h if h is not None else self.default_view_token return self.default_view_token
def __init__(self, request, uri, views, default_view_token, alternates_template=None): """ Init function for class :param request: the Flask request object that triggered this class object creation :type request: Flask request object :param uri: the URI called that triggered this API endpoint (can be via redirects but the main URI is needed) :type uri: string :param views: a list of views available for this resource :type views: list (of View class objects) :param default_view_token: the ID of the default view (key of a view in the list of Views) :type default_view_token: string (key in views) :param alternates_template: the jinja template to use for rendering the HTML alternates view :type alternates_template: string | None """ self.request = request self.uri = uri # ensure alternates isn't hogged by user for k, v in views.items(): if k == 'alternates': raise ViewsFormatsException( 'You must not manually add a view with token \'alternates\' as this is auto-created' ) self.views = views # ensure that the default view is actually a given view if default_view_token not in self.views.keys(): raise ViewsFormatsException( 'The view token you specified for the default view is not in the list of views you supplied' ) self.default_view_token = default_view_token self.alternates_template = alternates_template # auto-add in an Alternates view self.views['alternates'] = View( 'Alternates', 'The view that lists all other views', ['text/html', 'application/json', '_internal'] + self.RDF_MIMETYPES, 'text/html', languages=['en'], # default 'en' only for now namespace='https://promsns.org/def/alt') # get view & format for this request, flag any errors but do not except out try: self.view = self._get_requested_view() try: self.format = self._get_requested_format() if self.format is None: self.format = self.views[self.view].default_format self.language = self._get_requested_language() if self.language is None: self.language = self.views[self.view].default_language except ViewsFormatsException as e: print(e) self.vf_error = str(e) except ViewsFormatsException as e: print(e) self.vf_error = str(e) self.headers = dict()
def __init__(self, request, uri, views, default_view_token, alternates_template=None): """ Constructor :param request: Flask request object that triggered this class object's creation. :type request: :class:`flask.request` :param uri: The URI that triggered this API endpoint (can be via redirects but the main URI is needed). :type uri: str :param views: A dictionary of views available for this resource. :type views: dict (of :class:`.View` class objects) :param default_view_token: The ID of the default view (key of a view in the dictionary of :class:`.View` objects) :type default_view_token: str (a key in views) :param alternates_template: The Jinja2 template to use for rendering the HTML *alternates view*. If None, then it will default to try and use a template called :code:`alternates.html`. :type alternates_template: str .. seealso:: See the :class:`.View` class on how to create a dictionary of views. """ self.request = request self.uri = uri # ensure alternates isn't hogged by user for k, v in views.items(): if k == 'alternates': raise ViewsFormatsException( 'You must not manually add a view with token \'alternates\' as this is auto-created' ) self.views = views # ensure that the default view is actually a given view if default_view_token not in self.views.keys(): raise ViewsFormatsException( 'The view token you specified for the default view is not in the list of views you supplied' ) self.default_view_token = default_view_token self.alternates_template = alternates_template # auto-add in an Alternates view self.views['alternates'] = View( 'Alternates', 'The view that lists all other views', ['text/html', 'application/json', '_internal'] + self.RDF_MIMETYPES, 'text/html', languages=['en'], # default 'en' only for now namespace='https://promsns.org/def/alt') # get view & format for this request, flag any errors but do not except out try: self.view = self._get_requested_view() try: self.format = self._get_requested_format() if self.format is None: self.format = self.views[self.view].default_format self.language = self._get_requested_language() if self.language is None: self.language = self.views[self.view].default_language except ViewsFormatsException as e: print(e) self.vf_error = str(e) except ViewsFormatsException as e: print(e) self.vf_error = str(e) self.headers = dict()
def __init__(self, request, uri, label, comment, register_items, contained_item_classes, register_total_count, *args, views=None, default_view_token=None, super_register=None, page_size_max=1000, register_template=None, **kwargs): """ Constructor :param request: The Flask request object triggering this class object's creation. :type request: :class:`.flask.request` :param uri: The URI requested. :type uri: str :param label: The label of the Register. :type label: str :param comment: A description of the Register. :type comment: str :param register_items: The items within this register as a list of URI strings or tuples with string elements like (URI, label). They can also be tuples like (URI, URI, label) if you want to manually specify an item's class. :type register_items: list :param contained_item_classes: The list of URI strings of each distinct class of item contained in this Register. :type contained_item_classes: list :param register_total_count: The total number of items in this Register (not of a page but the register as a whole). :type register_total_count: int :param views: A dictionary of named :class:`.View` objects available for this Register, apart from 'reg' which is auto-created. :type views: dict :param default_view_token: The ID of the default :class:`.View` (key of a view in the list of Views). :type default_view_token: str :param super_register: A super-Register URI for this register. Can be within this API or external. :type super_register: str :param register_template: The Jinja2 template to use for rendering the HTML view of the register. If None, then it will default to try and use a template called :code:`alternates.html`. :type register_template: str or None """ if views is None: views = {} for k, v in views.items(): if k == 'reg': raise ViewsFormatsException( 'You must not manually add a view with token \'reg\' as this is auto-created' ) views.update(self._add_standard_reg_view()) if default_view_token is None: default_view_token = 'reg' super(RegisterRenderer, self).__init__(request, uri, views, default_view_token, **kwargs) self.label = label self.comment = comment if register_items is not None: self.register_items = register_items else: self.register_items = [] self.contained_item_classes = contained_item_classes self.register_total_count = register_total_count self.per_page = request.args.get('per_page', type=int, default=20) self.page = request.args.get('page', type=int, default=1) self.super_register = super_register self.page_size_max = page_size_max self.register_template = register_template self.paging_error = self._paging() try: self.format = self._get_requested_format() except ViewsFormatsException as e: self.vf_error = str(e)
def __init__(self, request, instance_uri, label, comment, parent_container_uri, parent_container_label, members, members_total_count, *args, profiles=None, default_profile_token=None, super_register=None, page_size_max=1000, register_template=None, **kwargs): """ Constructor :param request: The Flask request object triggering this class object's creation. :type request: :class:`.flask.request` :param instance_uri: The URI requested. :type instance_uri: str :param label: The label of the Register. :type label: str :param comment: A description of the Register. :type comment: str :param members: The items within this register as a list of URI strings or tuples with string elements like (URI, label). They can also be tuples like (URI, URI, label) if you want to manually specify an item's class. :type members: list :param contained_item_classes: The list of URI strings of each distinct class of item contained in this Register. :type contained_item_classes: list :param members_total_count: The total number of items in this Register (not of a page but the register as a whole). :type members_total_count: int :param profiles: A dictionary of named :class:`.View` objects available for this Register, apart from 'reg' which is auto-created. :type profiles: dict :param default_profile_token: The ID of the default :class:`.View` (key of a profile in the list of Views). :type default_profile_token: str :param super_register: A super-Register URI for this register. Can be within this API or external. :type super_register: str :param register_template: The Jinja2 template to use for rendering the HTML profile of the register. If None, then it will default to try and use a template called :code:`alt.html`. :type register_template: str or None :param per_page: Number of items to show per page if not specified in request. If None, then it will default to RegisterRenderer.DEFAULT_ITEMS_PER_PAGE. :type per_page: int or None """ if profiles is None: profiles = {} for k, v in profiles.items(): if k == 'mem': raise ViewsFormatsException( 'You must not manually add a profile with token \'mem\' as this is auto-created' ) profiles.update({ 'mem': Profile( 'Members Profile', 'A very basic RDF data model-only profile that lists the sub-items (members) of collections (rdf:Bag)', ['text/html'] + Renderer.RDF_MEDIA_TYPES, 'text/html', profile_uri='https://w3id.org/profile/mem') }) if default_profile_token is None: default_profile_token = 'mem' super(ContainerRenderer, self).__init__(request, instance_uri, profiles, default_profile_token, **kwargs) if self.vf_error is None: self.label = label self.comment = comment self.parent_container_uri = parent_container_uri self.parent_container_label = parent_container_label if members is not None: self.members = members else: self.members = [] self.members_total_count = members_total_count self.per_page = request.args.get( 'per_page', type=int, default=ContainerRenderer.DEFAULT_ITEMS_PER_PAGE) self.page = request.args.get('page', type=int, default=1) self.super_register = super_register self.page_size_max = page_size_max self.members_template = register_template self.paging_error = self._paging() self.template_extras = kwargs