archive.py 6.12 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Copyright 2018 Markus Scheidgen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an"AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

15
16
17
18
19
"""
The archive API of the nomad@FAIRDI APIs. This API is about serving processed
(parsed and normalized) calculation data in nomad's *meta-info* format.
"""

20
from typing import Dict, Any
21
22
import os.path
from flask import send_file
23
from flask_restplus import abort, Resource
24
import json
25

26
27
import nomad_meta_info

28
from nomad.files import UploadFiles, Restricted
29

30
from .app import api
31
32
from .auth import login_if_available, create_authorization_predicate, \
    signature_token_argument, with_signature_token
33
34
35
from .common import calc_route

ns = api.namespace(
36
37
    'archive',
    description='Access archive data and archive processing logs.')
38
39


40
41
42
43
archive_file_request_parser = api.parser()
archive_file_request_parser.add_argument(**signature_token_argument)


44
45
@calc_route(ns, '/logs')
class ArchiveCalcLogResource(Resource):
46
    @api.doc('get_archive_logs')
47
    @api.response(404, 'The upload or calculation does not exist')
48
    @api.response(401, 'Not authorized to access the data.')
49
    @api.response(200, 'Archive data send', headers={'Content-Type': 'application/plain'})
50
    @api.expect(archive_file_request_parser, validate=True)
51
    @login_if_available
52
    @with_signature_token
53
    def get(self, upload_id, calc_id):
54
55
56
        """
        Get calculation processing log.

57
        Calcs are references via *upload_id*, *calc_id* pairs.
58
        """
59
        archive_id = '%s/%s' % (upload_id, calc_id)
60

61
        upload_files = UploadFiles.get(
62
            upload_id, is_authorized=create_authorization_predicate(upload_id, calc_id))
63

64
        if upload_files is None:
65
            abort(404, message='Upload %s does not exist.' % upload_id)
66
67
68

        try:
            return send_file(
69
                upload_files.archive_log_file(calc_id, 'rb'),
70
71
72
73
                mimetype='text/plain',
                as_attachment=True,
                attachment_filename='%s.log' % archive_id)
        except Restricted:
74
            abort(401, message='Not authorized to access %s/%s.' % (upload_id, calc_id))
75
76
        except KeyError:
            abort(404, message='Calculation %s does not exist.' % archive_id)
77
78
79
80


@calc_route(ns)
class ArchiveCalcResource(Resource):
81
    @api.doc('get_archive_calc')
82
    @api.response(404, 'The upload or calculation does not exist')
83
    @api.response(401, 'Not authorized to access the data.')
84
    @api.response(200, 'Archive data send')
85
    @api.expect(archive_file_request_parser, validate=True)
86
    @login_if_available
87
    @with_signature_token
88
    def get(self, upload_id, calc_id):
89
90
91
        """
        Get calculation data in archive form.

92
        Calcs are references via *upload_id*, *calc_id* pairs.
93
        """
94
        archive_id = '%s/%s' % (upload_id, calc_id)
95

96
        upload_file = UploadFiles.get(
97
            upload_id, is_authorized=create_authorization_predicate(upload_id, calc_id))
98

99
        if upload_file is None:
100
            abort(404, message='Archive %s does not exist.' % upload_id)
101
102
103

        try:
            return send_file(
104
                upload_file.archive_file(calc_id, 'rb'),
105
106
107
108
                mimetype='application/json',
                as_attachment=True,
                attachment_filename='%s.json' % archive_id)
        except Restricted:
109
            abort(401, message='Not authorized to access %s/%s.' % (upload_id, calc_id))
110
        except KeyError:
111
            abort(404, message='Calculation %s does not exist.' % archive_id)
112
113


114
115
@ns.route('/metainfo/<string:metainfo_package_name>')
@api.doc(params=dict(metainfo_package_name='The name of the metainfo package.'))
116
117
118
119
class MetainfoResource(Resource):
    @api.doc('get_metainfo')
    @api.response(404, 'The metainfo does not exist')
    @api.response(200, 'Metainfo data send')
120
    def get(self, metainfo_package_name):
121
122
123
124
        """
        Get a metainfo definition file.
        """
        try:
125
            return load_metainfo(metainfo_package_name), 200
126
        except FileNotFoundError:
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
            abort(404, message='The metainfo %s does not exist.' % metainfo_package_name)


metainfo_main_path = os.path.dirname(os.path.abspath(nomad_meta_info.__file__))


def load_metainfo(package_name: str, is_path: bool = False, loaded_packages: Dict[str, Any] = None) -> Dict[str, Any]:
    """
    Loads the given metainfo package and all its dependencies. Returns a dict with
    all loaded package_names and respective packages.

    Arguments:
        package_name: The name of the package, or a path to .nomadmetainfo.json file.
        is_path: True will interpret package_name as (relative) path.
        loaded_packages: Give a dict and the function will added freshly loaded packages
            to it and return it.
    """
    if loaded_packages is None:
        loaded_packages = {}

    if is_path:
        metainfo_path = package_name
    else:
        metainfo_path = os.path.join(metainfo_main_path, package_name)

    package_name = os.path.basename(package_name)

    if package_name in loaded_packages:
        return loaded_packages

    with open(metainfo_path, 'rt') as f:
        metainfo_json = json.load(f)

    loaded_packages[package_name] = metainfo_json

    for dependency in metainfo_json.get('dependencies', []):
        if 'relativePath' in dependency:
            dependency_path = os.path.join(
                os.path.dirname(metainfo_path), dependency['relativePath'])
        elif 'metainfoPath' in dependency:
            dependency_path = os.path.join(metainfo_main_path, dependency['metainfoPath'])
        else:
            raise Exception(
                'Invalid dependency type in metainfo package %s' % metainfo_path)

        load_metainfo(dependency_path, is_path=True, loaded_packages=loaded_packages)

    return loaded_packages