Backend Documentation

General Framework

The backend for this extension is built as a Jupyter Server Extension. The project entry points are specified with the pyproject.toml file in the root directory. These point to the zenodo_jupyterlab.server module, which contains the extenion.py and __init__.py files which run the function that sets up the API handlers defined within other files in that directory. This guide will go through each section, with explanation of functionality.

Files in zenodo_jupyterlab/server/

extension.py

_load_jupyter_server_extenion is a basic function that calls on setup_handlers which is defined in handlers.py and passes the server_app.web_app object. This is automatically passed via the server extension points.

__init__.py

Defines the _jupyter_server_extension_points function that signals to the primary extension in pyproject.toml how to access the server extension and to build when it built.

handlers.py

This file generates the API endpoints for use by the frontend components. All handlers inherit from jupyter_server.base.handlers.APIHandler (except the XSRFTokenHandler inherits from JupyterHandler). For simplicity, parameters are placed within the function definitions here, though they are accessed via the APIHandler.get_argument() function because they are URI encoded in the API calls. In addition, return statements are defined plainly here, though they are actually returned via the APIHandler.finish() function.

class EnvHandler

Interacts with environmental variables in the Jupyter instance. Exploits the os module.

get(env_var: string)

Simple function to return the value of a stored environmental variable. Returns a dict containing the variable name and value.

Returns: {env_var: *value*}

post(key: string, value: string)

Simple function to set the value of an environmental variable. Returns a dict with the parameters.

Returns: `{key, value}

class XSRFTokenHandler

Note: this inherits from JupyterHandler, not APIHandler.

get(self: JupyterHandler)

Accesses JupyterHandler.xsrf_token and returns it.

Returns: {‘xsrfToken’: xsrf_token}

class Search RecordHandler

Handler designed to interact with searchRecords function defined in search.py.

get(search_field: string, page: int, communities: string)

Awaits response from searchRecords after passing all of the arguments and returns the list of corresponding records (max size: 25).

Returns: {‘records’: response}

class SearchCommunityHandler

Handler designed to interact with searchCommunities function defined in search.py.

get(search_field: string, page: int)

Awaits response from searchCommunities after passing all of the arguments and returns the list of corresponding communities (max size: 25).

Returns: {‘communities’: response}

class RecordInfoHandler

Handler designed to interact with recordInformation function defined in search.py.

get(record-id: string)

Awaits response from recordInformation after passing the record-id and returns the desired data.

Returns: {‘data’: response}

class FileBrowserHandler

Interacts with environmental information about the folder and files in the $HOME directory and child directories.

`get(path: string)

Pulls the Jupyter instance home directory from the $HOME environmental variable. Then appends the path passed to this to generate a relative path and verifies whether it exists (if not, returns an “error”). Exploits the os.listdir function to iterate through entries within the folder. Note: it explicitly ignores all entries that start with “.”, though, in principle, these should be excluded by listdir. It then accumulates a list of directories of entry information:

entry = {
    "name": file name,
    "type": directory or file,
    "path": relative path from home directory,
    "modified": isoformatted timestamp of last modification,
    "size": size
}

This information is all drawn from the returned data from os.listdir.

Returns: {‘entries’: list of entry dictionaries}

class ZenodoAPIHandler

Interacts with all functions that make calls to the eossr.api.zenodo.ZenodoAPI object. They are contained within this class to limit the need of generating new ZenodoAPI objects with each interaction.

property zAPI

Once logged in, this will hold a ZenodoAPI object initialized with the input access token and sandbox boolean. As long as they do not log in again, this object will remain for the lifetime of the Jupyter instance.

post(form_data: json dictionary)

Takes in a form_data JSON dictionary, that contains at least an action entry, which specifies which code to run.

if action == 'check-connection'

Verifies the validity of a Zenodo access token via the checkZenodoConnection function defined in testConnection.py. If valid, sets zAPI to an initialized ZenodoAPI object. Returns the response from the called function.

Returns: {‘status’: response}

if action == 'upload'

Interacts with the upload function defined in upload.py. If zAPI is not yet initialized, the function returns a status of “Please log in before attempting to upload.” Otherwise, returns the response from the function.

Returns: {‘status’: response}

class ServerInfoHandler

get

Retrieves the home directory.

Returns: {‘root_dir’: home directory}

setup_handlers(web_app)

Defines the API endpoints for access from the frontend. Builds the urls off of the “web_app” base path and “zenodo-jupyterlab”. Thus all handlers are of the form: base_path + “zenodo-jupyterlab-action”. This function then adds these endpoints to the web_app.

This files handles all search requests to Zenodo via the eOSSR library.

searchRecords(search_field: string, page: int, **kwargs)

Calls the eossr.api.zenodo.search_records with the given arguments, as well as restricts the size of the response to 25 (i.e. passes size = 25 as well). Parses the returned list of eossr.api.zenodo.Record objects and returns a list of the following kinds of dictionaries:

record = {
    "id": Record ID,
    "title": Record title,
    "date": Date Published,
    "resource_type": Records resource type (from within the metadata)
}

If this call to eOSSR fails, the function simply returns ["failed"].

Returns: [list of records]

searchCommunities(search_field: string, page: int)

Calls the eossr.api.zenodo.search_communities with the given arguments, as well as restricts the size of the response to 25 (i.e. passes size = 25 as well). Parses the returned list of dictionaries and returns a list of the following kinds of dictionaries:

community = {
    "id": Community ID,
    "title": Community title,
    "date": Date Published,
}

If this call to eOSSR fails, the function simply returns ["failed"].

Returns: [list of communities]

recordInformation(recordID: string)

Returns more specific information about a specified record via eossr.api.zenodo.get_record. Parses the returned data from this function and creates the following dictionary:

record= {
    'authors': authors with affiliations as listed in the record,
    'filelist': the list of files (full download links) attached to record
}

Note: The existing information such as title and id are still held on the frontend in the tabular display, so no need to repass that information. Secondary note: if this call fails, this function returns {“status”: “failed”}

Returns: record

testConnection.py

File devoted to validating Zenodo access tokens.

checkZenodoConnection

Extracts the access token and sandbox boolean from the environmental variables ZENODO_API_KEY and ZENODO_SANDBOX, respectively. Parses the string value of the sandbox boolean into a Python boolean (stored as a string due to the mismatch is syntax between Typescript and Python booleans). Creates a eossr.api.zenodo.ZenodoAPI object with those extracted variables as arguments and stores it in zAPI. Then, uses the eossr.api.zenodo.ZenodoAPI.query_user_deposits function and extracts the status from this response, which indicates whether or not the access token is valid or if a connection can be made to the Zenodo REST API. If either stage fails, zAPI is initialized to None and the query status is set to 0.

Returns: status code of the query, zAPI

upload.py

Devoted to generating and populating new Zenodo record deposits.

createDeposit(zAPI: eossr.api.zenodo.ZenodoAPI)

Uses eossr.api.zenodo.ZenodoAPI.create_new_deposit to create an empty deposit. Note: whether or not this deposit exists on Zenodo or Zenodo Sandbox is entirely dependent on what option the user selected when logging in.

Returns: ID of the newly created record

createMetadata(zAPI: eossr.api.zenodo.ZenodoAPI, recordID: int, form_data: FormData object)

Work in Progress
Extracts title from form_data and creates a JSON dict as follows:

json_metadata = {
    "title": given title
}

Uses eossr.api.zenodo.ZenodoAPI.set_deposit_metadata to take in the recordID and the JSON data and add this metadata to the existing Zenodo object.

Returns: response

upload(zAPI: eossr.api.zenodo.ZenodoAPI, form_data: FormData object)

Verifies if zAPI has been initialized; if not, returns None. Calls createDeposit and passes zAPI and captures returns record ID. Then uses this record ID, form_data, and the zAPI to call createMetadata.

Returns: “Success” if response doesn’t equal None