Completed
Push — master ( 3de2d7...fcfe03 )
by Gus
01:06
created

ProcessorsAPI.resolve_jar_path()   B

Complexity

Conditions 5

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
c 1
b 0
f 0
dl 0
loc 25
rs 8.0894
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
from pkg_resources import resource_filename
4
from .processors import *
5
from .sentiment import SentimentAnalysisAPI
6
import os
7
import shlex
8
import os
9
import subprocess as sp
10
import requests
11
import time
12
import sys
13
import logging
14
15
16
class ProcessorsAPI(object):
17
18
    PROC_VAR = 'PROCESSORS_SERVER'
19
20
    def __init__(self, port, hostname="127.0.0.1", jar_path=None, log_file=None):
21
22
        self.hostname = hostname
23
        self.port = port
24
        self.make_address(hostname, port)
25
        self._start_command = "java -cp {} NLPServer {}"
26
        self.timeout = 120
27
        # how long to wait between requests
28
        self.wait_time = 2
29
        # processors
30
        self.default = Processor(self.address)
31
        self.fastnlp = FastNLPProcessor(self.address)
32
        self.bionlp = BioNLPProcessor(self.address)
33
        # sentiment
34
        self.sentiment = SentimentAnalysisAPI(self.address)
35
        # use the os module's devnull for compatibility with python 2.7
36
        #self.DEVNULL = open(os.devnull, 'wb')
37
38
        self.logger = logging.getLogger(__name__)
39
        self.log_file = self.prepare_log_file(log_file)
40
41
        # resolve jar path
42
        self.resolve_jar_path(jar_path)
43
        # attempt to establish connection with server
44
        self.establish_connection()
45
46
    def prepare_log_file(self, lf):
47
        """
48
        Configure logger and return file path for logging
49
        """
50
        # log_file
51
        log_file = os.path.expanduser(os.path.join("~", ".py-processors.log")) if not lf else os.path.expanduser(lf)
52
        # attach handler
53
        handler = logging.FileHandler(log_file)
54
        self.logger.addHandler(handler)
55
        self.logger.setLevel(logging.INFO)
56
        return log_file
57
58
    def annotate(self, text):
59
        """
60
        Uses default processor (CoreNLP) to annotate text.  Included for backwards compatibility.
61
        """
62
        return self.default.annotate(text)
63
64
    def establish_connection(self):
65
        """
66
        Attempt to connect to a server (assumes server is running)
67
        """
68
        if self.annotate("Blah"):
69
            print("Connection with server established!")
70
        else:
71
            try:
72
                # Attempt to start the server
73
                self._start_server()
74
            except Exception as e:
75
                if not os.path.exists(self.jar_path):
76
                    print("processors-server.jar not found.")
77
                print("Unable to start server. Please start the server manually with .start_server(\"path/to/processors-server.jar\")")
78
                print("\n{}".format(e))
79
80
    def resolve_jar_path(self, jar_path):
81
        """
82
        Attempts to preferentially set value of self.jar_path
83
        """
84
        # Preference 1: if a .jar is given, check to see if the path is valid
85
        if jar_path:
86
            print("Using provided path")
87
            jp = os.path.expanduser(jar_path)
88
            # check if path is valid
89
            if os.path.exists(jp):
90
                self.jar_path = jp
91
        else:
92
            # Preference 2: if a PROCESSORS_SERVER environment variable is defined, check its validity
93
            if ProcessorsAPI.PROC_VAR in os.environ:
94
                print("Using path given via $PROCESSORS_SERVER")
95
                jp = os.path.expanduser(os.environ[ProcessorsAPI.PROC_VAR])
96
                # check if path is valid
97
                if os.path.exists(jp):
98
                    self.jar_path = jp
99
                else:
100
                    print("WARNING: {0} path is invalid.  \nPlease verify this entry in your environment:\n\texport {0}=/path/to/processors-server.jar".format(ProcessorsAPI.PROC_VAR))
101
            # Preference 3: attempt to use the processors-sever.jar downloaded when this package was installed
102
            else:
103
                print("Using default")
104
                self.jar_path = resource_filename(__name__, "processors-server.jar")
105
106
    def start_server(self, jar_path=None, timeout=120):
107
        """
108
        Starts processors-sever.jar
109
        """
110
        self.timeout = int(float(timeout)/2)
111
        if jar_path:
112
            self.jar_path = jar_path
113
        self._start_server()
114
115
    def stop_server(self, port=None):
116
        """
117
        Sends a poison pill to the server and waits for shutdown response
118
        """
119
        port = port or self.port
120
        address = "http://{}:{}".format(self.hostname, port)
121
        shutdown_address = "{}/shutdown".format(address)
122
        # attempt shutdown
123
        try:
124
            response = requests.post(shutdown_address)
125
            if response:
126
                print(response.content.decode("utf-8"))
127
            return True
128
        # will fail if the server is already down
129
        except Exception as e:
130
            pass
131
        return False
132
133
    def _start_server(self, port=None):
134
        """
135
        "Private" method called by start_server()
136
        """
137
        if port:
138
            self.port = port
139
        # build the command
140
        cmd = self._start_command.format(self.jar_path, self.port)
141
        #print(cmd)
142
        self._process = sp.Popen(shlex.split(cmd),
143
                                 shell=False,
144
                                 stderr=open(self.log_file, 'wb'),
145
                                 stdout=open(self.log_file, 'wb'),
146
                                 universal_newlines=True)
147
148
        print("Starting processors-server ({}) on port {} ...".format(self.jar_path, self.port))
149
        print("\nWaiting for server...")
150
151
        progressbar_length = int(self.timeout/self.wait_time)
152
        for i in range(progressbar_length):
153
            try:
154
                success = self.annotate("blah")
155
                if success:
156
                    print("\n\nConnection with processors-server established ({})".format(self.address))
157
                    return True
158
                sys.stdout.write("\r[{:{}}]".format('='*i, progressbar_length))
159
                time.sleep(self.wait_time)
160
            except Exception as e:
161
                raise(e)
162
                #print(e)
163
164
        # if the server still hasn't started, raise an Exception
165
        raise Exception("Couldn't connect to processors-server. Is the port in use?")
166
167
    def make_address(self, hostname, port):
168
        # update hostname
169
        self.hostname = hostname
170
        # update port
171
        self.port = port
172
        # update address
173
        self.address = "http://{}:{}".format(self.hostname, self.port)
174
175
    def _get_path(self, p):
176
        """
177
        Expand a user-specified path.  Supports "~" shortcut.
178
        """
179
        return os.path.abspath(os.path.normpath(os.path.expanduser(p)))
180
181
    def __del__(self):
182
        """
183
        Stop server
184
        """
185
        try:
186
            self.stop_server()
187
            # close our file object
188
            #self.DEVNULL.close()
189
            print("Successfully shut down processors-server!")
190
        except Exception as e:
191
            #print(e)
192
            print("Couldn't kill processors-server.  Was server started externally?")
193