models.py 7.44 KB
Newer Older
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
1
2
3
4
5
6
7
'''
Senpy Models. 

This implementation should mirror the JSON schema definition.
For compatibility with Py3 and for easier debugging, this new version drops introspection
and adds all arguments to the models.
'''
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
8
9
10
from __future__ import print_function
from six import string_types

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
11
12
import time
import copy
13
14
import json
import os
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
15
import logging
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
16
17
import jsonref
import jsonschema
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
18

19
from flask import Response as FlaskResponse
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
20

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
21
DEFINITIONS_FILE = 'definitions.json'
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
22
23
24
CONTEXT_PATH = os.path.join(
    os.path.dirname(os.path.realpath(__file__)), 'schemas', 'context.jsonld')

25

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
26
27
28
29
def get_schema_path(schema_file, absolute=False):
    if absolute:
        return os.path.realpath(schema_file)
    else:
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
30
31
32
        return os.path.join(
            os.path.dirname(os.path.realpath(__file__)), 'schemas',
            schema_file)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
33
34
35
36
37


def read_schema(schema_file, absolute=False):
    schema_path = get_schema_path(schema_file, absolute)
    schema_uri = 'file://{}'.format(schema_path)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
38
39
    with open(schema_path) as f:
        return jsonref.load(f, base_uri=schema_uri)
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
40
41


J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
42
43
44
base_schema = read_schema(DEFINITIONS_FILE)
logging.debug(base_schema)

45

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
46
class Context(dict):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
47
    @staticmethod
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
48
49
50
51
52
    def load(context):
        logging.debug('Loading context: {}'.format(context))
        if not context:
            return context
        elif isinstance(context, list):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
53
54
            contexts = []
            for c in context:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
55
                contexts.append(Context.load(c))
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
56
57
            return contexts
        elif isinstance(context, dict):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
58
            return Context(context)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
59
        elif isinstance(context, string_types):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
60
61
            try:
                with open(context) as f:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
62
                    return Context(json.loads(f.read()))
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
63
64
            except IOError:
                return context
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
65
        else:
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
66
67
            raise AttributeError('Please, provide a valid context')

68

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
69
70
base_context = Context.load(CONTEXT_PATH)

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
71

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
72
class SenpyMixin(object):
73
    context = base_context["@context"]
74

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
75
    def flask(self, in_headers=False, headers=None, **kwargs):
76
        """
77
78
79
        Return the values and error to be used in flask.
        So far, it returns a fixed context. We should store/generate different
        contexts if the plugin adds more aliases.
80
        """
81
82
83
        headers = headers or {}
        kwargs["with_context"] = True
        js = self.jsonld(**kwargs)
84
        if in_headers:
85
86
87
            url = js["@context"]
            del js["@context"]
            headers.update({
88
89
90
                "Link": ('<%s>;'
                         'rel="http://www.w3.org/ns/json-ld#context";'
                         ' type="application/ld+json"' % url)
91
            })
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
92
93
94
95
96
97
        return FlaskResponse(
            json.dumps(
                js, indent=2, sort_keys=True),
            status=getattr(self, "status", 200),
            headers=headers,
            mimetype="application/json")
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
98

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
99
100
    def serializable(self):
        def ser_or_down(item):
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
101
102
103
104
105
106
107
108
109
110
111
112
            if hasattr(item, 'serializable'):
                return item.serializable()
            elif isinstance(item, dict):
                temp = dict()
                for kp in item:
                    vp = item[kp]
                    temp[kp] = ser_or_down(vp)
                return temp
            elif isinstance(item, list):
                return list(ser_or_down(i) for i in item)
            else:
                return item
113

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
114
        return ser_or_down(self._plain_dict())
115

116
    def jsonld(self, with_context=True, context_uri=None):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
117
        ser = self.serializable()
118

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
119
        if with_context:
120
121
122
123
124
            context = []
            if context_uri:
                context = context_uri
            else:
                context = self.context.copy()
125
            if hasattr(self, 'prefix'):
126
127
128
129
130
                # This sets @base for the document, which will be used in
                # all relative URIs will. For example, if a uri is "Example" and
                # prefix =s "http://example.com", the absolute URI after expanding
                # with JSON-LD will be "http://example.com/Example"

131
                prefix_context = {"@base": self.prefix}
132
133
134
135
136
                if isinstance(context, list):
                    context.append(prefix_context)
                else:
                    context = [context, prefix_context]
            ser["@context"] = context
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
137
        return ser
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
138

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
139
    def to_JSON(self, *args, **kwargs):
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
140
        js = json.dumps(self.jsonld(*args, **kwargs), indent=4, sort_keys=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
141
142
        return js

143
144
145
146
147
148
    def validate(self, obj=None):
        if not obj:
            obj = self
        if hasattr(obj, "jsonld"):
            obj = obj.jsonld()
        jsonschema.validate(obj, self.schema)
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
149
150


J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
151
class SenpyModel(SenpyMixin, dict):
152

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
153
    schema = base_schema
154

155
    def __init__(self, *args, **kwargs):
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
156
157
        self.id = kwargs.pop('id', '{}_{}'.format(
            type(self).__name__, time.time()))
158

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
159
160
        temp = dict(*args, **kwargs)

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
161
        for obj in [self.schema, ] + self.schema.get('allOf', []):
162
163
164
165
            for k, v in obj.get('properties', {}).items():
                if 'default' in v:
                    temp[k] = copy.deepcopy(v['default'])

166
167
168
169
170
        for i in temp:
            nk = self._get_key(i)
            if nk != i:
                temp[nk] = temp[i]
                del temp[i]
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
        if 'context' in temp:
            context = temp['context']
            del temp['context']
            self.__dict__['context'] = Context.load(context)
        super(SenpyModel, self).__init__(temp)

    def _get_key(self, key):
        key = key.replace("__", ":", 1)
        return key

    def __setitem__(self, key, value):
        dict.__setitem__(self, key, value)

    def __delitem__(self, key):
        dict.__delitem__(self, key)

    def __getattr__(self, key):
        try:
            return self.__getitem__(self._get_key(key))
        except KeyError:
            raise AttributeError(key)

    def __setattr__(self, key, value):
        self.__setitem__(self._get_key(key), value)

    def __delattr__(self, key):
        self.__delitem__(self._get_key(key))
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
198

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
199
    def _plain_dict(self):
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
200
        d = {k: v for (k, v) in self.items() if k[0] != "_"}
201
        d["@id"] = d.pop('id')
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
202
203
        return d

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
204

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
205
206
207
class Response(SenpyModel):
    schema = read_schema('response.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
208

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
209
210
211
class Results(SenpyModel):
    schema = read_schema('results.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
212

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
213
214
215
class Entry(SenpyModel):
    schema = read_schema('entry.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
216

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
217
218
219
class Sentiment(SenpyModel):
    schema = read_schema('sentiment.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
220

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
221
222
223
class Analysis(SenpyModel):
    schema = read_schema('analysis.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
224

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
225
226
227
class EmotionSet(SenpyModel):
    schema = read_schema('emotionSet.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
228

229
230
231
class Emotion(SenpyModel):
    schema = read_schema('emotion.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
232

233
234
235
class EmotionModel(SenpyModel):
    schema = read_schema('emotionModel.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
236

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
237
238
239
class Suggestion(SenpyModel):
    schema = read_schema('suggestion.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
240

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
241
242
243
class PluginModel(SenpyModel):
    schema = read_schema('plugin.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
244

245
246
247
class EmotionPluginModel(SenpyModel):
    schema = read_schema('plugin.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
248

249
250
251
class SentimentPluginModel(SenpyModel):
    schema = read_schema('plugin.json')

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
252

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
253
254
255
256
class Plugins(SenpyModel):
    schema = read_schema('plugins.json')


J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
257
258
259
260
261
262
263
264
class Error(SenpyMixin, BaseException):
    def __init__(self,
                 message,
                 status=500,
                 params=None,
                 errors=None,
                 *args,
                 **kwargs):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
265
266
267
268
269
270
        self.message = message
        self.status = status
        self.params = params or {}
        self.errors = errors or ""

    def _plain_dict(self):
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
271
        return self.__dict__
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
272
273

    def __str__(self):
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
274
        return str(self.jsonld())