Skip to content

Commit

Permalink
views: implementation of records_files views
Browse files Browse the repository at this point in the history
* Closes #71

Co-authored-by: Diego Rodriguez <diego.rodriguez@cern.ch>
  • Loading branch information
Glignos and Diego Rodriguez committed May 24, 2019
1 parent 989753b commit 1d2f879
Show file tree
Hide file tree
Showing 11 changed files with 542 additions and 41 deletions.
14 changes: 14 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
..
This file is part of Invenio.
Copyright (C) 2019 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.



Configuration
=============

.. automodule:: invenio_records_files.config
:members:
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Invenio-Records-Files.
:maxdepth: 2

installation
configuration
usage


Expand Down
127 changes: 94 additions & 33 deletions invenio_records_files/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Integration of records and files for Invenio.
r"""Integration of records and files for Invenio.
Invenio-Records-Files provides basic API for integrating
`Invenio-Records <https://invenio-records.rtfd.io/>`_
Expand All @@ -32,20 +32,20 @@
>>> ext_filesrest = InvenioFilesREST(app)
>>> ext_records = InvenioRecords(app)
In order for the following examples to work, you need to work within an
In order for the following examples to work, you need to work within a
Flask application context so let's push one:
>>> ctx = app.app_context()
>>> ctx.push()
Also, for the examples to work we need to create the database and tables (note,
in this example we use an in-memory SQLite database):
Also, for the examples to work you need to create the database and tables
(note, in this example you use an in-memory SQLite database):
>>> from invenio_db import db
>>> db.create_all()
Last, since we're managing files, we need to create a base location. Here we
will create a location in a temporary directory:
Lastly, since you're managing files, you need to create a default location.
Here you will create a location in a temporary directory:
>>> import tempfile
>>> tmppath = tempfile.mkdtemp()
Expand Down Expand Up @@ -80,29 +80,25 @@
>>> bucket = Bucket.create()
>>> record_buckets = RecordsBuckets.create(record=record.model, bucket=bucket)
Normally the bucket creation and bucket to record assignment is done by an
external module (e.g. `Invenio-Deposit <https://invenio-deposit.rtfd.io>`_ is
one example of this).
The ``files`` property now has a value and we can e.g. ask for the number of
The ``files`` property now has a value and you can e.g. get the number of
files:
>>> len(record.files)
0
Creating files
--------------
We are now ready to create our first file using the Invenio-Records-Files API:
You are now ready to create you first file using the Invenio-Records-Files API:
>>> from six import BytesIO
>>> record.files['hello.txt'] = BytesIO(b'Hello, World')
In above example we create a file named ``hello.txt`` and assign a *stream*
like object which will be saved as a new object in the bucket.
In the above example you created a file named ``hello.txt`` as a new object
in the record bucket.
Accessing files
---------------
We can access the just stored file through the same API:
You can access the above file through the same API:
>>> len(record.files)
1
Expand All @@ -114,21 +110,20 @@
Metadata for files
------------------
Besides creating files we can also assign metadata to files:
Besides creating files you can also assign metadata to files:
>>> fileobj['filetype'] = 'txt'
>>> print(record.files['hello.txt']['filetype'])
txt
Certain key names are however reserved and cannot be used for **setting**
metadata:
Certain key names are however reserved:
>>> fileobj['key'] = 'test'
Traceback (most recent call last):
...
KeyError: 'key'
The reserved key names are all the properties which exists on
The reserved key names are all the properties which already exist in
:py:class:`invenio_files_rest.models.ObjectVersion`.
You can however still use the reserved keys for **getting** metadata:
Expand All @@ -138,7 +133,7 @@
Dumping files
-------------
You can make a dictionary of all files
You can make a dictionary of all files:
>>> dump = record.files.dumps()
>>> for k in sorted(dump[0].keys()):
Expand All @@ -151,34 +146,98 @@
size
version_id
This is also how files are stored inside the record in the ``_files`` key:
>>> print(record['_files'][0]['key'])
hello.txt
Extracting file from record
---------------------------
Some Invenio modules, e.g.
`Invenio-Previewer <https://invenio-previewer.rtfd.io/>`_ need to extract a
file from the record and be resilient towards exactly which record class is
being used. This can be done using the record file factory:
Retrieve files from a record
----------------------------
Invenio-Records-Files provides an utility to retrieve files of a given
record.
>>> from invenio_records_files.utils import record_file_factory
>>> fileobj = record_file_factory(None, record, 'hello.txt')
>>> print(fileobj.key)
hello.txt
If a file does not exist or the record class has no files property, the
factory will return ``None``:
>>> fileobj = record_file_factory(None, record, 'invalid')
>>> fileobj is None
True
Some other Invenio modules such as
`Invenio-Previewer <https://invenio-previewer.rtfd.io/>`_
already use it to programmatically access record's files.
Integration with Invenio REST API
---------------------------------
Invenio-Records-Files provides REST endpoints to retrieve or upload the files
of a record:
.. code-block:: console
# Upload a file named example.txt to the record with pid of 1
$ curl -X PUT http://localhost:5000/api/records/1/files/example.txt \
--data-binary @example.txt
# Get the list of files for this record
$ curl -X GET http://localhost:5000/api/records/1/files/
# Download the file named ``example.txt`` of this record
$ curl -X GET http://localhost:5000/api/records/1/files/example.txt \
-o example.txt
Invenio-Records-Files provides the same REST endpoints for bucket and objects
available in `Invenio-Files-REST <https://invenio-files-rest.readthedocs.io/en/
latest/_modules/invenio_files_rest/views.html>`__,
by implicitly injecting the record's bucket ID to the request.
For example given the following configuration:
.. code-block:: python
# Invenio-Records-REST
RECORDS_REST_ENDPOINTS = {
recid: {
# ...,
item_route='/records/<pid(recid):pid_value>',
#...,
},
docid: {
# ...,
item_route='/documents/<pid(docid):pid_value>',
#...,
}
}
# Invenio-Records-Files
RECORDS_FILES_REST_ENDPOINTS = {
'RECORDS_REST_ENDPOINTS': {
'recid': '/files',
'docid': '/doc-files',
},
'DEPOSIT_REST_ENDPOINTS': {
'depid': '/deposit-files,
}
}
You can access the files of a record with PID ``1`` using the
URL ``/api/records/1/files`` or of a document with PID ``123`` using
the URL ``/api/documents/123/doc-files``.
You can access a specific file, for instance ``example.txt``,
with the following URL ``/api/records/1/files/example.txt``.
Invenio-Records-Files endpoint offers the same functionality provided by
`Invenio-Files-REST API
<https://invenio-files-rest.readthedocs.io/en/latest/
api.html#module-invenio_files_rest.views>`__.
More information about handling files through the REST API can be found `here
<https://invenio-files-rest.readthedocs.io/en/latest/usage.html>`__.
Integration with Invenio-Records-UI
-----------------------------------
If you are using `Invenio-Records-UI <https://invenio-records-ui.RTFD.io/>`_,
If you are using `Invenio-Records-UI <https://invenio-records-ui.RTFD.io/>`__,
you can easily add new views by defining new endpoints into your
``RECORDS_UI_ENDPOINTS`` configuration. In particular, you can add the
``file_download_ui`` endpoint:
Expand All @@ -198,6 +257,8 @@

from __future__ import absolute_import, print_function

from invenio_records_files.ext import InvenioRecordsFiles

from .version import __version__

__all__ = ('__version__', )
__all__ = ('__version__', 'InvenioRecordsFiles')
12 changes: 11 additions & 1 deletion invenio_records_files/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,22 @@ class FilesMixin(object):
def _create_bucket(self):
"""Return an instance of ``Bucket`` class.
.. note:: Reimplement in children class for custom behavior.
.. note:: Override for custom behavior.
:returns: Instance of :class:`invenio_files_rest.models.Bucket`.
"""
return None

def resolve_files_to_bucket_id(self, files):
"""Get bucket ID from record.
.. note:: Override for custom behavior.
:returns: UUID of record's bucket.
"""
# Passing the record's files, not to query for it again.
return files.bucket.id

@property
def files(self):
"""Get files iterator.
Expand Down
45 changes: 45 additions & 0 deletions invenio_records_files/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2019 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Invenio-Records-Files configuration."""

RECORDS_FILES_REST_ENDPOINTS = {}
"""REST endpoints configuration.
You can configure the REST API endpoint to access the record's
files as follows:
.. code-block:: python
RECORDS_FILES_REST_ENDPOINTS = {
'<*_REST_ENDPOINTS>': {
'<record_pid_type>': '<endpoint_suffix>',
}
}
* ``<*_REST_ENDPOINTS>`` corresponds to `Invenio-Records-REST endpoint
configurations names <https://invenio-records-rest.readthedocs.io/en/
latest/configuration.html#invenio_records_rest.config
.RECORDS_REST_ENDPOINTS>`_ you have defined in your application.
* ``<record_pid_type>`` is the PID type associated to the record defined in
your `Invenio-Records-REST <https://invenio-records-rest.readthedocs.io/en/
latest/configuration.html#invenio_records_rest.
config.RECORDS_REST_ENDPOINTS>`_ like configuration.
* ``<endpoint_suffix>`` is the endpoint name to access the record's files.
.. code-block:: console
{'recid': '/myawesomefiles'} -> /records/1/myawesomefiles
An example of this configuration is provided in the
`Integration with Invenio REST API
<usage.html#integration-with-invenio-rest-api>`_ section of the documentation.
"""
34 changes: 34 additions & 0 deletions invenio_records_files/ext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2019 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Flask extension for the Invenio-Records-Files."""


from __future__ import absolute_import, print_function

from invenio_records_files import config


class InvenioRecordsFiles(object):
"""Invenio-Records-Files extension."""

def __init__(self, app=None, **kwargs):
"""Extension initialization."""
if app:
self.init_app(app, **kwargs)

def init_app(self, app):
"""Flask application initialization."""
self.init_config(app)
app.extensions['invenio-records-files'] = self

def init_config(self, app):
"""Initialize configuration."""
for k in dir(config):
if k.startswith('RECORDS_FILES_'):
app.config.setdefault(k, getattr(config, k))
Loading

0 comments on commit 1d2f879

Please sign in to comment.