hito_tools 26.1rc2__tar.gz → 26.1rc3__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.

Potentially problematic release.


This version of hito_tools might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hito_tools
3
- Version: 26.1rc2
3
+ Version: 26.1rc3
4
4
  Summary: Modules for interacting with Hito and NSIP
5
5
  License-Expression: BSD-3-Clause
6
6
  License-File: LICENSE
@@ -2,7 +2,7 @@
2
2
  import datetime
3
3
  import re
4
4
  from io import StringIO
5
- from typing import Dict, List
5
+ from typing import Dict, List, Optional
6
6
 
7
7
  import pandas as pd
8
8
 
@@ -30,6 +30,12 @@ HTTP_STATUS_FORBIDDEN = 403
30
30
  HTTP_STATUS_NOT_FOUND = 404
31
31
  HTTP_STATUS_CONFLICT = 409
32
32
 
33
+ AGENT_HAS_MULTIPLE_CONTRACTS_PATTERN = re.compile(
34
+ (
35
+ r'"Agent has active multi-contracts in same laboratory - manual action needed\s+'
36
+ r"\|\s+idAgentContract\s+:\s+(?P<id1>\d+)\s+\|\s+idAgentContract\s+:\s+(?P<id2>\d+)"
37
+ )
38
+ )
33
39
  AGENT_NOT_IN_PROJECT_PATTERN = re.compile(
34
40
  r'"Error : This project has not been affected to this agent, declaration forbidden"$'
35
41
  )
@@ -84,6 +90,26 @@ class NSIPConnection:
84
90
  f"{self.api_params[api_category][api_name]}"
85
91
  )
86
92
 
93
+ def _check_multiple_contracts(self, reason: str) -> Optional[str]:
94
+ """
95
+ One of the possible error during agent-related updates is that the user has
96
+ multiple contracts attached to the lab for the current period. In this case
97
+ the reason contains the different contract IDs: used the last one
98
+ the update is retried with the second contract (generally the current one)
99
+ mentioned in the error message.
100
+
101
+ :param reason: error message returned by API
102
+ :return: contract ID if it is the multiple contract error or None
103
+ """
104
+
105
+ m = AGENT_HAS_MULTIPLE_CONTRACTS_PATTERN.match(http_reason)
106
+ if m:
107
+ contract = m.group("id2")
108
+ else:
109
+ contract = None
110
+
111
+ return contract
112
+
87
113
  def get_agent_list(self, context: str = "NSIP"):
88
114
  """
89
115
  Retrieve NSIP agents from NSIP API and return a dict built from the retrieved JSON
@@ -117,27 +143,52 @@ class NSIPConnection:
117
143
  http_status, http_reason
118
144
  """
119
145
 
146
+ contract = None
120
147
  url = self._get_api_url("agent_api", "project_assign")
121
148
  params = {
122
149
  "projectId": project_id,
123
- "emailReseda": email,
124
150
  "startDate": start_date.date().isoformat(),
125
151
  "context": "NSIP",
126
152
  }
127
153
 
128
- r = requests.post(url, headers={"Authorization": f"Bearer {self.token}"}, params=params)
129
- if r.content:
130
- reason = r.content.decode("utf-8")
131
- else:
132
- reason = ""
154
+ retry = True
155
+ retry_attempts = 0
156
+ while retry:
157
+ # adding agent is retried only for some specific errors
158
+ retry = False
159
+
160
+ if contract:
161
+ print(f"INFO: adding agent {email} to project {project_id} using contract {contract}")
162
+ params["idAgentContract"] = contract
163
+ else:
164
+ params["emailReseda"] = email
165
+
166
+ r = requests.post(url, headers={"Authorization": f"Bearer {self.token}"}, params=params)
167
+ if r.content:
168
+ reason = r.content.decode("utf-8")
169
+ else:
170
+ reason = ""
171
+
172
+ if r.status_code == HTTP_STATUS_OK:
173
+ status = 0
174
+ elif r.status_code == HTTP_STATUS_CREATED:
175
+ status = 0
176
+ print(f"INFO: agent {email} successfully added to project {project_id}")
177
+ elif r.status_code == HTTP_STATUS_NOT_FOUND:
178
+ contract = self._check_multiple_contracts(r.reason)
179
+ if retry_attempts == 0 and contract is not None:
180
+ retry = True
181
+ print(
182
+ (
183
+ f"Agent {row.email_reseda} has several contracts for the"
184
+ f" current period: retrying update with contract {contract}"
185
+ )
186
+ )
187
+ else:
188
+ status = 1
189
+ else:
190
+ status = 1
133
191
 
134
- if r.status_code == HTTP_STATUS_OK:
135
- status = 0
136
- elif r.status_code == HTTP_STATUS_CREATED:
137
- status = 0
138
- print(f"INFO: agent {email} successfully added to project {project_id}")
139
- else:
140
- status = 1
141
192
  return status, r.status_code, reason
142
193
 
143
194
  def update_agent(
@@ -199,7 +250,6 @@ class NSIPConnection:
199
250
  time: int,
200
251
  period_start: str,
201
252
  validation_date: datetime.date = None,
202
- contract: int = None,
203
253
  ) -> int:
204
254
  """
205
255
  Add or update a project declaration for a user specified by its RESEDA email
@@ -211,22 +261,17 @@ class NSIPConnection:
211
261
  week)
212
262
  :param period_start: start date of the validation period
213
263
  :param validation_date: validation date
214
- :param contract: contract ID in case the agent has multiple contracts for the period
215
264
  :return: status (0 for successful add, -1 for successful update, positive value if errors),
216
265
  http_status if errors else declaration ID added/modified, http_reason
217
266
  """
218
267
 
268
+ contract = None
219
269
  url = self._get_api_url("agent_api", "declaration_add")
220
270
  if project_type:
221
271
  params = {"projectId": int(project_id), "referenceId": ""}
222
272
  else:
223
273
  params = {"projectId": "", "referenceId": int(project_id)}
224
274
  params["context"] = "NSIP"
225
- if contract:
226
- print(f"INFO: updating {email} declaration using contract {contract}")
227
- params["idAgentContract"] = contract
228
- else:
229
- params["emailReseda"] = email
230
275
  params["time"] = time
231
276
  if validation_date:
232
277
  validation_date_str = validation_date.date().isoformat()
@@ -238,6 +283,12 @@ class NSIPConnection:
238
283
  # declaration update is retried only for some specific errors
239
284
  retry = False
240
285
 
286
+ if contract:
287
+ print(f"INFO: updating {email} declaration using contract {contract}")
288
+ params["idAgentContract"] = contract
289
+ else:
290
+ params["emailReseda"] = email
291
+
241
292
  r = requests.post(url, headers={"Authorization": f"Bearer {self.token}"}, params=params)
242
293
  reason = r.text
243
294
 
@@ -289,6 +340,16 @@ class NSIPConnection:
289
340
  f" (error={http_status}, reason={http_reason})"
290
341
  )
291
342
  status = -2
343
+ else:
344
+ contract = self._check_multiple_contracts(r.reason)
345
+ if contract:
346
+ retry = True
347
+ print(
348
+ (
349
+ f"Agent {row.email_reseda} has several contracts for the"
350
+ f" current period: retrying update with contract {contract}"
351
+ )
352
+ )
292
353
 
293
354
  if r.status_code == HTTP_STATUS_OK:
294
355
  return status, declaration_id, reason
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "hito_tools"
7
- version = "26.1.rc2"
7
+ version = "26.1.rc3"
8
8
  description = "Modules for interacting with Hito and NSIP"
9
9
  readme = "README.md"
10
10
  authors = [ { name = "Michel Jouvin", email = "michel.jouvin@ijclab.in2p3.fr" } ]
File without changes
File without changes