cmsdials 1.2.0__tar.gz → 1.4.0__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.
- {cmsdials-1.2.0 → cmsdials-1.4.0}/PKG-INFO +49 -1
- {cmsdials-1.2.0 → cmsdials-1.4.0}/README.md +47 -0
- cmsdials-1.4.0/cmsdials/clients/file_index/client.py +13 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/file_index/models.py +1 -1
- cmsdials-1.4.0/cmsdials/clients/h1d/client.py +13 -0
- cmsdials-1.4.0/cmsdials/clients/h2d/client.py +13 -0
- cmsdials-1.4.0/cmsdials/clients/lumisection/client.py +13 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/mes/client.py +4 -0
- cmsdials-1.4.0/cmsdials/clients/oms_proxy/client.py +46 -0
- cmsdials-1.4.0/cmsdials/clients/oms_proxy/models.py +14 -0
- cmsdials-1.4.0/cmsdials/clients/run/client.py +13 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/cmsdials.py +2 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/filters.py +3 -0
- cmsdials-1.4.0/cmsdials/utils/__init__.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/utils/api_client.py +82 -13
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/utils/base_model.py +4 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/pyproject.toml +2 -1
- cmsdials-1.2.0/cmsdials/clients/file_index/client.py +0 -27
- cmsdials-1.2.0/cmsdials/clients/h1d/client.py +0 -27
- cmsdials-1.2.0/cmsdials/clients/h2d/client.py +0 -27
- cmsdials-1.2.0/cmsdials/clients/lumisection/client.py +0 -27
- cmsdials-1.2.0/cmsdials/clients/run/client.py +0 -27
- {cmsdials-1.2.0 → cmsdials-1.4.0}/LICENSE +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/__init__.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/auth/__init__.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/auth/_base.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/auth/bearer.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/auth/client.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/auth/exceptions.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/auth/models.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/auth/secret_key.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/__init__.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/file_index/__init__.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/h1d/__init__.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/h1d/models.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/h2d/__init__.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/h2d/models.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/lumisection/__init__.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/lumisection/models.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/mes/__init__.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/mes/models.py +0 -0
- {cmsdials-1.2.0/cmsdials/clients/run → cmsdials-1.4.0/cmsdials/clients/oms_proxy}/__init__.py +0 -0
- {cmsdials-1.2.0/cmsdials/utils → cmsdials-1.4.0/cmsdials/clients/run}/__init__.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/clients/run/models.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/utils/_json.py +0 -0
- {cmsdials-1.2.0 → cmsdials-1.4.0}/cmsdials/utils/logger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cmsdials
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: The Python api client interface to DIALS service
|
|
5
5
|
Home-page: https://github.com/cms-DQM/dials-py
|
|
6
6
|
Author: Gabriel Moreira
|
|
@@ -22,6 +22,7 @@ Requires-Dist: pydantic (>=1,<2)
|
|
|
22
22
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
23
23
|
Requires-Dist: tqdm (>=4.66.1) ; extra == "tqdm"
|
|
24
24
|
Requires-Dist: typing-extensions (>=3.6.6,<4.6.0)
|
|
25
|
+
Requires-Dist: urllib3 (>=2.2.3,<3.0.0)
|
|
25
26
|
Project-URL: Repository, https://github.com/cms-DQM/dials-py
|
|
26
27
|
Description-Content-Type: text/markdown
|
|
27
28
|
|
|
@@ -200,6 +201,53 @@ Progress: 100%|█████████████████████
|
|
|
200
201
|
|
|
201
202
|
The total attribute of the bar is dynamically updated while fetching the pages.
|
|
202
203
|
|
|
204
|
+
## Retrying
|
|
205
|
+
|
|
206
|
+
In case of an unstable connection, DNS failures or service overload it is possible to configure any `get`, `list` and `list_all` to retry the underlying requests using native `urllib3` retry class, for example:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from urllib3.util import Retry
|
|
210
|
+
|
|
211
|
+
data = dials.h1d.get(dataset_id=14677060, run_number=367112, ls_number=10, me_id=1, retries=Retry(total=3, backoff_factor=0.1))
|
|
212
|
+
data = dials.h1d.list(retries=Retry(total=3, backoff_factor=0.1))
|
|
213
|
+
data = dials.h1d.list_all(LumisectionHistogram1DFilters(), max_pages=5, retries=Retry(total=5, backoff_factor=0.1))
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Resuming
|
|
217
|
+
|
|
218
|
+
When listing and endpoint for a long time, you may loose connection or the server can potentially return an error. By specifying `keep_failed` and using `resume_from` you can resume from an older response object, for example:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from cmsdials.auth.client import AuthClient
|
|
222
|
+
from cmsdials.auth.bearer import Credentials
|
|
223
|
+
from cmsdials import Dials
|
|
224
|
+
from cmsdials.filters import LumisectionHistogram2DFilters
|
|
225
|
+
|
|
226
|
+
auth = AuthClient()
|
|
227
|
+
creds = Credentials.from_creds_file()
|
|
228
|
+
dials = Dials(creds, workspace="tracker")
|
|
229
|
+
|
|
230
|
+
data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), keep_failed=True) # Code may broke inside this routine
|
|
231
|
+
print(len(data.results)) # 100
|
|
232
|
+
print(data.exc_type)
|
|
233
|
+
print(data.exc_formatted)
|
|
234
|
+
|
|
235
|
+
# After failing, run this again
|
|
236
|
+
data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), keep_failed=True, resume_from=data) # Resume from failed object
|
|
237
|
+
print(len(data.results)) # 200
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
You may find it useful to resume from a partial response, i.e,
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), max_pages=10, keep_failed=True) # Code may not break, however will fetch only 100 elements
|
|
244
|
+
print(len(data.results)) # 100
|
|
245
|
+
|
|
246
|
+
# After fetching first 100, fetch next 100
|
|
247
|
+
data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), max_pages=10, keep_failed=True, resume_from=data) # Resume from partial object
|
|
248
|
+
print(len(data.results)) # 200
|
|
249
|
+
```
|
|
250
|
+
|
|
203
251
|
## Usage with local DIALS
|
|
204
252
|
|
|
205
253
|
All classes that interface the DIALS service inherits the class `BaseAPIClient` which propagate the `base_url`, `route` and `version` attributes with production values. In order to use dials-py with a local version of DIALS it is possible to overwrite those attributes when instantiating the `AuthClient` and the `Dials` client, for example:
|
|
@@ -173,6 +173,53 @@ Progress: 100%|█████████████████████
|
|
|
173
173
|
|
|
174
174
|
The total attribute of the bar is dynamically updated while fetching the pages.
|
|
175
175
|
|
|
176
|
+
## Retrying
|
|
177
|
+
|
|
178
|
+
In case of an unstable connection, DNS failures or service overload it is possible to configure any `get`, `list` and `list_all` to retry the underlying requests using native `urllib3` retry class, for example:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from urllib3.util import Retry
|
|
182
|
+
|
|
183
|
+
data = dials.h1d.get(dataset_id=14677060, run_number=367112, ls_number=10, me_id=1, retries=Retry(total=3, backoff_factor=0.1))
|
|
184
|
+
data = dials.h1d.list(retries=Retry(total=3, backoff_factor=0.1))
|
|
185
|
+
data = dials.h1d.list_all(LumisectionHistogram1DFilters(), max_pages=5, retries=Retry(total=5, backoff_factor=0.1))
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Resuming
|
|
189
|
+
|
|
190
|
+
When listing and endpoint for a long time, you may loose connection or the server can potentially return an error. By specifying `keep_failed` and using `resume_from` you can resume from an older response object, for example:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from cmsdials.auth.client import AuthClient
|
|
194
|
+
from cmsdials.auth.bearer import Credentials
|
|
195
|
+
from cmsdials import Dials
|
|
196
|
+
from cmsdials.filters import LumisectionHistogram2DFilters
|
|
197
|
+
|
|
198
|
+
auth = AuthClient()
|
|
199
|
+
creds = Credentials.from_creds_file()
|
|
200
|
+
dials = Dials(creds, workspace="tracker")
|
|
201
|
+
|
|
202
|
+
data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), keep_failed=True) # Code may broke inside this routine
|
|
203
|
+
print(len(data.results)) # 100
|
|
204
|
+
print(data.exc_type)
|
|
205
|
+
print(data.exc_formatted)
|
|
206
|
+
|
|
207
|
+
# After failing, run this again
|
|
208
|
+
data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), keep_failed=True, resume_from=data) # Resume from failed object
|
|
209
|
+
print(len(data.results)) # 200
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
You may find it useful to resume from a partial response, i.e,
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), max_pages=10, keep_failed=True) # Code may not break, however will fetch only 100 elements
|
|
216
|
+
print(len(data.results)) # 100
|
|
217
|
+
|
|
218
|
+
# After fetching first 100, fetch next 100
|
|
219
|
+
data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), max_pages=10, keep_failed=True, resume_from=data) # Resume from partial object
|
|
220
|
+
print(len(data.results)) # 200
|
|
221
|
+
```
|
|
222
|
+
|
|
176
223
|
## Usage with local DIALS
|
|
177
224
|
|
|
178
225
|
All classes that interface the DIALS service inherits the class `BaseAPIClient` which propagate the `base_url`, `route` and `version` attributes with production values. In order to use dials-py with a local version of DIALS it is possible to overwrite those attributes when instantiating the `AuthClient` and the `Dials` client, for example:
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ...utils.api_client import BaseAuthorizedAPIClient
|
|
2
|
+
from .models import FileIndex, FileIndexFilters, PaginatedFileIndexList
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FileIndexClient(BaseAuthorizedAPIClient):
|
|
6
|
+
data_model = FileIndex
|
|
7
|
+
pagination_model = PaginatedFileIndexList
|
|
8
|
+
filter_class = FileIndexFilters
|
|
9
|
+
lookup_url = "file-index/"
|
|
10
|
+
|
|
11
|
+
def get(self, dataset_id: int, file_id: int, **kwargs):
|
|
12
|
+
edp = f"{dataset_id}/{file_id}/"
|
|
13
|
+
return super().get(edp, **kwargs)
|
|
@@ -14,7 +14,7 @@ class FileIndex(BaseModel):
|
|
|
14
14
|
creation_date: datetime
|
|
15
15
|
last_modification_date: datetime
|
|
16
16
|
logical_file_name: str
|
|
17
|
-
status: str = Field(..., max_length=
|
|
17
|
+
status: str = Field(..., max_length=255)
|
|
18
18
|
err_trace: Optional[str] = Field(..., max_length=5000)
|
|
19
19
|
|
|
20
20
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ...utils.api_client import BaseAuthorizedAPIClient
|
|
2
|
+
from .models import LumisectionHistogram1D, LumisectionHistogram1DFilters, PaginatedLumisectionHistogram1DList
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class LumisectionHistogram1DClient(BaseAuthorizedAPIClient):
|
|
6
|
+
data_model = LumisectionHistogram1D
|
|
7
|
+
pagination_model = PaginatedLumisectionHistogram1DList
|
|
8
|
+
filter_class = LumisectionHistogram1DFilters
|
|
9
|
+
lookup_url = "th1/"
|
|
10
|
+
|
|
11
|
+
def get(self, dataset_id: int, run_number: int, ls_number: int, me_id: int, **kwargs):
|
|
12
|
+
edp = f"{dataset_id}/{run_number}/{ls_number}/{me_id}/"
|
|
13
|
+
return super().get(edp, **kwargs)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ...utils.api_client import BaseAuthorizedAPIClient
|
|
2
|
+
from .models import LumisectionHistogram2D, LumisectionHistogram2DFilters, PaginatedLumisectionHistogram2DList
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class LumisectionHistogram2DClient(BaseAuthorizedAPIClient):
|
|
6
|
+
data_model = LumisectionHistogram2D
|
|
7
|
+
pagination_model = PaginatedLumisectionHistogram2DList
|
|
8
|
+
filter_class = LumisectionHistogram2DFilters
|
|
9
|
+
lookup_url = "th2/"
|
|
10
|
+
|
|
11
|
+
def get(self, dataset_id: int, run_number: int, ls_number: int, me_id: int, **kwargs):
|
|
12
|
+
edp = f"{dataset_id}/{run_number}/{ls_number}/{me_id}/"
|
|
13
|
+
return super().get(edp, **kwargs)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ...utils.api_client import BaseAuthorizedAPIClient
|
|
2
|
+
from .models import Lumisection, LumisectionFilters, PaginatedLumisectionList
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class LumisectionClient(BaseAuthorizedAPIClient):
|
|
6
|
+
data_model = Lumisection
|
|
7
|
+
pagination_model = PaginatedLumisectionList
|
|
8
|
+
filter_class = LumisectionFilters
|
|
9
|
+
lookup_url = "lumisection/"
|
|
10
|
+
|
|
11
|
+
def get(self, dataset_id: int, run_number: int, ls_number: int, **kwargs):
|
|
12
|
+
edp = f"{dataset_id}/{run_number}/{ls_number}/"
|
|
13
|
+
return super().get(edp, **kwargs)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
from requests.exceptions import HTTPError
|
|
5
|
+
|
|
6
|
+
from ...auth._base import BaseCredentials
|
|
7
|
+
from ...utils.api_client import BaseAPIClient
|
|
8
|
+
from ...utils.logger import logger
|
|
9
|
+
from .models import OMSFilter, OMSPage
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OMSProxyClient(BaseAPIClient):
|
|
13
|
+
default_timeout = 30
|
|
14
|
+
lookup_url = "oms-proxy/"
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
creds: BaseCredentials,
|
|
19
|
+
*args: str,
|
|
20
|
+
**kwargs: str,
|
|
21
|
+
) -> None:
|
|
22
|
+
super().__init__(*args, **kwargs)
|
|
23
|
+
self.creds = creds
|
|
24
|
+
|
|
25
|
+
def _build_headers(self) -> dict:
|
|
26
|
+
base = {"accept": "application/json"}
|
|
27
|
+
self.creds.before_request(base)
|
|
28
|
+
return base
|
|
29
|
+
|
|
30
|
+
def query(self, endpoint: str, filters: list[OMSFilter], pages: Optional[list[OMSPage]] = None):
|
|
31
|
+
headers = self._build_headers()
|
|
32
|
+
endpoint_url = self.api_url + self.lookup_url
|
|
33
|
+
|
|
34
|
+
# Format filters
|
|
35
|
+
filters = {f"filter[{_filter.attribute_name}][{_filter.operator}]": _filter.value for _filter in filters}
|
|
36
|
+
pages = {f"page[{page.attribute_name}]": page.value for page in pages} if pages and len(pages) > 0 else {}
|
|
37
|
+
params = {"endpoint": endpoint, **filters, **pages}
|
|
38
|
+
|
|
39
|
+
response = requests.get(endpoint_url, headers=headers, params=params, timeout=self.default_timeout)
|
|
40
|
+
try:
|
|
41
|
+
response.raise_for_status()
|
|
42
|
+
except HTTPError as err:
|
|
43
|
+
logger.info(f"Api raw response: {response.text}")
|
|
44
|
+
raise err
|
|
45
|
+
|
|
46
|
+
return response.json()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Literal, Union
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class OMSFilter(BaseModel):
|
|
7
|
+
attribute_name: str
|
|
8
|
+
value: Union[str, int, float]
|
|
9
|
+
operator: Literal["EQ", "NEQ", "LT", "GT", "LE", "GE", "LIKE"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OMSPage(BaseModel):
|
|
13
|
+
attribute_name: str
|
|
14
|
+
value: int
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ...utils.api_client import BaseAuthorizedAPIClient
|
|
2
|
+
from .models import PaginatedRunList, Run, RunFilters
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RunClient(BaseAuthorizedAPIClient):
|
|
6
|
+
data_model = Run
|
|
7
|
+
pagination_model = PaginatedRunList
|
|
8
|
+
filter_class = RunFilters
|
|
9
|
+
lookup_url = "run/"
|
|
10
|
+
|
|
11
|
+
def get(self, dataset_id: int, run_number: int, **kwargs):
|
|
12
|
+
edp = f"{dataset_id}/{run_number}/"
|
|
13
|
+
return super().get(edp, **kwargs)
|
|
@@ -6,6 +6,7 @@ from .clients.h1d.client import LumisectionHistogram1DClient
|
|
|
6
6
|
from .clients.h2d.client import LumisectionHistogram2DClient
|
|
7
7
|
from .clients.lumisection.client import LumisectionClient
|
|
8
8
|
from .clients.mes.client import MonitoringElementClient
|
|
9
|
+
from .clients.oms_proxy.client import OMSProxyClient
|
|
9
10
|
from .clients.run.client import RunClient
|
|
10
11
|
|
|
11
12
|
|
|
@@ -17,3 +18,4 @@ class Dials:
|
|
|
17
18
|
self.lumi = LumisectionClient(creds, workspace, *args, **kwargs)
|
|
18
19
|
self.run = RunClient(creds, workspace, *args, **kwargs)
|
|
19
20
|
self.mes = MonitoringElementClient(creds, workspace, *args, **kwargs)
|
|
21
|
+
self.oms = OMSProxyClient(creds, *args, **kwargs)
|
|
@@ -3,6 +3,7 @@ from .clients.h1d.models import LumisectionHistogram1DFilters
|
|
|
3
3
|
from .clients.h2d.models import LumisectionHistogram2DFilters
|
|
4
4
|
from .clients.lumisection.models import LumisectionFilters
|
|
5
5
|
from .clients.mes.models import MEFilters
|
|
6
|
+
from .clients.oms_proxy.models import OMSFilter, OMSPage
|
|
6
7
|
from .clients.run.models import RunFilters
|
|
7
8
|
|
|
8
9
|
|
|
@@ -13,4 +14,6 @@ __all__ = [
|
|
|
13
14
|
"LumisectionFilters",
|
|
14
15
|
"RunFilters",
|
|
15
16
|
"MEFilters",
|
|
17
|
+
"OMSFilter",
|
|
18
|
+
"OMSPage",
|
|
16
19
|
]
|
|
File without changes
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from importlib import util as importlib_util
|
|
2
|
+
from traceback import format_exc
|
|
2
3
|
from typing import Optional
|
|
3
4
|
from urllib.parse import parse_qs, urlparse
|
|
5
|
+
from warnings import warn
|
|
4
6
|
|
|
5
|
-
import
|
|
7
|
+
from requests import Response, Session
|
|
8
|
+
from requests.adapters import DEFAULT_RETRIES, HTTPAdapter
|
|
6
9
|
from requests.exceptions import HTTPError
|
|
7
10
|
|
|
8
11
|
from ..auth._base import BaseCredentials
|
|
@@ -35,6 +38,19 @@ class BaseAPIClient:
|
|
|
35
38
|
return value + "/"
|
|
36
39
|
return value
|
|
37
40
|
|
|
41
|
+
@classmethod
|
|
42
|
+
def _requests_get_retriable(cls, *args, retries=DEFAULT_RETRIES, **kwargs) -> Response:
|
|
43
|
+
"""
|
|
44
|
+
requests.get() with an additional `retries` parameter.
|
|
45
|
+
|
|
46
|
+
Specify retries=<number of attempts - 1> for simple use cases.
|
|
47
|
+
For advanced usage, see https://docs.python-requests.org/en/latest/user/advanced/
|
|
48
|
+
"""
|
|
49
|
+
with Session() as s:
|
|
50
|
+
s.mount(cls.PRODUCTION_BASE_URL, HTTPAdapter(max_retries=retries))
|
|
51
|
+
ret = s.get(*args, **kwargs)
|
|
52
|
+
return ret
|
|
53
|
+
|
|
38
54
|
@property
|
|
39
55
|
def api_url(self):
|
|
40
56
|
return self.base_url + self.route + self.version
|
|
@@ -65,10 +81,15 @@ class BaseAuthorizedAPIClient(BaseAPIClient):
|
|
|
65
81
|
self.creds.before_request(base)
|
|
66
82
|
return base
|
|
67
83
|
|
|
68
|
-
def get(self,
|
|
69
|
-
endpoint_url = self.api_url + self.lookup_url +
|
|
84
|
+
def get(self, edp: str, retries=DEFAULT_RETRIES): # noqa: A002
|
|
85
|
+
endpoint_url = self.api_url + self.lookup_url + edp
|
|
70
86
|
headers = self._build_headers()
|
|
71
|
-
response =
|
|
87
|
+
response = self._requests_get_retriable(
|
|
88
|
+
endpoint_url,
|
|
89
|
+
headers=headers,
|
|
90
|
+
timeout=self.default_timeout,
|
|
91
|
+
retries=retries,
|
|
92
|
+
)
|
|
72
93
|
|
|
73
94
|
try:
|
|
74
95
|
response.raise_for_status()
|
|
@@ -79,11 +100,17 @@ class BaseAuthorizedAPIClient(BaseAPIClient):
|
|
|
79
100
|
response = response.json()
|
|
80
101
|
return self.data_model(**response)
|
|
81
102
|
|
|
82
|
-
def list(self, filters=None):
|
|
103
|
+
def list(self, filters=None, retries=DEFAULT_RETRIES):
|
|
83
104
|
filters = filters or self.filter_class()
|
|
84
105
|
endpoint_url = self.api_url + self.lookup_url
|
|
85
106
|
headers = self._build_headers()
|
|
86
|
-
response =
|
|
107
|
+
response = self._requests_get_retriable(
|
|
108
|
+
endpoint_url,
|
|
109
|
+
headers=headers,
|
|
110
|
+
params=filters.cleandict(),
|
|
111
|
+
timeout=self.default_timeout,
|
|
112
|
+
retries=retries,
|
|
113
|
+
)
|
|
87
114
|
|
|
88
115
|
try:
|
|
89
116
|
response.raise_for_status()
|
|
@@ -99,10 +126,30 @@ class BaseAuthorizedAPIClient(BaseAPIClient):
|
|
|
99
126
|
|
|
100
127
|
raise ValueError("pagination model is None and response is not a list.")
|
|
101
128
|
|
|
102
|
-
def __list_sync(
|
|
103
|
-
|
|
129
|
+
def __list_sync(
|
|
130
|
+
self,
|
|
131
|
+
filters,
|
|
132
|
+
max_pages: Optional[int] = None,
|
|
133
|
+
enable_progress: bool = False,
|
|
134
|
+
retries=DEFAULT_RETRIES,
|
|
135
|
+
keep_failed: bool = False,
|
|
136
|
+
resume_from=None,
|
|
137
|
+
):
|
|
138
|
+
next_string: Optional[str] = None
|
|
104
139
|
results = []
|
|
105
140
|
is_last_page = False
|
|
141
|
+
|
|
142
|
+
if resume_from is not None:
|
|
143
|
+
results = resume_from.results
|
|
144
|
+
next_string = resume_from.next
|
|
145
|
+
if next_string is None and len(results):
|
|
146
|
+
warn(
|
|
147
|
+
"resume_from.next is None while resume_from.result is not empty, doing nothing.",
|
|
148
|
+
RuntimeWarning,
|
|
149
|
+
stacklevel=2,
|
|
150
|
+
)
|
|
151
|
+
is_last_page = True
|
|
152
|
+
|
|
106
153
|
total_pages = 0
|
|
107
154
|
use_tqdm = TQDM_INSTALLED and enable_progress
|
|
108
155
|
|
|
@@ -110,12 +157,34 @@ class BaseAuthorizedAPIClient(BaseAPIClient):
|
|
|
110
157
|
progress = tqdm(desc="Progress", total=1)
|
|
111
158
|
|
|
112
159
|
while is_last_page is False:
|
|
160
|
+
next_token = parse_qs(urlparse(next_string).query).get("next_token") if next_string else None
|
|
113
161
|
curr_filters = self.filter_class(**filters.dict())
|
|
114
162
|
curr_filters.next_token = next_token
|
|
115
|
-
|
|
163
|
+
try:
|
|
164
|
+
response = self.list(curr_filters, retries=retries)
|
|
165
|
+
except Exception as e: # noqa: BLE001
|
|
166
|
+
if use_tqdm:
|
|
167
|
+
progress.close()
|
|
168
|
+
|
|
169
|
+
if not keep_failed:
|
|
170
|
+
raise e
|
|
171
|
+
|
|
172
|
+
exc_formatted = format_exc()
|
|
173
|
+
warn(
|
|
174
|
+
"HTTP request failed, returning partial results. Exception: " + exc_formatted,
|
|
175
|
+
RuntimeWarning,
|
|
176
|
+
stacklevel=2,
|
|
177
|
+
)
|
|
178
|
+
return self.pagination_model(
|
|
179
|
+
next=next_string,
|
|
180
|
+
previous=None,
|
|
181
|
+
results=results,
|
|
182
|
+
exc_type=e.__class__.__name__,
|
|
183
|
+
exc_formatted=exc_formatted,
|
|
184
|
+
)
|
|
116
185
|
results.extend(response.results)
|
|
117
|
-
|
|
118
|
-
|
|
186
|
+
next_string = response.next
|
|
187
|
+
is_last_page = next_string is None
|
|
119
188
|
total_pages += 1
|
|
120
189
|
max_pages_reached = max_pages and total_pages >= max_pages
|
|
121
190
|
if use_tqdm:
|
|
@@ -131,12 +200,12 @@ class BaseAuthorizedAPIClient(BaseAPIClient):
|
|
|
131
200
|
progress.close()
|
|
132
201
|
|
|
133
202
|
return self.pagination_model(
|
|
134
|
-
next=
|
|
203
|
+
next=next_string,
|
|
135
204
|
previous=None,
|
|
136
205
|
results=results, # No problem re-using last response count
|
|
137
206
|
)
|
|
138
207
|
|
|
139
208
|
def list_all(self, filters, max_pages: Optional[int] = None, **kwargs):
|
|
140
209
|
if self.pagination_model is None:
|
|
141
|
-
return self.list(filters)
|
|
210
|
+
return self.list(filters, retries=kwargs.get("retries", DEFAULT_RETRIES))
|
|
142
211
|
return self.__list_sync(filters, max_pages, **kwargs)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from importlib import util as importlib_util
|
|
2
|
+
from typing import Optional
|
|
2
3
|
|
|
3
4
|
from pydantic import BaseModel
|
|
4
5
|
|
|
@@ -17,6 +18,9 @@ class OBaseModel(BaseModel):
|
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class PaginatedBaseModel(BaseModel):
|
|
21
|
+
exc_type: Optional[str] = None
|
|
22
|
+
exc_formatted: Optional[str] = None
|
|
23
|
+
|
|
20
24
|
def to_pandas(self):
|
|
21
25
|
if PANDAS_NOT_INSTALLED:
|
|
22
26
|
raise RuntimeError(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "cmsdials"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.4.0"
|
|
4
4
|
description = "The Python api client interface to DIALS service"
|
|
5
5
|
authors = ["Gabriel Moreira <gabrielmscampos@gmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -23,6 +23,7 @@ pydantic = "<2, >=1"
|
|
|
23
23
|
typing-extensions = "<4.6.0, >=3.6.6"
|
|
24
24
|
pandas = {version = ">=1.5.3", optional = true}
|
|
25
25
|
tqdm = {version = ">=4.66.1", optional = true}
|
|
26
|
+
urllib3 = "^2.2.3"
|
|
26
27
|
|
|
27
28
|
[tool.poetry.group.dev.dependencies]
|
|
28
29
|
pre-commit = "^3.6.2"
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
from requests.exceptions import HTTPError
|
|
3
|
-
|
|
4
|
-
from ...utils.api_client import BaseAuthorizedAPIClient
|
|
5
|
-
from ...utils.logger import logger
|
|
6
|
-
from .models import FileIndex, FileIndexFilters, PaginatedFileIndexList
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class FileIndexClient(BaseAuthorizedAPIClient):
|
|
10
|
-
data_model = FileIndex
|
|
11
|
-
pagination_model = PaginatedFileIndexList
|
|
12
|
-
filter_class = FileIndexFilters
|
|
13
|
-
lookup_url = "file-index/"
|
|
14
|
-
|
|
15
|
-
def get(self, dataset_id: int, file_id: int):
|
|
16
|
-
endpoint_url = f"{self.api_url}{self.lookup_url}{dataset_id}/{file_id}/"
|
|
17
|
-
headers = self._build_headers()
|
|
18
|
-
response = requests.get(endpoint_url, headers=headers, timeout=self.default_timeout)
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
response.raise_for_status()
|
|
22
|
-
except HTTPError as err:
|
|
23
|
-
logger.info(f"Api raw response: {response.text}")
|
|
24
|
-
raise err
|
|
25
|
-
|
|
26
|
-
response = response.json()
|
|
27
|
-
return self.data_model(**response)
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
from requests.exceptions import HTTPError
|
|
3
|
-
|
|
4
|
-
from ...utils.api_client import BaseAuthorizedAPIClient
|
|
5
|
-
from ...utils.logger import logger
|
|
6
|
-
from .models import LumisectionHistogram1D, LumisectionHistogram1DFilters, PaginatedLumisectionHistogram1DList
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class LumisectionHistogram1DClient(BaseAuthorizedAPIClient):
|
|
10
|
-
data_model = LumisectionHistogram1D
|
|
11
|
-
pagination_model = PaginatedLumisectionHistogram1DList
|
|
12
|
-
filter_class = LumisectionHistogram1DFilters
|
|
13
|
-
lookup_url = "th1/"
|
|
14
|
-
|
|
15
|
-
def get(self, dataset_id: int, run_number: int, ls_number: int, me_id: int):
|
|
16
|
-
endpoint_url = f"{self.api_url}{self.lookup_url}{dataset_id}/{run_number}/{ls_number}/{me_id}/"
|
|
17
|
-
headers = self._build_headers()
|
|
18
|
-
response = requests.get(endpoint_url, headers=headers, timeout=self.default_timeout)
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
response.raise_for_status()
|
|
22
|
-
except HTTPError as err:
|
|
23
|
-
logger.info(f"Api raw response: {response.text}")
|
|
24
|
-
raise err
|
|
25
|
-
|
|
26
|
-
response = response.json()
|
|
27
|
-
return self.data_model(**response)
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
from requests.exceptions import HTTPError
|
|
3
|
-
|
|
4
|
-
from ...utils.api_client import BaseAuthorizedAPIClient
|
|
5
|
-
from ...utils.logger import logger
|
|
6
|
-
from .models import LumisectionHistogram2D, LumisectionHistogram2DFilters, PaginatedLumisectionHistogram2DList
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class LumisectionHistogram2DClient(BaseAuthorizedAPIClient):
|
|
10
|
-
data_model = LumisectionHistogram2D
|
|
11
|
-
pagination_model = PaginatedLumisectionHistogram2DList
|
|
12
|
-
filter_class = LumisectionHistogram2DFilters
|
|
13
|
-
lookup_url = "th2/"
|
|
14
|
-
|
|
15
|
-
def get(self, dataset_id: int, run_number: int, ls_number: int, me_id: int):
|
|
16
|
-
endpoint_url = f"{self.api_url}{self.lookup_url}{dataset_id}/{run_number}/{ls_number}/{me_id}/"
|
|
17
|
-
headers = self._build_headers()
|
|
18
|
-
response = requests.get(endpoint_url, headers=headers, timeout=self.default_timeout)
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
response.raise_for_status()
|
|
22
|
-
except HTTPError as err:
|
|
23
|
-
logger.info(f"Api raw response: {response.text}")
|
|
24
|
-
raise err
|
|
25
|
-
|
|
26
|
-
response = response.json()
|
|
27
|
-
return self.data_model(**response)
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
from requests.exceptions import HTTPError
|
|
3
|
-
|
|
4
|
-
from ...utils.api_client import BaseAuthorizedAPIClient
|
|
5
|
-
from ...utils.logger import logger
|
|
6
|
-
from .models import Lumisection, LumisectionFilters, PaginatedLumisectionList
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class LumisectionClient(BaseAuthorizedAPIClient):
|
|
10
|
-
data_model = Lumisection
|
|
11
|
-
pagination_model = PaginatedLumisectionList
|
|
12
|
-
filter_class = LumisectionFilters
|
|
13
|
-
lookup_url = "lumisection/"
|
|
14
|
-
|
|
15
|
-
def get(self, dataset_id: int, run_number: int, ls_number: int):
|
|
16
|
-
endpoint_url = f"{self.api_url}{self.lookup_url}{dataset_id}/{run_number}/{ls_number}/"
|
|
17
|
-
headers = self._build_headers()
|
|
18
|
-
response = requests.get(endpoint_url, headers=headers, timeout=self.default_timeout)
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
response.raise_for_status()
|
|
22
|
-
except HTTPError as err:
|
|
23
|
-
logger.info(f"Api raw response: {response.text}")
|
|
24
|
-
raise err
|
|
25
|
-
|
|
26
|
-
response = response.json()
|
|
27
|
-
return self.data_model(**response)
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
from requests.exceptions import HTTPError
|
|
3
|
-
|
|
4
|
-
from ...utils.api_client import BaseAuthorizedAPIClient
|
|
5
|
-
from ...utils.logger import logger
|
|
6
|
-
from .models import PaginatedRunList, Run, RunFilters
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class RunClient(BaseAuthorizedAPIClient):
|
|
10
|
-
data_model = Run
|
|
11
|
-
pagination_model = PaginatedRunList
|
|
12
|
-
filter_class = RunFilters
|
|
13
|
-
lookup_url = "run/"
|
|
14
|
-
|
|
15
|
-
def get(self, dataset_id: int, run_number: int):
|
|
16
|
-
endpoint_url = f"{self.api_url}{self.lookup_url}{dataset_id}/{run_number}/"
|
|
17
|
-
headers = self._build_headers()
|
|
18
|
-
response = requests.get(endpoint_url, headers=headers, timeout=self.default_timeout)
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
response.raise_for_status()
|
|
22
|
-
except HTTPError as err:
|
|
23
|
-
logger.info(f"Api raw response: {response.text}")
|
|
24
|
-
raise err
|
|
25
|
-
|
|
26
|
-
response = response.json()
|
|
27
|
-
return self.data_model(**response)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cmsdials-1.2.0/cmsdials/clients/run → cmsdials-1.4.0/cmsdials/clients/oms_proxy}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|