models.py 6.45 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
35
36
37
38
39
40
41
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)
    return jsonref.load(open(schema_path), base_uri=schema_uri)
        

base_schema = read_schema(DEFINITIONS_FILE)
logging.debug(base_schema)

class Context(dict):
42

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

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

66

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
67
68
class SenpyMixin(object):
    context = base_context
69

70
71
72
73
74
75
76
77
78
79
80
81
82
    def flask(self,
              in_headers=False,
              url="http://demos.gsi.dit.upm.es/senpy/senpy.jsonld"):
        """
        Return the values and error to be used in flask
        """
        headers = None
        if in_headers:
            headers = {
                "Link": ('<%s>;'
                         'rel="http://www.w3.org/ns/json-ld#context";'
                         ' type="application/ld+json"' % url)
            }
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
83
84
        return FlaskResponse(self.to_JSON(with_context=not in_headers),
                             status=getattr(self, "status", 200),
85
86
                             headers=headers,
                             mimetype="application/json")
87

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

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
    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())
104

105

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
106
107
    def jsonld(self, context=None, with_context=False):
        ser = self.serializable()
108

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
109
110
        if  with_context:
            ser["@context"] = self.context
111

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
112
        return ser
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
113

114

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
115
116
117
118
119
    def to_JSON(self, *args, **kwargs):
        js = json.dumps(self.jsonld(*args, **kwargs), indent=4,
                        sort_keys=True)
        return js

120
121
122
123
124
125
    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
126
127
    
class SenpyModel(SenpyMixin, dict):
128

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
129
    schema = base_schema
130

131
    def __init__(self, *args, **kwargs):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
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
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
        temp = dict(*args, **kwargs)

        reqs = self.schema.get('required', [])
        for i in reqs:
            if i not in temp:
                prop = self.schema['properties'][i]
                if 'default' in prop:
                    temp[i] = copy.deepcopy(prop['default'])
        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))
        
    
    @classmethod
    def from_base(cls, name):
        subschema = base_schema[name]
        return warlock.model_factory(subschema, base_class=cls)

    def _plain_dict(self):
        d =  { k: v for (k,v) in self.items() if k[0] != "_"}
        if hasattr(self, "id"):
            d["@id"] = self.id
        return d

    @property
    def id(self):
        if not hasattr(self, '_id'):
            self.__dict__["_id"] = '_:{}_{}'.format(type(self).__name__, time.time())
        return self._id

    @id.setter
    def id(self, value):
        self._id = value
    

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

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

    def jsonld(self, context=None, with_context=True):
        return super(Results, self).jsonld(context, with_context)

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')

class Suggestion(SenpyModel):
    schema = read_schema('suggestion.json')

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

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())