models.py 7.95 KB
Newer Older
1
2
import json
import os
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
3
4
5
import logging

from builtins import str
6
from collections import defaultdict
7
from pyld import jsonld
8
from flask import Response as FlaskResponse
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
9

10

11
12
13
14
15
16
class Leaf(dict):
    _prefix = None
    _frame = {}
    _context = {}

    def __init__(self,
17
18
19
20
21
22
23
24
25
                 *args,
                 **kwargs):

        id = kwargs.pop("id", None)
        context = kwargs.pop("context", self._context)
        vocab = kwargs.pop("vocab", None)
        prefix = kwargs.pop("prefix", None)
        frame = kwargs.pop("frame", None)
        super(Leaf, self).__init__(*args, **kwargs)
26
        if context is not None:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
27
            self.context = context
28
29
        if frame is not None:
            self._frame = frame
30
        self._prefix = prefix
31
        self.id = id
32
33

    def __getattr__(self, key):
34
35
36
37
38
39
40
        try:
            return object.__getattr__(self, key)
        except AttributeError:
            try:
                return super(Leaf, self).__getitem__(self._get_key(key))
            except KeyError:
                raise AttributeError()
41

42
43
44
45
46
47
    def __setattr__(self, key, value):
        try:
            object.__getattr__(self, key)
            object.__setattr__(self, key, value)
        except AttributeError:
            key = self._get_key(key)
48
49
50
51
            if key == "@context":
                value = self.get_context(value)
            elif key == "@id":
                value = self.get_id(value)
52
53
54
            if key[0] == "_":
                object.__setattr__(self, key, value)
            else:
55
56
57
58
59
60
61
62
63
64
                if value is None:
                    try:
                        super(Leaf, self).__delitem__(key)
                    except KeyError:
                        pass
                else:
                    super(Leaf, self).__setitem__(key, value)

    def get_id(self, id):
        """
65
        Get id, dealing with prefixes
66
        """
67
68
69
        # This is not the most elegant solution to change the @id attribute,
        # but it is the quickest way to have it included in the dictionary
        # without extra boilerplate.
70
71
72
73
        if id and self._prefix and ":" not in id:
            return "{}{}".format(self._prefix, id)
        else:
            return id
74

75
    def __delattr__(self, key):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
76
77
78
79
        if key in self.__dict__:
            del self.__dict__[key]
        else:
            super(Leaf, self).__delitem__(self._get_key(key))
80

81
    def _get_key(self, key):
82
83
84
        if key[0] == "_":
            return key
        elif key in ["context", "id"]:
85
            return "@{}".format(key)
86
87
        else:
            return key
88

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
89
90
91
92
93
94
95
96
97
    @staticmethod
    def get_context(context):
        if isinstance(context, list):
            contexts = []
            for c in context:
                contexts.append(Response.get_context(c))
            return contexts
        elif isinstance(context, dict):
            return context
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
98
        elif isinstance(context, str):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
99
100
101
102
103
104
            try:
                with open(context) as f:
                    return json.loads(f.read())
            except IOError:
                return context

105
    def compact(self):
106
        return jsonld.compact(self, self.get_context(self.context))
107
108
109
110
111
112
113
114

    def frame(self, frame=None, options=None):
        if frame is None:
            frame = self._frame
        if options is None:
            options = {}
        return jsonld.frame(self, frame, options)

115
116
117
118
    def jsonld(self, frame=None, options=None,
               context=None, removeContext=None):
        if removeContext is None:
            removeContext = Response._context  # Loop?
119
120
121
        if frame is None:
            frame = self._frame
        if context is None:
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
            context = self.context
        else:
            context = self.get_context(context)
        # For some reason, this causes errors with pyld
        # if options is None:
            # options = {"expandContext": context.copy() }
        js = self
        if frame:
            logging.debug("Framing: %s", json.dumps(self, indent=4))
            logging.debug("Framing with %s", json.dumps(frame, indent=4))
            js = jsonld.frame(js, frame, options)
            logging.debug("Result: %s", json.dumps(js, indent=4))
            logging.debug("Compacting with %s", json.dumps(context, indent=4))
            js = jsonld.compact(js, context, options)
            logging.debug("Result: %s", json.dumps(js, indent=4))
        if removeContext == context:
            del js["@context"]
        return js

    def to_JSON(self, removeContext=None):
        return json.dumps(self.jsonld(removeContext=removeContext),
143
144
145
                          default=lambda o: o.__dict__,
                          sort_keys=True, indent=4)

146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
    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
        """
        js = self.jsonld()
        headers = None
        if in_headers:
            ctx = js["@context"]
            headers = {
                "Link": ('<%s>;'
                         'rel="http://www.w3.org/ns/json-ld#context";'
                         ' type="application/ld+json"' % url)
            }
            del js["@context"]
162
        return FlaskResponse(json.dumps(js, indent=4),
163
164
165
                             status=self.get("status", 200),
                             headers=headers,
                             mimetype="application/json")
166

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

168
class Response(Leaf):
169
170
171
172
173
174
175
176
177
178
179
180
    _context = Leaf.get_context("{}/context.jsonld".format(
        os.path.dirname(os.path.realpath(__file__))))
    _frame = {
        "@context": _context,
        "analysis": {
            "@explicit": True,
            "maxPolarityValue": {},
            "minPolarityValue": {},
            "name": {},
            "version": {},
        },
        "entries": {}
181
182
    }

183
184
185
    def __init__(self, *args, **kwargs):
        context = kwargs.pop("context", None)
        frame = kwargs.pop("frame", None)
186
        if context is None:
187
188
189
190
191
192
193
194
195
196
197
198
199
            context = self._context
        self.context = context
        super(Response, self).__init__(
            *args, context=context, frame=frame, **kwargs)
        if self._frame is not None and "entries" in self._frame:
            self.analysis = []
            self.entries = []

    def jsonld(self, frame=None, options=None, context=None, removeContext={}):
        return super(Response, self).jsonld(frame,
                                            options,
                                            context,
                                            removeContext)
200
201
202


class Entry(Leaf):
203
    _context = {
204
205
        "@vocab": ("http://persistence.uni-leipzig.org/"
                   "nlp2rdf/ontologies/nif-core#")
206
207

    }
208

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
209
    def __init__(self, text=None, emotion_sets=None, opinions=None, **kwargs):
210
211
212
        super(Entry, self).__init__(**kwargs)
        if text:
            self.text = text
213
214
        self.emotionSets = emotion_sets if emotion_sets else []
        self.opinions = opinions if opinions else []
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
215

216

217
class Opinion(Leaf):
218
219
220
    _context = {
        "@vocab": "http://www.gsi.dit.upm.es/ontologies/marl/ns#"
    }
221

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
222
    def __init__(self, polarityValue=None, hasPolarity=None, *args, **kwargs):
223
        super(Opinion, self).__init__(*args,
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
224
225
                                      **kwargs)
        if polarityValue is not None:
226
            self.hasPolarityValue = polarityValue
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
227
228
        if hasPolarity is not None:
            self.hasPolarity = hasPolarity
229
230
231


class EmotionSet(Leaf):
232
    _context = {}
233

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
234
    def __init__(self, emotions=None, *args, **kwargs):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
235
236
        if not emotions:
            emotions = []
237
        super(EmotionSet, self).__init__(context=EmotionSet._context,
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
238
239
                                         *args,
                                         **kwargs)
240
        self.emotions = emotions or []
241

242

243
class Emotion(Leaf):
244
245
    _context = {}

246

247
class Error(Leaf):
248
249
250
251
252
    # A better pattern would be this:
    # http://flask.pocoo.org/docs/0.10/patterns/apierrors/
    _frame = {}
    _context = {}

253
    def __init__(self, *args, **kwargs):
254
        super(Error, self).__init__(*args, **kwargs)