2727# THE SOFTWARE.
2828# -------------------------------------------------------------------------------
2929
30- try :
31- from urllib .error import HTTPError
32- from urllib .request import Request , urlopen
33- except ImportError :
34- # Python 2 backward compatibility
35- from urllib2 import urlopen , Request , HTTPError
36-
3730from contextlib import closing
3831from logging import LoggerAdapter , getLogger
3932from time import sleep
33+ from urllib .error import HTTPError
34+ from urllib .parse import urljoin
35+ from urllib .request import Request , urlopen
4036from xml .etree import ElementTree
4137
4238from .time_util import Timer
@@ -104,6 +100,10 @@ class WPS10Service:
104100 request
105101 """
106102
103+ DEFAULT_CONTENT_TYPE = "application/xml; charset=utf-8"
104+ RETRY_TIME = 20 # seconds
105+ STATUS_POLL_RETRIES = 3 # re-try attempts
106+
107107 STATUS = {
108108 "{http://www.opengis.net/wps/1.0.0}ProcessAccepted" : "ACCEPTED" ,
109109 "{http://www.opengis.net/wps/1.0.0}ProcessFailed" : "FAILED" ,
@@ -120,12 +120,17 @@ def __init__(self, url, headers=None, logger=None):
120120 self .headers = headers or {}
121121 self .logger = self ._LoggerAdapter (logger or getLogger (__name__ ), {})
122122
123- def retrieve (self , request , handler = None ):
123+ def retrieve (self , request , handler = None , content_type = None ):
124124 """Send a synchronous POST WPS request to a server and retrieve
125125 the output.
126126 """
127+ headers = {
128+ ** self .headers ,
129+ "Content-Type" : content_type or self .DEFAULT_CONTENT_TYPE ,
130+ }
131+
127132 return self ._retrieve (
128- Request (self .url , request , self . headers ), handler , self .error_handler
133+ Request (self .url , request , headers ), handler , self .error_handler
129134 )
130135
131136 def retrieve_async (
@@ -136,16 +141,20 @@ def retrieve_async(
136141 cleanup_handler = None ,
137142 polling_interval = 1 ,
138143 output_name = "output" ,
144+ content_type = None ,
139145 ):
140146 """Send an asynchronous POST WPS request to a server and retrieve
141147 the output.
142148 """
143149 timer = Timer ()
144150 status , percentCompleted , status_url , execute_response = self .submit_async (
145- request
151+ request ,
152+ content_type = content_type ,
146153 )
147154 wpsstatus = WPSStatus ()
148- wpsstatus .update (status , percentCompleted , status_url , execute_response )
155+ wpsstatus .update (
156+ status , percentCompleted , urljoin (self .url , status_url ), execute_response
157+ )
149158
150159 def log_wpsstatus (wpsstatus ):
151160 self .logger .info (
@@ -169,7 +178,7 @@ def log_wpsstatus_percentCompleted(wpsstatus):
169178
170179 last_status = wpsstatus .status
171180 last_percentCompleted = wpsstatus .percentCompleted
172- wpsstatus .update (* self .poll_status (wpsstatus .url ))
181+ wpsstatus .update (* self .poll_status (urljoin ( self . url , wpsstatus .url ) ))
173182
174183 if wpsstatus .status != last_status :
175184 log_wpsstatus (wpsstatus )
@@ -197,7 +206,9 @@ def retrieve_async_output(self, status_url, output_name, handler=None):
197206 """Retrieve asynchronous job output reference."""
198207 self .logger .debug ("Retrieving asynchronous job output '%s'." , output_name )
199208 output_url = self .parse_output_reference (status_url , output_name )
200- return self ._retrieve (Request (output_url , None , self .headers ), handler )
209+ return self ._retrieve (
210+ Request (urljoin (self .url , output_url ), None , self .headers ), handler
211+ )
201212
202213 @staticmethod
203214 def parse_output_reference (xml , identifier ):
@@ -210,25 +221,60 @@ def parse_output_reference(xml, identifier):
210221 elm_reference = elm .find (
211222 "./{http://www.opengis.net/wps/1.0.0}Reference"
212223 )
213- return elm_reference .attrib ["href" ]
224+ return (
225+ elm_reference .attrib .get ("{http://www.w3.org/1999/xlink}href" )
226+ or elm_reference .attrib ["href" ]
227+ )
214228
215- def submit_async (self , request ):
229+ def submit_async (self , request , content_type = None ):
216230 """Send a POST WPS asynchronous request to a server and retrieve
217231 the status URL.
218232 """
219233 self .logger .debug ("Submitting asynchronous job." )
234+ headers = {
235+ ** self .headers ,
236+ "Content-Type" : content_type or self .DEFAULT_CONTENT_TYPE ,
237+ }
220238 return self ._retrieve (
221- Request (self .url , request , self . headers ),
239+ Request (self .url , request , headers ),
222240 self .parse_status ,
223241 self .error_handler ,
224242 )
225243
226244 def poll_status (self , status_url ):
227245 """Poll status of an asynchronous WPS job."""
228246 self .logger .debug ("Polling asynchronous job status." )
229- return self ._retrieve (
230- Request (status_url , None , self .headers ), self .parse_status
231- )
247+
248+ for index in range (self .STATUS_POLL_RETRIES + 1 ):
249+
250+ if index == 0 :
251+ self .logger .debug ("Polling asynchronous job status." )
252+ else :
253+ self .logger .debug (
254+ "Polling asynchronous job status. Retry attempt #%s." , index
255+ )
256+
257+ try :
258+ return self ._retrieve (
259+ Request (status_url , None , self .headers ), self .parse_status
260+ )
261+ except Exception as error :
262+ if index < self .STATUS_POLL_RETRIES :
263+ self .logger .error (
264+ "Status poll failed. Retrying in %s seconds. %s: %s" ,
265+ self .RETRY_TIME ,
266+ error .__class__ .__name__ ,
267+ error ,
268+ )
269+ else :
270+ self .logger .error (
271+ "Status poll failed. No more retries. %s: %s" ,
272+ error .__class__ .__name__ ,
273+ error ,
274+ )
275+ raise
276+
277+ sleep (self .RETRY_TIME )
232278
233279 @classmethod
234280 def parse_status (cls , response ):
0 commit comments