mmtinterface.py 11.7 KB
Newer Older
Theresa Pollinger's avatar
Theresa Pollinger committed
1
2
# to run mmt server : cf. https://docs.python.org/3/library/subprocess.html
import subprocess
3
import psutil
4
import time
5
# for now, running
6
7
# java -jar /home/freifrau/Desktop/masterarbeit/mmt/deploy/mmt.jar --file=server-interview.msl --keepalive
# or
Theresa Pollinger's avatar
Theresa Pollinger committed
8
9
10
# mmt
# extension info.kwarc.mmt.interviews.InterviewServer
# server on 8080
11
import threading
12
# TODO ask dennis on whether and how to delete modules
Theresa Pollinger's avatar
Theresa Pollinger committed
13
14

# to do http requests
15
# http://docs.python-requests.org/en/master/user/quickstart/
Theresa Pollinger's avatar
Theresa Pollinger committed
16
17
import requests
from requests.utils import quote
18
from contextlib import closing
Theresa Pollinger's avatar
Theresa Pollinger committed
19
#from urllib.parse import urlencode # is what we actually want to use
Theresa Pollinger's avatar
Theresa Pollinger committed
20
from lxml import etree
Theresa Pollinger's avatar
Theresa Pollinger committed
21
#from openmath import openmath
Theresa Pollinger's avatar
Theresa Pollinger committed
22

23

24
def start_mmt_server(port_number, mmtjar):
Theresa Pollinger's avatar
Theresa Pollinger committed
25
26
    p = subprocess.run(["/usr/bin/java", "-jar", mmtjar, # "--file=server-interview.msl",
                          "server", "on", str(port_number),
27
28
29
                          "--keepalive"],
                          stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    out = p.stdout
Theresa Pollinger's avatar
Theresa Pollinger committed
30
31
    if p.returncode != 0 and p.returncode != 130:
        raise MMTServerError("Could not start the MMT server, return code " + str(p.returncode) + ", " + str(out))
32
33
34
35
36
    #outs, errs = p.communicate()
    #if outs is not None:
        #print("MMT server terminated before rest of program, ")  # this just wont stay alive!!!
        #p.terminate()
        #raise MMTServerError("MMT server terminated before rest of program, ")  # + p.stdout)
37
38


39
40
def start_mmt_extension(port_number, mmtjar, timeout=3.0):
    time.sleep(0.1)  # hope for server to have started communication already
41
42
    completed = subprocess.run(["/usr/bin/java", "-jar", mmtjar, ":send", str(port_number),
                  "extension", "info.kwarc.mmt.interviews.InterviewServer"],
43
44
                               stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    if completed.stdout != None and "(Connection refused)" in str(completed.stdout):
45
46
47
48
49
50
51
52
        if timeout > 0.0:
            start_mmt_extension(port_number, mmtjar, timeout-0.1)
        else:
            raise MMTServerError("unable to start interview extension")


def find_free_port():
    import socket
53
54
55
56
57
58
    port_number = 0
    while (port_number < 1024) or (8888 < port_number < 10000):
        with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s1:
            s1.bind(('localhost', 0))            # Bind to a free port provided by the host.
            port_number = s1.getsockname()[1]
    return port_number  # Return the port number assigned.
59
60


61
62
63
def run_mmt(mmtjar, port_number=None):
    if port_number is None:
        port_number = find_free_port()
64

65
    print("port_number: " + str(port_number))
66

67
68
69
70
71
    t1 = threading.Thread(target=start_mmt_server, args=(port_number, mmtjar), daemon=True)
    t1.start()
    #t2 = threading.Thread(target=start_mmt_extension, args=[port_number], daemon=True)
    #t2.start()
    start_mmt_extension(port_number, mmtjar)
72
    return port_number
73

74

Theresa Pollinger's avatar
Theresa Pollinger committed
75
class MMTServerError(Exception):
76
    def __init__(self, err, longerr=None):
Theresa Pollinger's avatar
Theresa Pollinger committed
77
        self.error = err
78
79
        self.longerr = longerr
        super(MMTServerError, self).__init__("MMT server error: " + str(self.error), longerr)
Theresa Pollinger's avatar
Theresa Pollinger committed
80
81
82


class MMTReply:
83
84
85
    """An object that holds
        ok : whether the request was successful and
        root: the returned answer, usually an etree holding the xml reply"""
86

87
88
89
    def __init__(self, ok, root=None):
        self.ok = ok
        self.root = root
90
91
        #        print("creating reply")
        #        print(root)
92
93
        if isinstance(root, etree._Element):
            for element in root.iter():
94
95
                # for element in root.iter("div"): # why not entering this loop?
                # print("for element " + elementToString(element))
96
97
98
99
                if (element.get('class')) == 'error':
                    self.ok = False
                    for child in element:
                        if (child.get('class')) == 'message':
100
                            raise MMTServerError(child.text, element_to_string(self.root))
Theresa Pollinger's avatar
Theresa Pollinger committed
101
        if not self.ok:
102
            raise MMTServerError(element_to_string(self.root))
Theresa Pollinger's avatar
Theresa Pollinger committed
103
104
105

    def getConstant(self, constantname):
        elements = self.getConstants()
106
        # print ("elements: " + str(elements))
Theresa Pollinger's avatar
Theresa Pollinger committed
107
108
109
110
111
112
113
        for element in elements:
            if element.get('name') == constantname:
                return element

    def getConstants(self):
        elements = []
        for element in self.root.iter("constant"):
114
            # print("Constant: %s - %s - %s" % (element, element.text, element.keys()))
Theresa Pollinger's avatar
Theresa Pollinger committed
115
116
117
            elements.append(element)
        return elements

118
119
    def hasDefinition(self, constantname):
        if self.getDefinition(constantname) is not None:
120
121
            return True

Theresa Pollinger's avatar
Theresa Pollinger committed
122
123
124
125
126
    def getDefinition(self, constantname):
        element = self.getConstant(constantname)
        if element is not None:
            for child in element:
                if (child.tag) == 'definition':
127
                    # print(elementToString(child))
Theresa Pollinger's avatar
Theresa Pollinger committed
128
129
130
131
132
133
134
                    return child

    def getType(self, constantname):
        element = self.getConstant(constantname)
        if element is not None:
            for child in element:
                if (child.tag) == 'type':
135
136
137
138
139
140
141
142
143
144
145
146
147
148
                    print(element_to_string(child))
                    #return child
                    for oms in child.iter("{*}OMS"):
                        return self.get_name_or_expand_if_arrow(oms)

    def get_name_or_expand_if_arrow(self, oms):
        name = oms.get('name')
        if name == 'arrow':
            next = oms.getnext()
            name = next.get('name')
            while next.getnext() is not None:
                next = next.getnext()
                name = name + " → " + next.get('name')
        return name
Theresa Pollinger's avatar
Theresa Pollinger committed
149

150
151
    # (probably very volatile) accesses to concrete data structures
    def getIntervalBoundaries(self, mmtreply, intervalname):
Theresa Pollinger's avatar
Theresa Pollinger committed
152
153
        child = mmtreply.getDefinition(intervalname)
        for oms in child.iter("{*}OMS"):
Theresa Pollinger's avatar
Theresa Pollinger committed
154
155
            #print("OMS: %s - %s - %s" % (oms, oms.text, oms.keys()))
            if (oms.get('name') == 'interval'):
Theresa Pollinger's avatar
Theresa Pollinger committed
156
157
                a = oms.getnext()
                b = a.getnext()
Theresa Pollinger's avatar
Theresa Pollinger committed
158
159
                #print("a: %s - %s - %s" % (a, a.text, a.get('value')))
                #print("b: %s - %s - %s" % (b, b.text, b.get('value')))
Theresa Pollinger's avatar
Theresa Pollinger committed
160
161
                return (a.get('value'), b.get('value'))

162
163
    def tostring(self):
        return element_to_string(self.root)
Theresa Pollinger's avatar
Theresa Pollinger committed
164

165
166
167
168
    def inferred_type_to_string(self):
        type_string = ""
        for mo in self.root.iter("{*}mo"):
            type_string = type_string + " " + mo.text
169
        return type_string.strip()
170

171
172
173

def element_to_string(element):
    return etree.tostring(element, pretty_print=True).decode('utf8')
Theresa Pollinger's avatar
Theresa Pollinger committed
174

175

Theresa Pollinger's avatar
Theresa Pollinger committed
176
class MMTInterface:
177
    def __init__(self, mmt_jar="/home/freifrau/Desktop/masterarbeit/mmt/deploy/mmt.jar"):
Theresa Pollinger's avatar
Theresa Pollinger committed
178
        port_number = run_mmt(mmt_jar)  # , port_number)
Theresa Pollinger's avatar
Theresa Pollinger committed
179
        # set parameters for communication with mmt server
180
        self.serverInstance = 'http://localhost:'+str(port_number)
Theresa Pollinger's avatar
Theresa Pollinger committed
181
182
        self.extension = ':interview'
        self.URIprefix = 'http://mathhub.info/'
183
        self.namespace = 'MitM/smglom/calculus'  # TODO
184
        self.debugprint = True
185

Theresa Pollinger's avatar
Theresa Pollinger committed
186
187

    def mmt_new_theory(self, thyname):
188
189
190
        # So, ich hab mal was zu MMT/devel gepusht. Es gibt jetzt eine Extension namens InterviewServer. Starten tut man die mit "extension info.kwarc.mmt.interviews.InterviewServer"
        # Wenn du dann in MMT den Server (sagen wir auf Port 8080) startest, kannst du folgende HTTP-Requests ausführen:
        # "http://localhost:8080/:interview/new?theory="<MMT URI>"" fügt eine neue theorie mit der uri <MMT URI> hinzu
191
192
        req = '/' + self.extension + '/new?theory=' + quote(self.get_mpath(
            thyname)) + '&meta=' + quote('http://mathhub.info/MitM/Foundation?Logic')
Theresa Pollinger's avatar
Theresa Pollinger committed
193
194
195
196
        return self.http_request(req)

    def mmt_new_view(self, viewname, fromtheory, totheory):
        # analog für ?view="<MMT URI>".
197
198
        req = '/' + self.extension + '/new?view=' + quote(self.get_mpath(viewname)) + '&from=' + quote(self.get_mpath(
            fromtheory)) + '&to=' + quote(self.get_mpath(totheory))
Theresa Pollinger's avatar
Theresa Pollinger committed
199
200
201
        return self.http_request(req)

    def mmt_new_decl(self, declname, thyname, declcontent):
202
        # ".../:interview/new?decl="<irgendwas>"&cont="<MMT URI>" ist der query-path um der theorie <MMT URI> eine neue declaration hinzuzufügen (includes, konstanten...). Die Declaration sollte dabei in MMT-syntax als text im Body des HTTP-requests stehen.
203
        post = '/' + self.extension + '/new?decl=d&cont=' + quote(self.get_mpath(thyname))
Theresa Pollinger's avatar
Theresa Pollinger committed
204
205
206
        return self.http_request(post, add_dd(declcontent))

    def mmt_new_term(self, termname, thyname, termcontent):
207
        # analog für ".../:interview/new?term="<irgendwas>"&cont="<MMT URI>" für terme - nachdem da nicht klar ist was der server damit tun sollte gibt er den geparsten term als omdoc-xml zurück (wenn alles funktioniert)
208
209
210
211
212
        post = '/' + self.extension + '/new?term=' + quote(termname) + '&cont=' + quote(self.get_mpath(thyname))
        return self.http_request(post, termcontent)

    def mmt_infer_type(self, thyname, termcontent):
        post = '/' + self.extension + '/infer?cont=' + quote(self.get_mpath(thyname))
Theresa Pollinger's avatar
Theresa Pollinger committed
213
214
215
        return self.http_request(post, termcontent)

    def http_request(self, message, data=None):
216
        url = self.serverInstance + message
Theresa Pollinger's avatar
Theresa Pollinger committed
217
218
219
220
221
222
223
224
225
226
        print(url) if self.debugprint else 0
        session = requests.Session()
        adapter = requests.adapters.HTTPAdapter()
        session.mount('https://', adapter)
        session.mount('http://', adapter)
        try:
            if data:
                binary_data = data.encode('UTF-8')
                print('\n' + str(data)) if self.debugprint else 0
                headers = {'content-type': 'application/json',
227
                           'content-encoding': 'UTF-8'}
Theresa Pollinger's avatar
Theresa Pollinger committed
228
229
230
                req = session.post(url, data=binary_data, headers=headers, stream=True)
            else:
                req = requests.get(url)
231
        except ConnectionError as error:  # this seems to never be called
Theresa Pollinger's avatar
Theresa Pollinger committed
232
233
234
            print(error)
            print("Are you sure the mmt server is running?")
            raise SystemExit
235
        # print(req.text) if self.debugprint else 0
Theresa Pollinger's avatar
Theresa Pollinger committed
236
237
        if req.text.startswith('<'):
            root = etree.fromstring(req.text)
238
239
        else:
            root = None
Theresa Pollinger's avatar
Theresa Pollinger committed
240
        if req.status_code == 200:
241
242
            return MMTReply(True, root)
        return MMTReply(False, root)
Theresa Pollinger's avatar
Theresa Pollinger committed
243
244

    def get_mpath(self, thyname):
245
246
247
        mpath = thyname
        if not (mpath.startswith("http://") or mpath.startswith("https://")):
            mpath = self.URIprefix + self.namespace + "?" + thyname  # TODO
Theresa Pollinger's avatar
Theresa Pollinger committed
248
249
250
        return mpath

    def query_for(self, thingname):
251
252
        # this here just stolen from what MMTPy does
        # querycontent = b'<function name="presentDecl" param="xml"><literal><uri path="http://mathhub.info/MitM/smglom/algebra?magma"/></literal></function>'
Theresa Pollinger's avatar
Theresa Pollinger committed
253
        querycontent = '<function name="presentDecl" param="xml"><literal><uri path="' + (self.get_mpath(thingname)) + '"/></literal></function>'
254

Theresa Pollinger's avatar
Theresa Pollinger committed
255
256
257
        return self.http_qrequest(querycontent)

    def http_qrequest(self, data, message='/:query'):
258
259
        url = self.serverInstance + message
        print(url) if self.debugprint else 0
Theresa Pollinger's avatar
Theresa Pollinger committed
260
261
262
263
        session = requests.Session()
        adapter = requests.adapters.HTTPAdapter()
        session.mount('https://', adapter)
        session.mount('http://', adapter)
264
        print('\n' + str(data)) if self.debugprint else 0
Theresa Pollinger's avatar
Theresa Pollinger committed
265
266
        binary_data = data.encode('UTF-8')
        headers = {'content-type': 'application/xml'}
267
        req = session.post(url, data=binary_data, headers=headers, stream=True)
Theresa Pollinger's avatar
Theresa Pollinger committed
268
269
        root = etree.fromstring(req.text)
        if req.status_code == 200:
270
271
            return MMTReply(True, root)
        return MMTReply(False, root)
Theresa Pollinger's avatar
Theresa Pollinger committed
272

273

Theresa Pollinger's avatar
Theresa Pollinger committed
274
275
def add_dd(string):
    return string + "❙"