From 67f9aadeed8a703c0ca0daccc20d8f0f78864013 Mon Sep 17 00:00:00 2001 From: Leonardo Rossi Date: Fri, 22 Jul 2016 09:28:17 +0200 Subject: [PATCH] docs: addition of param and result sections * Improves documentation. (addresses #95) Signed-off-by: Leonardo Rossi --- docs/api.rst | 57 +++++- docs/conf.py | 25 ++- invenio_deposit/api.py | 183 ++++++++++++++++-- invenio_deposit/cli.py | 13 +- invenio_deposit/config.py | 45 ++++- invenio_deposit/ext.py | 31 ++- invenio_deposit/fetchers.py | 8 +- invenio_deposit/links.py | 18 +- invenio_deposit/minters.py | 28 ++- invenio_deposit/permissions.py | 6 +- invenio_deposit/providers.py | 7 +- invenio_deposit/receivers.py | 10 +- invenio_deposit/scopes.py | 5 +- invenio_deposit/search.py | 9 +- invenio_deposit/serializers.py | 39 +++- invenio_deposit/signals.py | 9 +- .../templates/invenio_deposit/index.html | 2 +- invenio_deposit/utils.py | 22 ++- invenio_deposit/views/rest.py | 90 ++++++++- invenio_deposit/views/ui.py | 8 +- 20 files changed, 555 insertions(+), 60 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 67d43af..5cd3dac 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -25,6 +25,59 @@ API Docs ======== -invenio_deposit ---------------- +API +--- +.. automodule:: invenio_deposit.api + :members: + +.. automodule:: invenio_deposit.fetchers + :members: + +.. automodule:: invenio_deposit.minters + :members: + +.. automodule:: invenio_deposit.providers + :members: + +.. automodule:: invenio_deposit.receivers + :members: + +.. automodule:: invenio_deposit.ext + :members: + +Configuration +------------- + +.. automodule:: invenio_deposit.config + :members: + +.. automodule:: invenio_deposit.permissions + :members: + +.. automodule:: invenio_deposit.signals + :members: + +.. automodule:: invenio_deposit.utils + :members: + +Views +----- + +.. automodule:: invenio_deposit.links + :members: + +.. automodule:: invenio_deposit.scopes + :members: + +.. automodule:: invenio_deposit.search + :members: + +.. automodule:: invenio_deposit.serializers + :members: + +.. automodule:: invenio_deposit.views.rest + :members: + +.. automodule:: invenio_deposit.views.ui + :members: diff --git a/docs/conf.py b/docs/conf.py index 40f6013..cece5e3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -326,6 +326,29 @@ # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False +nitpick_ignore = [('py:class', 'Record')] # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = { + 'https://docs.python.org/': None, + 'Flask': ( + 'http://flask.pocoo.org/docs/0.11', None + ), + 'Flask-Principal': ( + 'http://pythonhosted.org/Flask-Principal', None + ), + 'invenio-pidstore': ('http://pythonhosted.org/invenio-pidstore', None), + 'invenio-records': ('http://pythonhosted.org/invenio-records', None), + 'invenio-records-rest': ( + 'http://pythonhosted.org/invenio-records-rest', None + ), + 'invenio-records-files': ( + 'http://pythonhosted.org/invenio-records-files', None + ), + 'invenio-files-rest': ( + 'http://pythonhosted.org/invenio-files-rest', None + ), + 'invenio-access': ( + 'http://pythonhosted.org/invenio-access', None + ), +} diff --git a/invenio_deposit/api.py b/invenio_deposit/api.py index 229ca8d..32eaf66 100644 --- a/invenio_deposit/api.py +++ b/invenio_deposit/api.py @@ -56,7 +56,11 @@ def index(method=None, delete=False): - """Update index.""" + """Decorator to update index. + + :param method: Function wrapped. (Default: ``None``) + :param delete: If `True` delete the indexed record. (Default: ``None``) + """ if method is None: return partial(index, delete=delete) @@ -76,7 +80,12 @@ def wrapper(self_or_cls, *args, **kwargs): def has_status(method=None, status='draft'): - """Check that deposit has defined status (default: draft).""" + """Check that deposit has a defined status (default: draft). + + :param method: Function executed if record has a defined status. + (Default: ``None``) + :param status: Defined status to check. (Default: 'draft') + """ if method is None: return partial(has_status, status=status) @@ -91,7 +100,13 @@ def wrapper(self, *args, **kwargs): def preserve(method=None, result=True, fields=None): - """Preserve fields in deposit.""" + """Preserve fields in deposit. + + :param method: Function to execute. (Default: ``None``) + :param result: If `True` returns the result of method execution, + otherwise `self`. (Default: ``True``) + :param fields: List of fields to preserve (default: ('_deposit',)). + """ if method is None: return partial(preserve, result=result, fields=fields) @@ -116,7 +131,7 @@ class Deposit(Record): """Default deposit indexer.""" published_record_class = Record - """Record API class used for published records.""" + """The Record API class used for published records.""" deposit_fetcher = staticmethod(default_deposit_fetcher) """Function used to retrieve the deposit PID.""" @@ -142,7 +157,11 @@ def record_schema(self): ) def build_deposit_schema(self, record): - """Convert record schema to a valid deposit schema.""" + """Convert record schema to a valid deposit schema. + + :param record: The record used to build deposit schema. + :returns: The absolute URL to the schema or `None`. + """ schema_path = current_jsonschemas.url_to_path(record['$schema']) schema_prefix = current_app.config['DEPOSIT_JSONSCHEMAS_PREFIX'] if schema_path: @@ -181,13 +200,31 @@ def merge_with_published(self): @index def commit(self, *args, **kwargs): - """Store changes on current instance in database.""" + """Store changes on current instance in database and index it.""" return super(Deposit, self).commit(*args, **kwargs) @classmethod @index def create(cls, data, id_=None): - """Create a deposit.""" + """Create a deposit. + + Initialize the follow information inside the deposit: + + .. code-block:: python + + deposit['_deposit'] = { + 'id': pid_value, + 'status': 'draft', + 'owners': [user_id], + 'created_by': user_id, + } + + The deposit index is updated. + + :param data: Input dictionary to fill the deposit. + :param id_: Default uuid for the deposit. + :returns: The new created deposit. + """ data.setdefault('$schema', current_jsonschemas.path_to_url( current_app.config['DEPOSIT_DEFAULT_JSONSCHEMA'] )) @@ -207,7 +244,10 @@ def create(cls, data, id_=None): return super(Deposit, cls).create(data, id_=id_) def _publish_new(self, id_=None): - """Publish new deposit.""" + """Publish new deposit. + + :param id_: The forced record UUID. + """ minter = current_pidstore.minters[ current_app.config['DEPOSIT_PID_MINTER'] ] @@ -258,10 +298,39 @@ def _publish_edited(self): record = record.__class__(data, model=record.model) return record - # No need for indexing as it calls self.commit() @has_status def publish(self, pid=None, id_=None): - """Publish a deposit.""" + """Publish a deposit. + + If it's the first time: + + * it calls the minter and set the following meta information inside + the deposit: + + .. code-block:: python + + deposit['_deposit'] = { + 'type': pid_type, + 'value': pid_value, + 'revision_id': 0, + } + + * A dump of all information inside the deposit is done. + + * A snapshot of the files is done. + + Otherwise, published the new edited version. + In this case, if in the mainwhile someone already published a new + version, it'll try to merge the changes with the latest version. + + .. note:: no need for indexing as it calls `self.commit()`. + + Status required: ``'draft'``. + + :param pid: Force the new pid value. (Default: ``None``) + :param id_: Force the new uuid value as deposit id. (Default: ``None``) + :returns: Returns itself. + """ pid = pid or self.pid if not pid.is_registered(): @@ -278,7 +347,10 @@ def publish(self, pid=None, id_=None): return self def _prepare_edit(self, record): - """Update selected keys.""" + """Update selected keys. + + :param record: The record to prepare. + """ data = record.dumps() # Keep current record revision for merging. data['_deposit']['pid']['revision_id'] = record.revision_id @@ -289,7 +361,32 @@ def _prepare_edit(self, record): @has_status(status='published') @index def edit(self, pid=None): - """Edit deposit.""" + """Edit deposit. + + #. The signal :data:`invenio_records.signals.before_record_update` + is sent before the edit execution. + + #. The following meta information are saved inside the deposit: + + .. code-block:: python + + deposit['_deposit']['pid'] = record.revision_id + deposit['_deposit']['status'] = 'draft' + deposit['$schema'] = deposit_schema_from_record_schema + + #. The signal :data:`invenio_records.signals.after_record_update` is + sent after the edit execution. + + #. The deposit index is updated. + + Status required: `published`. + + .. note:: the process fails if the pid has status + :attr:`invenio_pidstore.models.PIDStatus.REGISTERED`. + + :param pid: Force a pid object. (Default: ``None``) + :returns: A new Deposit object. + """ pid = pid or self.pid with db.session.begin_nested(): @@ -310,7 +407,30 @@ def edit(self, pid=None): @has_status @index def discard(self, pid=None): - """Discard deposit changes.""" + """Discard deposit changes. + + #. The signal :data:`invenio_records.signals.before_record_update` is + sent before the edit execution. + + #. It restores the last published version. + + #. The following meta information are saved inside the deposit: + + .. code-block:: python + + deposit['_deposit']['status'] = 'draft' + deposit['$schema'] = deposit_schema_from_record_schema + + #. The signal :data:`invenio_records.signals.after_record_update` is + sent after the edit execution. + + #. The deposit index is updated. + + Status required: ``'draft'``. + + :param pid: Force a pid object. (Default: ``None``) + :returns: A new Deposit object. + """ pid = pid or self.pid with db.session.begin_nested(): @@ -330,7 +450,14 @@ def discard(self, pid=None): @has_status @index(delete=True) def delete(self, force=True, pid=None): - """Delete deposit.""" + """Delete deposit. + + Status required: ``'draft'``. + + :param force: Force deposit delete. (Default: ``True``) + :param pid: Force pid object. (Default: ``None``) + :returns: A new Deposit object. + """ pid = pid or self.pid if self['_deposit'].get('pid'): @@ -342,19 +469,34 @@ def delete(self, force=True, pid=None): @has_status @preserve(result=False) def clear(self, *args, **kwargs): - """Clear only drafts.""" + """Clear only drafts. + + Status required: ``'draft'``. + + Meta information inside `_deposit` are preserved. + """ super(Deposit, self).clear(*args, **kwargs) @has_status @preserve(result=False) def update(self, *args, **kwargs): - """Update only drafts.""" + """Update only drafts. + + Status required: ``'draft'``. + + Meta information inside `_deposit` are preserved. + """ super(Deposit, self).update(*args, **kwargs) @has_status @preserve def patch(self, *args, **kwargs): - """Patch only drafts.""" + """Patch only drafts. + + Status required: ``'draft'``. + + Meta information inside `_deposit` are preserved. + """ return super(Deposit, self).patch(*args, **kwargs) def _create_bucket(self): @@ -365,7 +507,12 @@ def _create_bucket(self): @property def files(self): - """Add validation on ``sort_by`` method.""" + """List of Files inside the deposit. + + Add validation on ``sort_by`` method: if, at the time of files access, + the record is not a ``'draft'`` then a + :exc:`invenio_pidstore.errors.PIDInvalidAction` is rised. + """ files_ = super(Deposit, self).files if files_: diff --git a/invenio_deposit/cli.py b/invenio_deposit/cli.py index a1fb711..449fedf 100644 --- a/invenio_deposit/cli.py +++ b/invenio_deposit/cli.py @@ -35,7 +35,11 @@ def process_minter(value): - """Load minter from PIDStore registry based on given value.""" + """Load minter from PIDStore registry based on given value. + + :param value: Name of the minter. + :returns: The minter. + """ try: return current_pidstore.minters[value] except KeyError: @@ -47,7 +51,12 @@ def process_minter(value): def process_schema(value): - """Load schema from JSONSchema registry based on given value.""" + """Load schema from JSONSchema registry based on given value. + + :param value: Schema path, relative to the directory when it was + registered. + :returns: The schema absolute path. + """ schemas = current_app.extensions['invenio-jsonschemas'].schemas try: return schemas[value] diff --git a/invenio_deposit/config.py b/invenio_deposit/config.py index 243a36a..db3837d 100644 --- a/invenio_deposit/config.py +++ b/invenio_deposit/config.py @@ -83,6 +83,12 @@ max_result_window=10000, ), ) +"""Basic REST deposit configuration. + +Most of the configurations have the same meaning of the record configuration +:data:`invenio_records_rest.config.RECORDS_REST_ENDPOINTS`. +Deposit introduce also configuration for files. +""" DEPOSIT_REST_SORT_OPTIONS = dict( deposits=dict( @@ -100,6 +106,10 @@ ) ) ) +"""Basic deposit sort configuration. +See :data:`invenio_records_rest.config.RECORDS_REST_SORT_OPTIONS` for more +information. +""" DEPOSIT_REST_DEFAULT_SORT = dict( deposits=dict( @@ -107,6 +117,11 @@ noquery='mostrecent' ) ) +"""Default deposit sort configuration. +See :data:`invenio_records_rest.config.RECORDS_REST_DEFAULT_SORT` for more +information. +""" + DEPOSIT_REST_FACETS = dict( deposits=dict( @@ -120,6 +135,11 @@ ) ) ) +"""Basic deposit facts configuration. +See :data:`invenio_records_rest.config.RECORDS_REST_FACETS` for more +information. +""" + DEPOSIT_RECORDS_UI_ENDPOINTS = dict( depid=dict( @@ -129,19 +149,40 @@ record_class='invenio_deposit.api:Deposit', ), ) +"""Basic deposit UI endpoints configuration. + +The structure of the dictionary is as follows: + +.. code-block:: python + + DEPOSIT_RECORDS_UI_ENDPOINTS = { + '': { + 'pid_type': '', + 'route': '/path/to/deposit/', + 'template': 'invenio_deposit/edit.html', + 'record_class': 'mypackage.api:MyDeposit', + } + } +""" DEPOSIT_UI_INDEX_TEMPLATE = 'invenio_deposit/index.html' -"""Index template.""" +"""Template for the index page.""" DEPOSIT_UI_NEW_TEMPLATE = 'invenio_deposit/edit.html' -"""New deposit template.""" +"""Template for a new deposit page.""" DEPOSIT_UI_JSTEMPLATE_ACTIONS = \ 'node_modules/invenio-records-js/dist/templates/actions.html' +"""Template for defined by `invenio-records-js`.""" + DEPOSIT_UI_JSTEMPLATE_ERROR = \ 'node_modules/invenio-records-js/dist/templates/error.html' +"""Template for defined by `invenio-records-js`.""" + DEPOSIT_UI_JSTEMPLATE_FORM = \ 'node_modules/invenio-records-js/dist/templates/form.html' +"""Template for defined by `invenio-records-js`.""" + DEPOSIT_UI_SEARCH_INDEX = 'deposits' """Search index name for the deposit.""" diff --git a/invenio_deposit/ext.py b/invenio_deposit/ext.py index c61c027..ba33df1 100644 --- a/invenio_deposit/ext.py +++ b/invenio_deposit/ext.py @@ -42,7 +42,13 @@ def __init__(self, app=None): self.init_app(app) def init_app(self, app): - """Flask application initialization.""" + """Flask application initialization. + + Initialize the CLI and all UI endpoints. + Connect all signals if `DEPOSIT_REGISTER_SIGNALS` is ``True``. + + :param app: An instance of :class:`~flask.app.Flask`. + """ self.init_config(app) app.cli.add_command(cmd) app.register_blueprint(ui.create_blueprint( @@ -54,7 +60,10 @@ def init_app(self, app): weak=False) def init_config(self, app): - """Initialize configuration.""" + """Initialize configuration. + + :param app: An instance of :class:`~flask.app.Flask`. + """ app.config.setdefault( 'DEPOSIT_BASE_TEMPLATE', app.config.get('BASE_TEMPLATE', @@ -68,12 +77,21 @@ class InvenioDepositREST(object): """Invenio-Deposit REST extension.""" def __init__(self, app=None): - """Extension initialization.""" + """Extension initialization. + + :param app: An instance of :class:`~flask.app.Flask`. + """ if app: self.init_app(app) def init_app(self, app): - """Flask application initialization.""" + """Flask application initialization. + + Initialize the CLI and all REST endpoints. + Connect all signals if `DEPOSIT_REGISTER_SIGNALS` is True. + + :param app: An instance of :class:`~flask.app.Flask`. + """ self.init_config(app) app.register_blueprint(rest.create_blueprint( app.config['DEPOSIT_REST_ENDPOINTS'] @@ -84,7 +102,10 @@ def init_app(self, app): weak=False) def init_config(self, app): - """Initialize configuration.""" + """Initialize configuration. + + :param app: An instance of :class:`~flask.app.Flask`. + """ for k in dir(config): if k.startswith('DEPOSIT_'): app.config.setdefault(k, getattr(config, k)) diff --git a/invenio_deposit/fetchers.py b/invenio_deposit/fetchers.py index 4d3c9ad..528845d 100644 --- a/invenio_deposit/fetchers.py +++ b/invenio_deposit/fetchers.py @@ -32,7 +32,13 @@ def deposit_fetcher(record_uuid, data): - """Fetch a deposit identifier.""" + """Fetch a deposit identifier. + + :param record_uuid: Record UUID. + :param data: Record content. + :returns: A :class:`invenio_pidstore.fetchers.FetchedPID` that contains + data['_deposit']['id'] as pid_value. + """ return FetchedPID( provider=DepositProvider, pid_type=DepositProvider.pid_type, diff --git a/invenio_deposit/links.py b/invenio_deposit/links.py index 0b3db8b..8e93a4c 100644 --- a/invenio_deposit/links.py +++ b/invenio_deposit/links.py @@ -29,7 +29,23 @@ def deposit_links_factory(pid): - """Factory for record links generation.""" + """Factory for record links generation. + + The dictionary is formed as: + + .. code-block:: python + + { + "files": "/url/to/files", + "publish": "/url/to/publish", + "edit": "/url/to/edit", + "discard": "/url/to/discard", + ... + } + + :param pid: The record PID object. + :returns: A dictionary that contains all the links. + """ links = default_links_factory(pid) def _url(name, **kwargs): diff --git a/invenio_deposit/minters.py b/invenio_deposit/minters.py index d6be0a1..e787c91 100644 --- a/invenio_deposit/minters.py +++ b/invenio_deposit/minters.py @@ -32,7 +32,32 @@ def deposit_minter(record_uuid, data): - """Mint a deposit identifier.""" + """Mint a deposit identifier. + + A PID with the following characteristics is created: + + .. code-block:: python + + { + "object_type": "rec", + "object_uuid": record_uuid, + "pid_value": "", + "pid_type": "depid", + } + + The following deposit meta information are updated: + + .. code-block:: python + + deposit['_deposit'] = { + "id": "", + "status": "draft", + } + + :param record_uuid: Record UUID. + :param data: Record content. + :returns: A :class:`invenio_pidstore.models.PersistentIdentifier` object. + """ provider = DepositProvider.create( object_type='rec', object_uuid=record_uuid, @@ -41,6 +66,5 @@ def deposit_minter(record_uuid, data): data['_deposit'] = { 'id': provider.pid.pid_value, 'status': 'draft', - # TODO 'owners': [current_user.get_id()], } return provider.pid diff --git a/invenio_deposit/permissions.py b/invenio_deposit/permissions.py index 1afdc16..7262490 100644 --- a/invenio_deposit/permissions.py +++ b/invenio_deposit/permissions.py @@ -31,7 +31,11 @@ def admin_permission_factory(): - """Factory for creating a permission for an admin. + """Factory for creating a permission for an admin `deposit-admin-access`. + + If `invenio-access` module is installed, it returns a + :class:`invenio_access.permissions.DynamicPermission` object. + Otherwise, it returns a :class:`flask_principal.Permission` object. :returns: Permission instance. """ diff --git a/invenio_deposit/providers.py b/invenio_deposit/providers.py index c7e3f3b..e7a8b80 100644 --- a/invenio_deposit/providers.py +++ b/invenio_deposit/providers.py @@ -48,7 +48,12 @@ class DepositProvider(BaseProvider): @classmethod def create(cls, object_type=None, object_uuid=None, **kwargs): - """Create a new deposit identifier.""" + """Create a new deposit identifier. + + :param object_type: The object type (Default: ``None``) + :param object_uuid: The object UUID (Default: ``None``) + :param kwargs: It contains the pid value. + """ assert 'pid_value' in kwargs kwargs.setdefault('status', cls.default_status) return super(DepositProvider, cls).create( diff --git a/invenio_deposit/receivers.py b/invenio_deposit/receivers.py index ed300c7..22e3b5c 100644 --- a/invenio_deposit/receivers.py +++ b/invenio_deposit/receivers.py @@ -30,7 +30,15 @@ def index_deposit_after_publish(sender, action=None, pid=None, deposit=None): - """Index the record after publishing.""" + """Index the record after publishing. + + .. note:: if the record is not published, it doesn't index. + + :param sender: Who send the signal. + :param action: Action executed by the sender. (Default: ``None``) + :param pid: PID object. (Default: ``None``) + :param deposit: Deposit object. (Default: ``None``) + """ if action == 'publish': _, record = deposit.fetch_published() index_record.delay(str(record.id)) diff --git a/invenio_deposit/scopes.py b/invenio_deposit/scopes.py index f8146ec..bf53b85 100644 --- a/invenio_deposit/scopes.py +++ b/invenio_deposit/scopes.py @@ -31,7 +31,7 @@ class DepositScope(Scope): - """Deposit scope.""" + """Basic deposit scope.""" def __init__(self, id_, *args, **kwargs): """Define the scope.""" @@ -42,5 +42,8 @@ def __init__(self, id_, *args, **kwargs): write_scope = DepositScope('write', help_text=_('Allow upload (but not publishing).')) +"""Allow upload (but not publishing).""" + actions_scope = DepositScope('actions', help_text=_('Allow publishing of uploads.')) +"""Allow publishing of uploads.""" diff --git a/invenio_deposit/search.py b/invenio_deposit/search.py index 3e578d5..9215809 100644 --- a/invenio_deposit/search.py +++ b/invenio_deposit/search.py @@ -36,7 +36,14 @@ def deposits_filter(): """Filter list of deposits. - Allow admin to see all or if we're not in a request. + Permit to the user to see all if: + + * The user is an admin (see + func:`invenio_deposit.permissions:admin_permission_factory`). + + * It's called outside of a request. + + Otherwise, it filters out any deposit where user is not the owner. """ if not has_request_context() or admin_permission_factory().can(): return Q() diff --git a/invenio_deposit/serializers.py b/invenio_deposit/serializers.py index 003448a..eb3ec03 100644 --- a/invenio_deposit/serializers.py +++ b/invenio_deposit/serializers.py @@ -32,8 +32,11 @@ def json_serializer(pid, data, *args): """Build a JSON Flask response using the given data. + :param pid: The `invenio_pidstore.models.PersistentIdentifier` of the + record. + :param data: The record metadata. :returns: A Flask response with JSON data. - :returns type: :py:class:`flask.Response` + :rtype: :py:class:`flask.Response`. """ if data is not None: response = Response( @@ -42,12 +45,15 @@ def json_serializer(pid, data, *args): ) else: response = Response(mimetype='application/json') - # response.set_etag(str(data.model.version_id)) return response def file_serializer(obj): - """Serialize a object.""" + """Serialize a object. + + :param obj: A :class:`invenio_files_rest.models.ObjectVersion` instance. + :returns: A dictionary with the fields to serialize. + """ return { "id": str(obj.file_id), "filename": obj.key, @@ -57,18 +63,38 @@ def file_serializer(obj): def json_file_serializer(obj, status=None): - """JSON File Serializer.""" + """JSON File Serializer. + + :param obj: A :class:`invenio_files_rest.models.ObjectVersion` instance. + :param status: A HTTP Status. (Default: ``None``) + :returns: A Flask response with JSON data. + :rtype: :py:class:`flask.Response`. + """ return make_response(jsonify(file_serializer(obj)), status) def json_files_serializer(objs, status=None): - """JSON Files Serializer.""" + """JSON Files Serializer. + + :parma objs: A list of:class:`invenio_files_rest.models.ObjectVersion` + instances. + :param status: A HTTP Status. (Default: ``None``) + :returns: A Flask response with JSON data. + :rtype: :py:class:`flask.Response`. + """ files = [file_serializer(obj) for obj in objs] return make_response(json.dumps(files), status) def json_file_response(obj, status=None): - """JSON Files/File serializer.""" + """JSON Files/File serializer. + + :param obj: A :class:`invenio_files_rest.models.ObjectVersion` instance or + a :class:`invenio_records_files.api.FilesIterator` if it's a list of + files. + :returns: A Flask response with JSON data. + :rtype: :py:class:`flask.Response`. + """ from invenio_records_files.api import FilesIterator if isinstance(obj, FilesIterator): @@ -78,3 +104,4 @@ def json_file_response(obj, status=None): json_v1_files_response = json_file_response +"""Default JSON files response.""" diff --git a/invenio_deposit/signals.py b/invenio_deposit/signals.py index 19353bd..405a8cf 100644 --- a/invenio_deposit/signals.py +++ b/invenio_deposit/signals.py @@ -32,10 +32,13 @@ """Signal is sent after the REST action. Kwargs: - action (str) - name of REST action, e.g. "publish". - pid (invenio_pidstore.models.PersistentIdentifier) - PID of the deposit. + +#. action (str) - name of REST action, e.g. "publish". + +#. pid (invenio_pidstore.models.PersistentIdentifier) - PID of the deposit. The pid_type is assumed to be 'depid'. - deposit (invenio_depost.api.Deposit) - API instance of the deposit + +#. deposit (invenio_depost.api.Deposit) - API instance of the deposit Example subscriber: diff --git a/invenio_deposit/templates/invenio_deposit/index.html b/invenio_deposit/templates/invenio_deposit/index.html index cbcdf01..55c22bd 100644 --- a/invenio_deposit/templates/invenio_deposit/index.html +++ b/invenio_deposit/templates/invenio_deposit/index.html @@ -94,7 +94,7 @@ {%- block search_sort %}
- {%- set sort_options = config.get('RECORDS_REST_SORT_OPTIONS', {}).get(config.DEPOSIT_UI_SEARCH_INDEX) %} + {%- set sort_options = config.get('DEPOSIT_REST_SORT_OPTIONS', {}).get(config.DEPOSIT_UI_SEARCH_INDEX) %} {%- if sort_options %} {%- block search_sort_select scoped %} diff --git a/invenio_deposit/utils.py b/invenio_deposit/utils.py index 1ee4378..480e164 100644 --- a/invenio_deposit/utils.py +++ b/invenio_deposit/utils.py @@ -33,7 +33,13 @@ def check_oauth2_scope(can_method, *myscopes): - """Check OAuth2 scope.""" + """Base permission factory that check OAuth2 scope and can_method. + + :param can_method: Permission check function that accept a record in input + and return a boolean. + :param myscopes: List of scopes required to permit the access. + :returns: A :class:`flask_principal.Permission` factory. + """ def check(record, *args, **kwargs): @require_api_auth() @require_oauth_scopes(*myscopes) @@ -45,13 +51,25 @@ def can(self): def can_elasticsearch(record): - """Try to search for given record.""" + """Check if a given record is indexed. + + :param record: A record object. + :returns: If the record is indexed returns `True`, otherwise `False`. + """ search = request._methodview.search_class() search = search.get_record(str(record.id)) return search.count() == 1 check_oauth2_scope_write = check_oauth2_scope(lambda x: True, write_scope.id) +"""Permission factory that check oauth2 scope. + +The scope :class:`invenio_deposit.scopes.write_scope` is checked. +""" check_oauth2_scope_write_elasticsearch = check_oauth2_scope( can_elasticsearch, write_scope.id) +"""Permission factory that check oauth2 scope and if the record is indexed. + +The scope :class:`invenio_deposit.scopes.write_scope` is checked. +""" diff --git a/invenio_deposit/views/rest.py b/invenio_deposit/views/rest.py index 0fa9138..cc9e2e8 100644 --- a/invenio_deposit/views/rest.py +++ b/invenio_deposit/views/rest.py @@ -54,7 +54,13 @@ def create_blueprint(endpoints): - """Create Invenio-Deposit-REST blueprint.""" + """Create Invenio-Deposit-REST blueprint. + + See: :data:`invenio_deposit.config.DEPOSIT_REST_ENDPOINTS`. + + :param endpoints: List of endpoints configuration. + :returns: The configured blueprint. + """ blueprint = Blueprint( 'invenio_deposit_rest', __name__, @@ -191,7 +197,17 @@ def __init__(self, serializers, pid_type, ctx, *args, **kwargs): @pass_record @need_record_permission('update_permission_factory') def post(self, pid, record, action): - """Handle deposit action.""" + """Handle deposit action. + + After the action is executed, a + :class:`invenio_deposit.signals.post_action` signal is sent. + + Permission required: `update_permission_factory`. + + :param pid: Pid object (from url). + :param record: Record object resolved from the pid. + :param action: The action to execute. + """ getattr(record, action)(pid=pid) db.session.commit() @@ -226,7 +242,14 @@ def __init__(self, serializers, pid_type, ctx, *args, **kwargs): @pass_record @need_record_permission('read_permission_factory') def get(self, pid, record): - """Get deposit/depositions/:id/files.""" + """Get files. + + Permission required: `read_permission_factory`. + + :param pid: Pid object (from url). + :param record: Record object resolved from the pid. + :returns: The files. + """ return self.make_response(record.files) @require_api_auth() @@ -234,7 +257,13 @@ def get(self, pid, record): @pass_record @need_record_permission('update_permission_factory') def post(self, pid, record): - """Handle POST deposit files.""" + """Handle POST deposit files. + + Permission required: `update_permission_factory`. + + :param pid: Pid object (from url). + :param record: Record object resolved from the pid. + """ # load the file uploaded_file = request.files['file'] # file name @@ -255,7 +284,28 @@ def post(self, pid, record): @pass_record @need_record_permission('update_permission_factory') def put(self, pid, record): - """Handle PUT deposit files.""" + """Handle the sort of the files through the PUT deposit files. + + Expected input in body PUT: + + .. code-block:: javascript + + [ + { + "id": 1 + }, + { + "id": 2 + }, + ... + } + + Permission required: `update_permission_factory`. + + :param pid: Pid object (from url). + :param record: Record object resolved from the pid. + :returns: The files. + """ try: ids = [data['id'] for data in json.loads( request.data.decode('utf-8'))] @@ -295,7 +345,17 @@ def __init__(self, serializers, pid_type, ctx, *args, **kwargs): @pass_record @need_record_permission('read_permission_factory') def get(self, pid, record, key, version_id, **kwargs): - """Get deposit/depositions/:id/files/:key.""" + """Get file. + + Permission required: `read_permission_factory`. + + :param pid: Pid object (from url). + :param record: Record object resolved from the pid. + :param key: Unique identifier for the file in the deposit. + :param version_id: File version. Optional. If no version is provided, + the last version is retrieved. + :returns: the file content. + """ try: obj = record.files[str(key)].get_version(version_id=version_id) return self.make_response(obj=obj or abort(404)) @@ -307,7 +367,14 @@ def get(self, pid, record, key, version_id, **kwargs): @pass_record @need_record_permission('update_permission_factory') def put(self, pid, record, key): - """Handle PUT deposit files.""" + """Handle the file rename through the PUT deposit file. + + Permission required: `update_permission_factory`. + + :param pid: Pid object (from url). + :param record: Record object resolved from the pid. + :param key: Unique identifier for the file in the deposit. + """ try: data = json.loads(request.data.decode('utf-8')) new_key = data['filename'] @@ -329,7 +396,14 @@ def put(self, pid, record, key): @pass_record @need_record_permission('update_permission_factory') def delete(self, pid, record, key): - """Handle DELETE deposit files.""" + """Handle DELETE deposit file. + + Permission required: `update_permission_factory`. + + :param pid: Pid object (from url). + :param record: Record object resolved from the pid. + :param key: Unique identifier for the file in the deposit. + """ try: del record.files[str(key)] record.commit() diff --git a/invenio_deposit/views/ui.py b/invenio_deposit/views/ui.py index 0d686ba..d61e8f7 100644 --- a/invenio_deposit/views/ui.py +++ b/invenio_deposit/views/ui.py @@ -31,7 +31,13 @@ def create_blueprint(endpoints): - """Create Invenio-Deposit-UI blueprint.""" + """Create Invenio-Deposit-UI blueprint. + + See: :data:`invenio_deposit.config.DEPOSIT_RECORDS_UI_ENDPOINTS`. + + :param endpoints: List of endpoints configuration. + :returns: The configured blueprint. + """ from invenio_records_ui.views import create_url_rule blueprint = Blueprint(