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.

@@ -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,,