Skip to content

Commit

Permalink
docs: improve documentation
Browse files Browse the repository at this point in the history
* Improves documentation. (addresses inveniosoftware#99)

* Fixes suggester configuration in example app.

Signed-off-by: Leonardo Rossi <[email protected]>
  • Loading branch information
Leonardo Rossi committed Jul 21, 2016
1 parent 523978c commit e9d9de9
Show file tree
Hide file tree
Showing 10 changed files with 324 additions and 39 deletions.
14 changes: 12 additions & 2 deletions examples/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
$ flask db create
$ flask index init
$ flask fixtures records
$ flask run --debugger
$ flask run --debugger -h 0.0.0.0 -p 5000
Try to get some records:
Expand Down Expand Up @@ -75,7 +75,7 @@
See suggestions:
$ curl -v -XGET 'http://localhost:5000/records/_suggestions?text=Reg'
$ curl -v -XGET 'http://localhost:5000/records/_suggest?title-complete=Reg'
"""

from __future__ import absolute_import, print_function
Expand Down Expand Up @@ -128,6 +128,16 @@
app.config['RECORDS_REST_ENDPOINTS'] = RECORDS_REST_ENDPOINTS
app.config['RECORDS_REST_ENDPOINTS']['recid']['search_index'] = index_name
app.config['RECORDS_REST_ENDPOINTS']['recid']['record_class'] = MementoRecord
# Configure suggesters
app.config['RECORDS_REST_ENDPOINTS']['recid']['suggesters'] = {
"title-complete": {
"completion": {
# see testrecord-v1.0.0.json for index configuration
"field": "suggest_title",
"size": 10
}
}
}
# Sort options
app.config['RECORDS_REST_SORT_OPTIONS'] = {
index_name: {
Expand Down
190 changes: 177 additions & 13 deletions invenio_records_rest/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@
from __future__ import absolute_import, print_function

from flask import request
from flask_babelex import gettext as _
from invenio_search import RecordsSearch

from .facets import terms_filter
from .utils import check_elasticsearch, deny_all


def _(x):
return x

RECORDS_REST_ENDPOINTS = dict(
recid=dict(
pid_type='recid',
Expand All @@ -58,16 +56,157 @@ def _(x):
max_result_window=10000,
),
)
"""Default REST endpoints loaded.
This option can be overwritten to describe the endpoints of the different
record types. Each endpoint is in charge of managing all its CRUD operations
(GET, POST, PUT, DELETE, ...).
The structure of the dictionary is as follows:
.. code-block:: python
from invenio_records_rest.query import es_search_factory
def search_factory(*args, **kwargs):
if not current_user.is_authenticated:
abort(401)
return es_search_factory(*args, **kwargs)
def permission_check_factory():
def check_title(record, *args, **kwargs):
def can(self):
if record['title'] == 'Hello World':
return True
return type('Check', (), {'can': can})()
RECORDS_REST_ENDPOINTS = {
"record-pid-type": {
"create_permission_factory_imp": permission_check_factory(),
"default_media_type": "application/json",
"delete_permission_factory_imp": permission_check_factory(),
"item_route": ""/recods/<pid(recid):pid_value>"",
"links_factory_imp": ("invenio_records_rest.links:"
"default_links_factory"),
"list_route": "/records/",
"max_result_window": 10000,
"pid_fetcher": "<registered-pid-fetcher>",
"pid_minter": "<registered-minter-name>",
"pid_type": "<record-pid-type>",
"read_permission_factory_imp": permission_check_factory(),
"record_class": "mypackage.api:MyRecord",
"record_loaders": {
"application/json": "mypackage.loaders:json_loader"
},
"record_serializers": {
"application/json": "mypackage.utils:my_json_serializer"
},
"search_class": "mypackage.utils:mysearchclass",
"search_factory_imp": search_factory(),
"search_index": "elasticsearch-index-name",
"search_serializers": {
"application/json": "mypackage.utils:my_json_search_serializer"
},
"search_type": "elasticsearch-doc-type",
"suggesters": {
"my_url_param_to_complete": {
"completion": {
"field": "suggest_byyear_elasticsearch_field",
"size": 10,
"context": "year"
}
},
},
"update_permission_factory_imp": permission_check_factory(),
"use_options_view": True,
},
}
:param create_permission_factory_imp: Import path to factory that crcreate
permission object for a given record.
:param default_media_type: Default media type for both records and search.
:param delete_permission_factory_imp: Import path to factory that creates a
delete permission object for a given record.
:param item_route: URL template for a single record.
:param links_factory_imp: Factory for record links generation.
:param list_route: Base URL for the records endpoint.
:param max_result_window: Maximum total number of records retrieved from a
query.
:param pid_type: It specifies the record pid type. It's used also to build the
endpoint name. Required.
E.g. ``url_for('record-pid-type_list')`` to point to the records list.
:param pid_fetcher: It identifies the registered fetcher name
(see class:`invenio_pidstore:_PIDStoreState`). Required.
:param pid_minter: It identifies the registered minter name
(see class:`invenio_pidstore:_PIDStoreState`). Required.
:param read_permission_factory_imp: Import path to factory that creates a
read permission object for a given record.
:param record_class: Name of the record API class.
:param record_loaders: It contains the list of record deserializers for
supperted formats.
:param record_serializers: It contains the list of record serializers for
supported formats.
:param search_class: Import path or class object for the object in charge of
execute the search queries. The default search class is
class:`invenio_search.api.RecordsSearch`.
For more information about resource loading, see
class:`elasticsearch_dsl.Search`.
:param search_factory_imp: Factory that parse query that returns a tuple with
search instance and URL arguments.
:param search_index: Name of the search index used when searching records.
:param search_serializers: It contains the list of records serializers for all
supported format. This configuration differ from the previous because in
this case it handle a list of records resulted by a search query instead of
a single record.
:param search_type: Name of the search type used when searching records.
:param suggesters: Suggester fields configuration. Any element of the
dictionary represents a suggestion field. The key of the dictionary element
is used to identify the url query parameter. The "field" parameter identify\
the suggester field name in your elasticsearch schema.
To have more information about suggestion configuration, you can read
suggesters section on ElasticSearch documentation.
Note: only completion suggessters are supported.
:param update_permission_factory_imp: Import path to factory that creates a
update permission object for a given record.
:param use_options_view: Determines if a special option view should be
installed.
"""

RECORDS_REST_DEFAULT_LOADERS = {
'application/json': lambda: request.get_json(),
'application/json-patch+json': lambda: request.get_json(force=True),
}
"""Default data loaders per request mime type.
This option can be overritten in each REST endpoint as follows::
This option can be overritten in each REST endpoint as follows:
.. code-block:: python
{
RECORDS_REST_ENDPOINTS = {
"recid": {
...
"record_loaders": {
Expand Down Expand Up @@ -97,13 +236,17 @@ def _(x):
)
"""Sort options for default sorter factory.
The structure of the dictionary is as follows::
The structure of the dictionary is as follows:
{
.. code-block:: python
RECORDS_REST_SORT_OPTIONS = {
"<index or index alias>": {
"fields": ["<search_field>", "<search_field>", ...],
"title": "<title displayed to end user in search-ui>",
"default_order": "<default sort order in search-ui>",
"<sort-field-name>": {
"fields": ["<search_field>", "<search_field>", ...],
"title": "<title displayed to end user in search-ui>",
"default_order": "<default sort order in search-ui>",
}
}
}
Expand All @@ -124,7 +267,19 @@ def _(x):
noquery='mostrecent',
)
)
"""Default sort option per index with/without query string."""
"""Default sort option per index with/without query string.
The structure of the dictionary is as follows:
.. code-block:: python
RECORDS_REST_DEFAULT_SORT = {
"<index or index alias>": {
"query": "<default-sort-if-a-query-is-passed-from-url>",
"noquery": "<default-sort-if-no-query-in-passed-from-url>"
}
}
"""

RECORDS_REST_FACETS = dict(
records=dict(
Expand All @@ -138,9 +293,11 @@ def _(x):
)
"""Facets per index for the default facets factory.
The structure of the dictionary is as follows::
The structure of the dictionary is as follows:
.. code-block:: python
{
RECORDS_REST_FACETS = {
"<index or index alias>": {
"aggs": {
"<key>": <aggregation definition>,
Expand All @@ -159,6 +316,13 @@ def _(x):
"""

RECORDS_REST_DEFAULT_CREATE_PERMISSION_FACTORY = deny_all
"""Default crete permission factory: reject any request."""

RECORDS_REST_DEFAULT_READ_PERMISSION_FACTORY = check_elasticsearch
"""Default read permission factory: check if the record exists."""

RECORDS_REST_DEFAULT_UPDATE_PERMISSION_FACTORY = deny_all
"""Default update permission factory: reject any request."""

RECORDS_REST_DEFAULT_DELETE_PERMISSION_FACTORY = deny_all
"""Default delete permission factory: reject any request."""
2 changes: 1 addition & 1 deletion invenio_records_rest/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
# Search
#
class MaxResultWindowRESTError(RESTException):
"""Maximum number of results passed."""
"""Maximum number of passed results have been reached."""

code = 400
description = 'Maximum number of results have been reached.'
Expand Down
23 changes: 20 additions & 3 deletions invenio_records_rest/facets.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,25 @@


def terms_filter(field):
"""Create a term filter."""
"""Create a term filter.
:param field: Field name.
:returns: Function that returns the Terms query.
"""
def inner(values):
return Q('terms', **{field: values})
return inner


def range_filter(field, start_date_math=None, end_date_math=None, **kwargs):
"""Create a range filter."""
"""Create a range filter.
:param field: Field name.
:param start_date_math: Starting date.
:param end_date_math: Ending date.
:param kwargs: Addition arguments passed to the Range query.
:returns: Function that returns the Range query.
"""
def inner(values):
if len(values) != 1 or values[0].count('--') != 1 or values[0] == '--':
raise RESTValidationError(
Expand Down Expand Up @@ -121,7 +132,13 @@ def _aggregations(search, definitions):


def default_facets_factory(search, index):
"""Add facets to query."""
"""Add a default facets to query.
:param search: Basic search object.
:param index: Index name.
:returns: A tuple containing the new search object and a dictionary with
all fields and values used.
"""
urlkwargs = MultiDict()

facets = current_app.config['RECORDS_REST_FACETS'].get(index)
Expand Down
6 changes: 5 additions & 1 deletion invenio_records_rest/links.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@


def default_links_factory(pid):
"""Factory for record links generation."""
"""Factory for record links generation.
:param pid: A class:`invenio_pidstore.models.PersistentIdentifier` object.
:returns: Dictionary containing a list of useful links for the record.
"""
endpoint = '.{0}_item'.format(pid.pid_type)
links = dict(self=url_for(endpoint, pid_value=pid.pid_value,
_external=True))
Expand Down
7 changes: 7 additions & 0 deletions invenio_records_rest/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,12 @@
from .schemas.json import RecordSchemaJSONV1

json_v1 = JSONSerializer(RecordSchemaJSONV1)
"""JSON v1 serializer.
It follows the schema class:`.schemas.json.RecordSchemaJSONV1`."""

json_v1_response = record_responsify(json_v1, 'application/json')
"""JSON response builder that uses the JSON v1 serializer."""

json_v1_search = search_responsify(json_v1, 'application/json')
"""JSON search response builder that uses the JSON v1 serializer."""
4 changes: 3 additions & 1 deletion invenio_records_rest/serializers/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def record_responsify(serializer, mimetype):
:param serializer: Serializer instance.
:param mimetype: MIME type of response.
:returns: Function that generates a record HTTP response.
"""
def view(pid, record, code=200, headers=None, links_factory=None):
response = current_app.response_class(
Expand All @@ -61,6 +62,7 @@ def search_responsify(serializer, mimetype):
:param serializer: Serializer instance.
:param mimetype: MIME type of response.
:returns: Function that generates a record HTTP response.
"""
def view(pid_fetcher, search_result, code=200, headers=None, links=None,
item_links_factory=None):
Expand Down Expand Up @@ -90,5 +92,5 @@ def add_link_header(response, links):
if links is not None:
response.headers.extend({
'Link': ', '.join([
'<{0}>; rel="{1}"'.format(l, r) for r, l in links.items()])
'<{0}>; rel="{1}"'.format(l, r) for r, l in links.items()])
})
Loading

0 comments on commit e9d9de9

Please sign in to comment.