dotstat_io 0.2.7__py3-none-any.whl → 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dotstat_io might be problematic. Click here for more details.
- dotstat_io/authentication.py +210 -191
- dotstat_io/client.py +402 -0
- {dotstat_io-0.2.7.dist-info → dotstat_io-1.0.1.dist-info}/METADATA +49 -33
- dotstat_io-1.0.1.dist-info/RECORD +6 -0
- {dotstat_io-0.2.7.dist-info → dotstat_io-1.0.1.dist-info}/WHEEL +1 -1
- dotstat_io/download_upload.py +0 -376
- dotstat_io-0.2.7.dist-info/RECORD +0 -6
dotstat_io/download_upload.py
DELETED
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import requests
|
|
3
|
-
import chardet
|
|
4
|
-
import logging
|
|
5
|
-
import xml.etree.ElementTree as ET
|
|
6
|
-
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from time import sleep
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
FORMAT = '%(message)s'
|
|
12
|
-
logging.basicConfig(format=FORMAT, level=logging.INFO)
|
|
13
|
-
log = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# class to download or upload data from/to .Stat Suite
|
|
17
|
-
class Download_upload():
|
|
18
|
-
|
|
19
|
-
# Declare constants
|
|
20
|
-
__ERROR = "An error occurred: "
|
|
21
|
-
__NO_ACCESS_TOKEN = "No access token"
|
|
22
|
-
__EXECUTION_IN_QUEUED = "Queued"
|
|
23
|
-
__EXECUTION_IN_PROGRESS = "InProgress"
|
|
24
|
-
__CONNECTION_ABORTED = "An existing connection was forcibly closed by the remote host"
|
|
25
|
-
__DOWNLOAD_SUCCESS = "Successful download"
|
|
26
|
-
__UPLOAD_SUCCESS = "The request was successfully processed "
|
|
27
|
-
__UPLOAD_FAILED = "The request failed with status code "
|
|
28
|
-
|
|
29
|
-
__NAMESPACE_MESSAGE = "{http://www.sdmx.org/resources/sdmxml/schemas/v2_1/message}"
|
|
30
|
-
__NAMESPACE_COMMON = "{http://www.sdmx.org/resources/sdmxml/schemas/v2_1/common}"
|
|
31
|
-
|
|
32
|
-
# Initialise Download_upload
|
|
33
|
-
def __init__(self, adfsAuthentication_obj, access_token):
|
|
34
|
-
self.adfsAuthentication_obj = adfsAuthentication_obj
|
|
35
|
-
self.access_token = access_token
|
|
36
|
-
|
|
37
|
-
def __enter__(self):
|
|
38
|
-
return self
|
|
39
|
-
|
|
40
|
-
def __exit__(self, exc_type, exc_value, traceback):
|
|
41
|
-
self.adfsAuthentication_obj = None
|
|
42
|
-
self.access_token = None
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# Download a file from .STAT
|
|
46
|
-
def download_file(self, dotstat_url: str, content_format: str, file_path: Path):
|
|
47
|
-
try:
|
|
48
|
-
Returned_Message = ""
|
|
49
|
-
|
|
50
|
-
if (self.access_token == None):
|
|
51
|
-
Returned_Message = self.__ERROR + self.__NO_ACCESS_TOKEN + os.linesep
|
|
52
|
-
|
|
53
|
-
# Write the result to the log
|
|
54
|
-
for line in Returned_Message.split(os.linesep):
|
|
55
|
-
if len(line) > 0:
|
|
56
|
-
log.info(' ' + line)
|
|
57
|
-
else:
|
|
58
|
-
if self.adfsAuthentication_obj.is_access_token_expired():
|
|
59
|
-
self.access_token = self.adfsAuthentication_obj.get_token()
|
|
60
|
-
|
|
61
|
-
headers = {
|
|
62
|
-
'accept': content_format,
|
|
63
|
-
'authorization': 'Bearer '+self.access_token
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
#
|
|
67
|
-
response = requests.get(dotstat_url, verify=True, headers=headers)
|
|
68
|
-
except Exception as err:
|
|
69
|
-
Returned_Message = self.__ERROR + str(err) + os.linesep
|
|
70
|
-
|
|
71
|
-
# Write the result to the log
|
|
72
|
-
for line in Returned_Message.split(os.linesep):
|
|
73
|
-
if len(line) > 0:
|
|
74
|
-
log.info(' ' + line)
|
|
75
|
-
else:
|
|
76
|
-
if response.status_code != 200:
|
|
77
|
-
Returned_Message = self.__ERROR + 'Error code: ' + \
|
|
78
|
-
str(response.status_code) + ' Reason: ' + str(response.reason)
|
|
79
|
-
if len(response.text) > 0:
|
|
80
|
-
Returned_Message += os.linesep + 'Text: ' + response.text
|
|
81
|
-
else:
|
|
82
|
-
if os.path.isfile(file_path):
|
|
83
|
-
os.remove(file_path)
|
|
84
|
-
with open(file_path, "wb") as file:
|
|
85
|
-
file.write(response.content)
|
|
86
|
-
Returned_Message = self.__DOWNLOAD_SUCCESS
|
|
87
|
-
|
|
88
|
-
# Write the result to the log
|
|
89
|
-
for line in Returned_Message.split(os.linesep):
|
|
90
|
-
if len(line) > 0:
|
|
91
|
-
log.info(' ' + line)
|
|
92
|
-
finally:
|
|
93
|
-
return Returned_Message
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# Download streamed content from .STAT
|
|
97
|
-
def download_stream(self, dotstat_url: str, content_format: str):
|
|
98
|
-
try:
|
|
99
|
-
Returned_Message = ""
|
|
100
|
-
|
|
101
|
-
if (self.access_token == None):
|
|
102
|
-
Returned_Message = self.__ERROR + self.__NO_ACCESS_TOKEN + os.linesep
|
|
103
|
-
else:
|
|
104
|
-
if self.adfsAuthentication_obj.is_access_token_expired():
|
|
105
|
-
self.access_token = self.adfsAuthentication_obj.get_token()
|
|
106
|
-
|
|
107
|
-
headers = {
|
|
108
|
-
'accept': content_format,
|
|
109
|
-
'Transfer-Encoding': 'chunked',
|
|
110
|
-
'authorization': 'Bearer '+self.access_token
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
#
|
|
114
|
-
return requests.get(dotstat_url, verify=True, headers=headers, stream=True)
|
|
115
|
-
except Exception as err:
|
|
116
|
-
Returned_Message = self.__ERROR + str(err) + os.linesep
|
|
117
|
-
return Returned_Message
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
# Upload a file to .STAT
|
|
121
|
-
def upload_file(self,
|
|
122
|
-
transfer_url: str,
|
|
123
|
-
file_path: Path,
|
|
124
|
-
space: str,
|
|
125
|
-
validationType: int,
|
|
126
|
-
use_filepath: bool = False):
|
|
127
|
-
try:
|
|
128
|
-
Returned_Message = ""
|
|
129
|
-
|
|
130
|
-
if (self.access_token == None):
|
|
131
|
-
Returned_Message = self.__ERROR + self.__NO_ACCESS_TOKEN + os.linesep
|
|
132
|
-
|
|
133
|
-
# Write the result to the log
|
|
134
|
-
for line in Returned_Message.split(os.linesep):
|
|
135
|
-
if len(line) > 0:
|
|
136
|
-
log.info(' ' + line)
|
|
137
|
-
else:
|
|
138
|
-
if self.adfsAuthentication_obj.is_access_token_expired():
|
|
139
|
-
self.access_token = self.adfsAuthentication_obj.get_token()
|
|
140
|
-
|
|
141
|
-
payload = {
|
|
142
|
-
'dataspace': space,
|
|
143
|
-
'validationType': validationType
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
headers = {
|
|
147
|
-
'accept': 'application/json',
|
|
148
|
-
'authorization': "Bearer "+self.access_token
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if use_filepath:
|
|
152
|
-
files = {
|
|
153
|
-
'dataspace': (None, payload['dataspace']),
|
|
154
|
-
'validationType': (None, payload['validationType']),
|
|
155
|
-
'filepath': (None, str(file_path))
|
|
156
|
-
}
|
|
157
|
-
else:
|
|
158
|
-
files = {
|
|
159
|
-
'dataspace': (None, payload['dataspace']),
|
|
160
|
-
'validationType': (None, payload['validationType']),
|
|
161
|
-
'file': (os.path.realpath(file_path), open(os.path.realpath(file_path), 'rb'), 'text/csv', '')
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
#
|
|
165
|
-
response = requests.post(transfer_url, verify=True, headers=headers, files=files)
|
|
166
|
-
except Exception as err:
|
|
167
|
-
Returned_Message = self.__ERROR + str(err) + os.linesep
|
|
168
|
-
|
|
169
|
-
# Write the result to the log
|
|
170
|
-
for line in Returned_Message.split(os.linesep):
|
|
171
|
-
if len(line) > 0:
|
|
172
|
-
log.info(' ' + line)
|
|
173
|
-
else:
|
|
174
|
-
if response.status_code != 200:
|
|
175
|
-
Returned_Message = self.__ERROR + 'Error code: ' + \
|
|
176
|
-
str(response.status_code) + ' Reason: ' + str(response.reason)
|
|
177
|
-
if len(response.text) > 0:
|
|
178
|
-
Returned_Message = Returned_Message + os.linesep + 'Text: ' + response.text
|
|
179
|
-
|
|
180
|
-
Returned_Message = Returned_Message + os.linesep
|
|
181
|
-
# Write the result to the log
|
|
182
|
-
for line in Returned_Message.split(os.linesep):
|
|
183
|
-
if len(line) > 0:
|
|
184
|
-
log.info(' ' + line)
|
|
185
|
-
else:
|
|
186
|
-
try:
|
|
187
|
-
Result = response.json()['message']
|
|
188
|
-
|
|
189
|
-
# Write the result to the log
|
|
190
|
-
for line in Result.split(os.linesep):
|
|
191
|
-
if len(line) > 0:
|
|
192
|
-
log.info(' ' + line)
|
|
193
|
-
|
|
194
|
-
Returned_Message = Result + os.linesep
|
|
195
|
-
|
|
196
|
-
# Check the request status
|
|
197
|
-
if (Result != "" and Result.find(self.__ERROR ) == -1):
|
|
198
|
-
# Extract the request ID the returned message
|
|
199
|
-
start = 'with ID'
|
|
200
|
-
end = 'was successfully'
|
|
201
|
-
requestId = Result[Result.find(
|
|
202
|
-
start)+len(start):Result.rfind(end)]
|
|
203
|
-
|
|
204
|
-
# Sleep a little bit before checking the request status
|
|
205
|
-
sleep(3)
|
|
206
|
-
|
|
207
|
-
# To avoid this error: maximum recursion depth exceeded while calling a Python object
|
|
208
|
-
# replace the recursive calls with while loops.
|
|
209
|
-
Result = self.__check_request_status(transfer_url, requestId, space)
|
|
210
|
-
|
|
211
|
-
# Write the result to the log
|
|
212
|
-
for line in Result.split(os.linesep):
|
|
213
|
-
if len(line) > 0:
|
|
214
|
-
log.info(' ' + line)
|
|
215
|
-
sleep(3)
|
|
216
|
-
|
|
217
|
-
Previous_Result = Result
|
|
218
|
-
while Result in [self.__EXECUTION_IN_PROGRESS, self.__EXECUTION_IN_QUEUED, self.__CONNECTION_ABORTED]:
|
|
219
|
-
Result = self.__check_request_status(transfer_url, requestId, space)
|
|
220
|
-
|
|
221
|
-
# Prevent loging again the same information such as "Queued" or "InProgress"
|
|
222
|
-
if Previous_Result != Result:
|
|
223
|
-
Previous_Result = Result
|
|
224
|
-
|
|
225
|
-
# Write the result to the log
|
|
226
|
-
for line in Previous_Result.split(os.linesep):
|
|
227
|
-
if (len(line) > 0 and line not in [self.__EXECUTION_IN_PROGRESS, self.__EXECUTION_IN_QUEUED, self.__CONNECTION_ABORTED]):
|
|
228
|
-
log.info(' ' + line)
|
|
229
|
-
sleep(3)
|
|
230
|
-
|
|
231
|
-
Returned_Message = Returned_Message + Result + os.linesep
|
|
232
|
-
except Exception as err:
|
|
233
|
-
Returned_Message = self.__ERROR + str(err) + os.linesep
|
|
234
|
-
if len(response.text) > 0:
|
|
235
|
-
Returned_Message = Returned_Message + 'Text: ' + response.text + os.linesep
|
|
236
|
-
|
|
237
|
-
# Write the result to the log
|
|
238
|
-
for line in Returned_Message.split(os.linesep):
|
|
239
|
-
if len(line) > 0:
|
|
240
|
-
log.info(' ' + line)
|
|
241
|
-
finally:
|
|
242
|
-
return Returned_Message
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
# Upload a structure to .STAT
|
|
246
|
-
def upload_structure(self, transfer_url: str, file_path: Path):
|
|
247
|
-
try:
|
|
248
|
-
Returned_Message = ""
|
|
249
|
-
|
|
250
|
-
if (self.access_token == None):
|
|
251
|
-
Returned_Message = self.__ERROR + self.__NO_ACCESS_TOKEN + os.linesep
|
|
252
|
-
|
|
253
|
-
# Write the result to the log
|
|
254
|
-
for line in Returned_Message.split(os.linesep):
|
|
255
|
-
if len(line) > 0:
|
|
256
|
-
log.info(' ' + line)
|
|
257
|
-
else:
|
|
258
|
-
if self.adfsAuthentication_obj.is_access_token_expired():
|
|
259
|
-
self.access_token = self.adfsAuthentication_obj.get_token()
|
|
260
|
-
|
|
261
|
-
# Detect the encoding used in file
|
|
262
|
-
detected_encoding = self.__detect_encode(file_path)
|
|
263
|
-
|
|
264
|
-
# Read file as a string "r+" with the detected encoding
|
|
265
|
-
with open(file=file_path, mode="r+", encoding=detected_encoding.get("encoding")) as file:
|
|
266
|
-
xml_data = file.read()
|
|
267
|
-
|
|
268
|
-
# Make sure the encoding is "utf-8"
|
|
269
|
-
tree = ET.fromstring(xml_data)
|
|
270
|
-
xml_data = ET.tostring(tree, encoding="utf-8", method='xml', xml_declaration=True)
|
|
271
|
-
|
|
272
|
-
headers = {
|
|
273
|
-
'Content-Type': 'application/xml',
|
|
274
|
-
'authorization': "Bearer "+self.access_token
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
#
|
|
278
|
-
response = requests.post(transfer_url, verify=True, headers=headers, data=xml_data)
|
|
279
|
-
except Exception as err:
|
|
280
|
-
Returned_Message = self.__ERROR + str(err) + os.linesep
|
|
281
|
-
|
|
282
|
-
# Write the result to the log
|
|
283
|
-
for line in Returned_Message.split(os.linesep):
|
|
284
|
-
if len(line) > 0:
|
|
285
|
-
log.info(' ' + line)
|
|
286
|
-
else:
|
|
287
|
-
try:
|
|
288
|
-
response.raise_for_status()
|
|
289
|
-
except requests.exceptions.HTTPError as e:
|
|
290
|
-
Returned_Message = f'{self.__UPLOAD_FAILED}{response.status_code}: {e}'
|
|
291
|
-
|
|
292
|
-
# Write the result to the log
|
|
293
|
-
for line in Returned_Message.split(os.linesep):
|
|
294
|
-
if len(line) > 0:
|
|
295
|
-
log.info(' ' + line)
|
|
296
|
-
else:
|
|
297
|
-
response_tree = ET.XML(response.content)
|
|
298
|
-
for error_message in response_tree.findall("./{0}ErrorMessage".format(self.__NAMESPACE_MESSAGE)):
|
|
299
|
-
text_element = error_message.find("./{0}Text".format(self.__NAMESPACE_COMMON))
|
|
300
|
-
if (text_element is not None):
|
|
301
|
-
if Returned_Message == "":
|
|
302
|
-
Returned_Message = f'{self.__UPLOAD_SUCCESS}with status code: {response.status_code}' + os.linesep
|
|
303
|
-
Returned_Message = Returned_Message + text_element.text + os.linesep
|
|
304
|
-
|
|
305
|
-
# Write the result to the log
|
|
306
|
-
for line in Returned_Message.split(os.linesep):
|
|
307
|
-
if len(line) > 0:
|
|
308
|
-
log.info(' ' + line)
|
|
309
|
-
finally:
|
|
310
|
-
return Returned_Message
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
# Detect the encoding used in file
|
|
314
|
-
def __detect_encode(self, file_path):
|
|
315
|
-
detector = chardet.UniversalDetector()
|
|
316
|
-
detector.reset()
|
|
317
|
-
with open(file=file_path, mode="rb") as file:
|
|
318
|
-
for row in file:
|
|
319
|
-
detector.feed(row)
|
|
320
|
-
if detector.done:
|
|
321
|
-
break
|
|
322
|
-
|
|
323
|
-
detector.close()
|
|
324
|
-
|
|
325
|
-
return detector.result
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
# Check request sent to .STAT status
|
|
329
|
-
# To avoid this error: maximum recursion depth exceeded while calling a Python object
|
|
330
|
-
# replace the recursive calls with while loops.
|
|
331
|
-
def __check_request_status(self, transfer_url, requestId, space):
|
|
332
|
-
try:
|
|
333
|
-
Returned_Message = ""
|
|
334
|
-
|
|
335
|
-
if (self.access_token == None):
|
|
336
|
-
Returned_Message = self.__ERROR + self.__NO_ACCESS_TOKEN + os.linesep
|
|
337
|
-
else:
|
|
338
|
-
if self.adfsAuthentication_obj.is_access_token_expired():
|
|
339
|
-
self.access_token = self.adfsAuthentication_obj.get_token()
|
|
340
|
-
|
|
341
|
-
headers = {
|
|
342
|
-
'accept': 'application/json',
|
|
343
|
-
'authorization': "Bearer "+self.access_token
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
payload = {
|
|
347
|
-
'dataspace': space,
|
|
348
|
-
'id': requestId
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
transfer_url = transfer_url.replace("import", "status")
|
|
352
|
-
transfer_url = transfer_url.replace("sdmxFile", "request")
|
|
353
|
-
|
|
354
|
-
#
|
|
355
|
-
response = requests.post(transfer_url, verify=True, headers=headers, data=payload)
|
|
356
|
-
except Exception as err:
|
|
357
|
-
Returned_Message = self.__ERROR + str(err)
|
|
358
|
-
else:
|
|
359
|
-
if response.status_code != 200:
|
|
360
|
-
Returned_Message = self.__ERROR + 'Error code: ' + \
|
|
361
|
-
str(response.status_code) + ' Reason: ' + str(response.reason)
|
|
362
|
-
if len(response.text) > 0:
|
|
363
|
-
Returned_Message = Returned_Message + os.linesep + 'Text: ' + response.text
|
|
364
|
-
else:
|
|
365
|
-
executionStatus = 'Execution status: ' + response.json()['executionStatus']
|
|
366
|
-
if response.json()['executionStatus'] in [self.__EXECUTION_IN_PROGRESS, self.__EXECUTION_IN_QUEUED, self.__CONNECTION_ABORTED]:
|
|
367
|
-
Returned_Message = response.json()['executionStatus']
|
|
368
|
-
else:
|
|
369
|
-
Returned_Message = executionStatus + os.linesep + 'Outcome: ' + response.json()['outcome'] + os.linesep
|
|
370
|
-
index = 0
|
|
371
|
-
while index < len(response.json()['logs']):
|
|
372
|
-
Returned_Message = Returned_Message + 'Log' + str(index) + ': ' + response.json()['logs'][index]['message'] + os.linesep
|
|
373
|
-
index += 1
|
|
374
|
-
finally:
|
|
375
|
-
return Returned_Message
|
|
376
|
-
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
dotstat_io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
dotstat_io/authentication.py,sha256=_OCTbSUcdD1jFoDbtMi48xC5U5ZsizC7w9K0Ar7mSAk,12236
|
|
3
|
-
dotstat_io/download_upload.py,sha256=ss-eZRhSL4iHzmv9vkY6x1BgMUgwMqKLkET2XmFC4-Q,16163
|
|
4
|
-
dotstat_io-0.2.7.dist-info/METADATA,sha256=tuGvwxeiR7CsKPLhY1yA3Dpz6h1ftK9MmyXkVcN1f0E,6930
|
|
5
|
-
dotstat_io-0.2.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
6
|
-
dotstat_io-0.2.7.dist-info/RECORD,,
|