models.py 7.46 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

21

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

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


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
35
36
37
    with open(schema_path) as f:
        return jsonref.load(f, base_uri=schema_uri)
    
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
38
39
40
41
42
        
base_schema = read_schema(DEFINITIONS_FILE)
logging.debug(base_schema)

class Context(dict):
43

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
44
    @staticmethod
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
45
46
47
48
49
    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
50
51
            contexts = []
            for c in context:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
52
                contexts.append(Context.load(c))
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
53
54
            return contexts
        elif isinstance(context, dict):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
55
            return Context(context)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
56
        elif isinstance(context, string_types):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
57
58
            try:
                with open(context) as f:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
59
                    return Context(json.loads(f.read()))
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
60
61
            except IOError:
                return context
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
62
63
        else:
            raise AttributeError('Please, provide a valid context')                
64

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
65
66
67
base_context = Context.load(CONTEXT_PATH)

class SenpyMixin(object):
68
    context = base_context["@context"]
69

70
71
    def flask(self,
              in_headers=False,
72
73
              headers=None,
              **kwargs):
74
        """
75
76
77
        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.
78
        """
79
80
81
        headers = headers or {}
        kwargs["with_context"] = True
        js = self.jsonld(**kwargs)
82
        if in_headers:
83
84
85
            url = js["@context"]
            del js["@context"]
            headers.update({
86
87
88
                "Link": ('<%s>;'
                         'rel="http://www.w3.org/ns/json-ld#context";'
                         ' type="application/ld+json"' % url)
89
90
            })
        return FlaskResponse(json.dumps(js, indent=2, sort_keys=True),
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
91
                             status=getattr(self, "status", 200),
92
93
                             headers=headers,
                             mimetype="application/json")
94

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
95

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    def serializable(self):
        def ser_or_down(item):
           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
        return ser_or_down(self._plain_dict())
111

112

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

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
116
        if  with_context:
117
118
119
120
121
            context = []
            if context_uri:
                context = context_uri
            else:
                context = self.context.copy()
122
            if hasattr(self, 'prefix'):
123
124
125
126
127
                # 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"

128
                prefix_context = {"@base": self.prefix}
129
130
131
132
133
                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
134
        return ser
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
135

136

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

142
143
144
145
146
147
    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
J. Fernando Sánchez committed
148
149
    
class SenpyModel(SenpyMixin, dict):
150

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
151
    schema = base_schema
152

153
    def __init__(self, *args, **kwargs):
154
155
156
        self.id = kwargs.pop('id', '{}_{}'.format(type(self).__name__,
                                                    time.time()))

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

159
160
161
162
163
        for obj in [self.schema,]+self.schema.get('allOf', []):
            for k, v in obj.get('properties', {}).items():
                if 'default' in v:
                    temp[k] = copy.deepcopy(v['default'])

164
165
166
167
168
        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
169
170
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
198
199
200
201
        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))
        
    
    def _plain_dict(self):
        d =  { k: v for (k,v) in self.items() if k[0] != "_"}
202
        d["@id"] = d.pop('id')
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
        return d

class Response(SenpyModel):
    schema = read_schema('response.json')

class Results(SenpyModel):
    schema = read_schema('results.json')

class Entry(SenpyModel):
    schema = read_schema('entry.json')

class Sentiment(SenpyModel):
    schema = read_schema('sentiment.json')

class Analysis(SenpyModel):
    schema = read_schema('analysis.json')

class EmotionSet(SenpyModel):
    schema = read_schema('emotionSet.json')

223
224
225
class Emotion(SenpyModel):
    schema = read_schema('emotion.json')

226
227
228
class EmotionModel(SenpyModel):
    schema = read_schema('emotionModel.json')

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
229
230
231
232
233
234
class Suggestion(SenpyModel):
    schema = read_schema('suggestion.json')

class PluginModel(SenpyModel):
    schema = read_schema('plugin.json')

235
236
237
238
239
240
class EmotionPluginModel(SenpyModel):
    schema = read_schema('plugin.json')

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

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
class Plugins(SenpyModel):
    schema = read_schema('plugins.json')

class Error(SenpyMixin, BaseException ):

    def __init__(self, message, status=500, params=None, errors=None, *args, **kwargs):
        self.message = message
        self.status = status
        self.params = params or {}
        self.errors = errors or ""

    def _plain_dict(self):
       return self.__dict__

    def __str__(self):
       return str(self.jsonld())