gn2pg_client 1.9.0.dev0__tar.gz → 1.9.1__tar.gz

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.
Files changed (34) hide show
  1. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/PKG-INFO +1 -1
  2. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/api.py +48 -31
  3. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/admin_views.py +0 -1
  4. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/models.py +0 -1
  5. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/download.py +56 -40
  6. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/helpers.py +41 -26
  7. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/locale/fr_FR/LC_MESSAGES/gn2pg.po +138 -89
  8. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/locale/fr_FR/LC_MESSAGES/gn2pg.po~ +172 -108
  9. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/main.py +2 -1
  10. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/store_postgresql.py +23 -15
  11. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/utils.py +1 -0
  12. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/pyproject.toml +1 -1
  13. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/LICENSE +0 -0
  14. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/README.md +0 -0
  15. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/__init__.py +0 -0
  16. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/__init__.py +0 -0
  17. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/app.py +0 -0
  18. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/config.py +0 -0
  19. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/database.py +0 -0
  20. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/env.py +0 -0
  21. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/index.py +0 -0
  22. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/static/css/custom_flaks_admin.css +0 -0
  23. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/static/img/src_gn2pg.png +0 -0
  24. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/templates/admin/master.html +0 -0
  25. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/templates/index.html +0 -0
  26. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/utils/__init__.py +0 -0
  27. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/app/utils/admin_views.py +0 -0
  28. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/check_conf.py +0 -0
  29. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/data/__init__.py +0 -0
  30. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/data/gn2pgconfig.toml +0 -0
  31. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/data/to_gnsynthese.sql +0 -0
  32. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/env.py +0 -0
  33. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/locale/en_US/LC_MESAGES/gn2pg.po +0 -0
  34. {gn2pg_client-1.9.0.dev0 → gn2pg_client-1.9.1}/gn2pg/logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: gn2pg_client
3
- Version: 1.9.0.dev0
3
+ Version: 1.9.1
4
4
  Summary: Import tool from GeoNature to a PostgreSQL database through Export module API (client side)
5
5
  License: AGPL-3.0-or-later
6
6
  Keywords: GeoNature,Export,SINP,opendata,biodiversity
@@ -22,9 +22,10 @@ from urllib.parse import urlencode
22
22
 
23
23
  import requests
24
24
  from requests.adapters import HTTPAdapter, Retry
25
- from requests.exceptions import RetryError
25
+ from requests.exceptions import HTTPError, InvalidSchema, RetryError
26
26
 
27
27
  from gn2pg import _, __version__
28
+ from gn2pg.check_conf import Gn2PgSourceConf
28
29
 
29
30
  logger = logging.getLogger(__name__)
30
31
 
@@ -33,8 +34,8 @@ class APIException(Exception):
33
34
  """An exception occurred while handling your request."""
34
35
 
35
36
 
36
- class HTTPError(APIException):
37
- """An HTTP error occurred."""
37
+ class ExportModuleNotFoundError(Exception):
38
+ """Custom exception raised when the EXPORTS module is not found."""
38
39
 
39
40
 
40
41
  class BaseAPI:
@@ -43,10 +44,10 @@ class BaseAPI:
43
44
 
44
45
  def __init__(
45
46
  self,
46
- config,
47
- controler,
47
+ config: Gn2PgSourceConf,
48
+ controler: str,
48
49
  ):
49
- self._config = config
50
+ self._config: Gn2PgSourceConf = config
50
51
  max_retry = config.max_retry
51
52
  max_requests = config.max_requests
52
53
  retry_delay = config.retry_delay
@@ -65,38 +66,42 @@ class BaseAPI:
65
66
  status_forcelist=[500, 501, 502, 503, 504],
66
67
  )
67
68
  self._session.mount("https://", HTTPAdapter(max_retries=retries))
68
- self._session.headers = {"Content-Type": "application/json"}
69
+ self._session.headers = {
70
+ "Content-Type": "application/json",
71
+ "Accept": "application/json, text/plain, */*",
72
+ }
69
73
  auth_payload = {
70
74
  "login": config.user_name,
71
75
  "password": config.user_password,
72
76
  }
73
- login = self._session.post(
74
- self._api_url + "auth/login",
75
- json=auth_payload,
76
- )
77
+
77
78
  try:
79
+ login = self._session.post(
80
+ self._api_url + "auth/login",
81
+ json=auth_payload,
82
+ )
83
+ login.raise_for_status()
78
84
  if login.status_code == 200:
79
85
  logger.info(
80
- "Successfully logged in into GeoNature named %s",
86
+ _("Successfully logged in into GeoNature named %s"),
81
87
  self._config.name,
82
88
  )
83
- else:
84
- logger.critical(
85
- (
86
- "Log in GeoNature named %s failed with status code %s, cause: %s",
87
- self._config.name,
88
- login.status_code,
89
- json.loads(login.content)["msg"],
90
- )
91
- )
92
-
93
- except Exception as error:
89
+ except HTTPError as error:
90
+ logger.critical(
91
+ _("Login into GeoNature from source %s failed with status code %s, message: %s"),
92
+ self._config.name,
93
+ error.response.status_code,
94
+ error.response.json(),
95
+ )
96
+ raise error
97
+ except InvalidSchema as error:
94
98
  logger.critical(
95
- "Session failed (%s), HTTP status code is %s", error, login.status_code
99
+ _("There is probably an error on source URL for %s , %s"), self._config.name, error
96
100
  )
97
- raise HTTPError(login.status_code) from error
101
+ raise error
98
102
 
99
103
  #  Find exports api path
104
+ self._export_api_path = None # Initialize the variable
100
105
  try:
101
106
  modules_list = self._session.get(self._api_url + "gn_commons/modules")
102
107
  logger.info(
@@ -104,6 +109,7 @@ class BaseAPI:
104
109
  modules_list.status_code,
105
110
  modules_list.url,
106
111
  )
112
+ modules_list.raise_for_status()
107
113
  if modules_list.status_code == 200 and "login?next=" not in modules_list.url:
108
114
  modules = json.loads(modules_list.content)
109
115
  for item in modules:
@@ -111,6 +117,16 @@ class BaseAPI:
111
117
  self._export_api_path = item["module_path"]
112
118
  logger.debug(_("Export api path is %s"), self._export_api_path)
113
119
  break
120
+ if self._export_api_path is None:
121
+ logger.critical(
122
+ _(
123
+ "EXPORTS module not found in the modules list for export %s. "
124
+ "User %s may not have required permissions on module."
125
+ ),
126
+ self._config.name,
127
+ self._config.user_name,
128
+ )
129
+ raise ExportModuleNotFoundError("Module not found")
114
130
  else:
115
131
  logger.critical(
116
132
  _("Get GeoNature modules failed with status code %s, cause: %s"),
@@ -118,9 +134,11 @@ class BaseAPI:
118
134
  json.loads(modules_list.content)["msg"],
119
135
  )
120
136
 
121
- except Exception as error:
122
- logger.critical(_("Find export module failed, %s"), error)
123
- raise HTTPError(login.status_code) from error
137
+ except HTTPError as error:
138
+ logger.critical(
139
+ _("Looking for export module failed for source %s , %s"), self._config.name, error
140
+ )
141
+ raise error
124
142
 
125
143
  @property
126
144
  def version(self) -> str:
@@ -217,9 +235,8 @@ class BaseAPI:
217
235
  return page_list, total_filtered, status_code
218
236
  except RetryError as e:
219
237
  last_response = e.response
220
- status_code = last_response.status_code
221
- logger.error(_("Export seems to be unavailable : %s"), e)
222
- raise RetryError from e
238
+ status_code = last_response.status_code if last_response is not None else None
239
+ raise e
223
240
 
224
241
  return None, 0, status_code
225
242
 
@@ -37,7 +37,6 @@ class ImportView(ReadOnlyView):
37
37
  "data_count_errors",
38
38
  "metadata_count_upserts",
39
39
  "metadata_count_errors",
40
- "xfer_http_status",
41
40
  "xfer_filters",
42
41
  "comment",
43
42
  )
@@ -19,7 +19,6 @@ class ImportLog(db.Model):
19
19
  )
20
20
  xfer_type = db.Column(db.String, index=True)
21
21
  xfer_status = db.Column(db.String, index=True)
22
- xfer_http_status = db.Column(db.String, index=True)
23
22
  xfer_filters = db.Column(JSONB, server_default="{}")
24
23
  xfer_start_ts = db.Column(
25
24
  db.DateTime(timezone=True),
@@ -20,10 +20,11 @@ from multiprocessing.pool import ThreadPool
20
20
  from threading import Thread
21
21
  from typing import Callable, List, Optional
22
22
 
23
- from requests.exceptions import RetryError
23
+ from requests.exceptions import HTTPError, InvalidSchema, RetryError
24
+ from urllib3.exceptions import ResponseError
24
25
 
25
26
  from gn2pg import _, __version__
26
- from gn2pg.api import DataAPI
27
+ from gn2pg.api import DataAPI, ExportModuleNotFoundError
27
28
  from gn2pg.check_conf import Gn2PgSourceConf
28
29
  from gn2pg.store_postgresql import StorePostgresql
29
30
  from gn2pg.utils import XferStatus
@@ -49,6 +50,7 @@ class DownloadGn:
49
50
  self, config: Gn2PgSourceConf, api_instance: DataAPI, backend: StorePostgresql
50
51
  ) -> None:
51
52
  self._config = config
53
+
52
54
  self._api_instance = api_instance
53
55
  self._backend = backend
54
56
  max_retry = config.max_retry
@@ -66,6 +68,8 @@ class DownloadGn:
66
68
  self.metadata_count_errors = 0
67
69
  self.xfer_type = ""
68
70
  self.xfer_filters = {}
71
+ self.xfer_status = XferStatus.init
72
+ self.xfer_comment = None
69
73
 
70
74
  self._limits = {
71
75
  "max_retry": max_retry,
@@ -75,7 +79,7 @@ class DownloadGn:
75
79
  # Init import log
76
80
  self.import_log_id = self._backend.import_log(
77
81
  controler=self._api_instance.controler,
78
- values={"xfer_status": XferStatus.init, "xfer_start_ts": datetime.now()},
82
+ values={"xfer_status": self.xfer_status, "xfer_start_ts": datetime.now()},
79
83
  )
80
84
 
81
85
  @property
@@ -147,28 +151,15 @@ class DownloadGn:
147
151
  # The Queue enables the report thread to get the progress from other threads
148
152
  self.queue: Queue = Queue()
149
153
  errors: List[Exception] = []
154
+
150
155
  # Initialize and start the report thread
151
156
  thread = Thread(target=report, args=[self.queue])
152
157
  thread.start()
158
+
153
159
  # Start the worker threads
154
160
  with ThreadPool(nb_threads) as thread:
155
161
  thread.map(partial(func, queue=self.queue), pages)
156
- # Will stop the report thread
157
- # self._backend.import_log(
158
- # controler=self._api_instance.controler,
159
- # id=self.import_log_id,
160
- # values={
161
- # "xfer_end_ts": datetime.now(),
162
- # "api_count_items": self.api_count_items,
163
- # "api_count_errors": self.api_count_errors,
164
- # "data_count_upserts": self.data_count_upserts,
165
- # "data_count_delete": self.data_count_delete,
166
- # "data_count_errors": self.data_count_errors,
167
- # "metadata_count_upserts": self.metadata_count_upserts,
168
- # "metadata_count_errors": self.metadata_count_errors,
169
- # "xfer_status": "success",
170
- # },
171
- # )
162
+
172
163
  self.queue.put(("DONE"))
173
164
  return errors
174
165
 
@@ -244,34 +235,41 @@ class DownloadGn:
244
235
  logger.info(_("QueryStrings %s"), params)
245
236
  pages = None
246
237
  try:
247
- pages, self.api_count_items, status_code = self._api_instance.page_list(
238
+ pages, self.api_count_items, _xfer_http_status = self._api_instance.page_list(
248
239
  kind="data", params=params
249
240
  )
250
- except RetryError:
241
+ except (RetryError, ResponseError) as e:
242
+ self.xfer_status = XferStatus.failed
243
+ self.xfer_comment = str(e)
244
+ logger.critical("%s %s %s", e, e.response, dir(e))
251
245
  logger.error(_("Could not retrieve API data from source %s"), self._config.name)
252
246
  return
253
247
 
254
248
  try:
255
249
  # input(f"FULL DOWNLOAD INPUT {self._config.name}")
256
250
  if pages:
251
+ self.xfer_status = XferStatus.import_data
252
+ self.xfer_type = "full"
253
+ self.xfer_filters = (json.dumps(params, default=str),)
257
254
  self._backend.import_log(
258
255
  controler=self._api_instance.controler,
259
256
  values={
260
- "xfer_type": "full",
261
- "xfer_status": XferStatus.import_data,
262
- "xfer_http_status": status_code,
263
- "xfer_filters": json.dumps(params),
257
+ "xfer_type": self.xfer_type,
258
+ "xfer_status": self.xfer_status,
259
+ "xfer_filters": self.xfer_filters,
264
260
  },
265
261
  )
266
262
 
267
263
  self.launch_threads(
268
264
  nb_threads=self._config.nb_threads, func=self.download, pages=pages
269
265
  )
270
-
266
+ self.xfer_status = XferStatus.success
271
267
  # Log download timestamp to download.
272
268
 
273
- except RetryError as e:
269
+ except (RetryError, ResponseError) as e:
274
270
  self.queue.put(("EXIT"))
271
+ self.xfer_status = XferStatus.failed
272
+ self.xfer_comment = str(e)
275
273
  logger.error(
276
274
  "A problem occured on FULL DOWNLOAD process for source %s : %s",
277
275
  self._config.name,
@@ -279,7 +277,7 @@ class DownloadGn:
279
277
  )
280
278
  return
281
279
 
282
- return
280
+ self.xfer_status = XferStatus.success
283
281
 
284
282
  def update(self, since: Optional[str] = None, actions: Optional[list] = None) -> None:
285
283
  """[summary]
@@ -323,16 +321,19 @@ class DownloadGn:
323
321
 
324
322
  # Process UPDATE
325
323
  try:
326
- upsert_pages, self.api_count_items, status_code = self._api_instance.page_list(
324
+ upsert_pages, self.api_count_items, _xfer_http_status = self._api_instance.page_list(
327
325
  kind="data", params=params
328
326
  )
327
+ self.xfer_type = "update"
328
+ self.xfer_status = XferStatus.import_data
329
+ self.xfer_filters = (json.dumps(params, default=str),)
330
+
329
331
  self._backend.import_log(
330
332
  controler=self._api_instance.controler,
331
333
  values={
332
334
  "xfer_type": "update",
333
- "xfer_status": XferStatus.import_data,
334
- "xfer_http_status": status_code,
335
- "xfer_filters": json.dumps(params, default=str),
335
+ "xfer_status": self.xfer_status,
336
+ "xfer_filters": self.xfer_filters,
336
337
  },
337
338
  )
338
339
  # input(f"UPDATE INPUT {self._config.name}")
@@ -342,10 +343,14 @@ class DownloadGn:
342
343
  func=self.download,
343
344
  pages=upsert_pages,
344
345
  )
345
- except RetryError as e:
346
+
347
+ except (RetryError, ResponseError) as e:
346
348
  self.queue.put(("EXIT"))
349
+ logger.critical("%s %s %s %s", dir(e), type(e), e.args, str(e))
350
+ self.xfer_status = XferStatus.failed
351
+ self.xfer_comment = str(e)
347
352
  logger.error(
348
- "A problem occured on UPDATE process for source %s : %s", self._config.name, e
353
+ _("A problem occured on UPDATE process for source %s : %s"), self._config.name, e
349
354
  )
350
355
  return
351
356
  # Process DELETE
@@ -355,7 +360,7 @@ class DownloadGn:
355
360
  since,
356
361
  )
357
362
  try:
358
- deleted_pages, _total_len, status_code = self._api_instance.page_list(
363
+ deleted_pages, _total_len, _xfer_http_status = self._api_instance.page_list(
359
364
  kind="log",
360
365
  params={
361
366
  "meta_last_action_date": f"gte:{since}",
@@ -365,25 +370,32 @@ class DownloadGn:
365
370
  pagination_param="page",
366
371
  )
367
372
  # input(f"DELETE INPUT {self._config.name}")
373
+ self.xfer_status = XferStatus.delete
368
374
  self._backend.import_log(
369
375
  controler=self._api_instance.controler,
370
- values={"xfer_status": XferStatus.delete, "xfer_http_status": status_code},
376
+ values={
377
+ "xfer_status": self.xfer_status,
378
+ },
371
379
  )
372
380
  if deleted_pages:
373
-
374
381
  self.launch_threads(
375
382
  nb_threads=self._config.nb_threads,
376
383
  func=self.delete,
377
384
  pages=deleted_pages,
378
385
  store=False,
379
386
  )
380
- except RetryError as e:
387
+
388
+ except (RetryError, ResponseError) as e:
381
389
  self.queue.put(("EXIT"))
390
+ self.xfer_status = XferStatus.failed
391
+ self.xfer_comment = str(e)
382
392
  logger.error(
383
393
  "A problem occured on DELETE process for source %s : %s", self._config.name, e
384
394
  )
385
395
  return
386
396
 
397
+ self.xfer_status = XferStatus.success
398
+
387
399
  def exit(self):
388
400
  """Final log on exit"""
389
401
  self._backend.import_log(
@@ -397,7 +409,8 @@ class DownloadGn:
397
409
  "data_count_errors": self.data_count_errors,
398
410
  "metadata_count_upserts": self.metadata_count_upserts,
399
411
  "metadata_count_errors": self.metadata_count_errors,
400
- "xfer_status": XferStatus.success,
412
+ "xfer_status": self.xfer_status,
413
+ "comment": self.xfer_comment,
401
414
  },
402
415
  )
403
416
 
@@ -411,4 +424,7 @@ class Data(DownloadGn):
411
424
  """
412
425
 
413
426
  def __init__(self, config, backend):
414
- super().__init__(config, DataAPI(config), backend)
427
+ try:
428
+ super().__init__(config, DataAPI(config), backend)
429
+ except (HTTPError, ExportModuleNotFoundError, InvalidSchema) as e:
430
+ logger.critical(e)
@@ -13,6 +13,8 @@ from os.path import isfile, join
13
13
  from pathlib import Path
14
14
  from typing import Optional
15
15
 
16
+ from requests.exceptions import InvalidSchema
17
+
16
18
  from gn2pg import _
17
19
  from gn2pg.download import Data
18
20
  from gn2pg.env import CONFDIR
@@ -74,19 +76,26 @@ def full_download_1source(ctrl, cfg):
74
76
 
75
77
  logger.debug(cfg)
76
78
  with StorePostgresql(cfg) as store_pg:
77
- downloader = ctrl(cfg, store_pg)
78
- logger.debug(
79
- _("%s => Starting download using controler %s"),
80
- cfg.source,
81
- downloader.name,
82
- )
83
- downloader.store()
84
- logger.info(
85
- _("%s => Ending download using controler %s"),
86
- cfg.source,
87
- downloader.name,
88
- )
89
- downloader.exit()
79
+ try:
80
+
81
+ downloader = ctrl(cfg, store_pg)
82
+ logger.debug(
83
+ _("%s => Starting download using controler %s"),
84
+ cfg.source,
85
+ downloader.name,
86
+ )
87
+ downloader.store()
88
+ logger.info(
89
+ _("%s => Ending download using controler %s"),
90
+ cfg.source,
91
+ downloader.name,
92
+ )
93
+ downloader.exit()
94
+ except (AttributeError, InvalidSchema) as e:
95
+ logger.critical(
96
+ _("An error occured when trying to download data from %s: %s"), cfg.name, e
97
+ )
98
+ return
90
99
 
91
100
 
92
101
  def full_download(cfg_ctrl):
@@ -114,19 +123,25 @@ def update_1source(ctrl, cfg):
114
123
  logger.debug(_("config source name %s"), cfg.name)
115
124
  logger.debug(_("controler %s"), ctrl)
116
125
  with StorePostgresql(cfg) as store_pg:
117
- downloader = ctrl(cfg, store_pg)
118
- logger.debug(
119
- _("%s => Starting update (%s)"),
120
- cfg.source,
121
- downloader.name,
122
- )
123
- downloader.update()
124
- logger.info(
125
- _("%s => Ending update (%s)"),
126
- cfg.name,
127
- downloader.name,
128
- )
129
- downloader.exit()
126
+ try:
127
+ downloader = ctrl(cfg, store_pg)
128
+ logger.debug(
129
+ _("%s => Starting update (%s)"),
130
+ cfg.source,
131
+ downloader.name,
132
+ )
133
+ downloader.update()
134
+ logger.info(
135
+ _("%s => Ending update (%s)"),
136
+ cfg.name,
137
+ downloader.name,
138
+ )
139
+ downloader.exit()
140
+ except AttributeError as e:
141
+ logger.critical(
142
+ _("An error occured when trying to download data from %s: %s"), cfg.name, e
143
+ )
144
+ return
130
145
 
131
146
 
132
147
  def update(cfg_ctrl):