cmsdials 0.0.0__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 cmsdials might be problematic. Click here for more details.

Files changed (52) hide show
  1. LICENSE +674 -0
  2. cmsdials/__init__.py +5 -0
  3. cmsdials/_version.py +1 -0
  4. cmsdials/auth/__init__.py +0 -0
  5. cmsdials/auth/_base.py +136 -0
  6. cmsdials/auth/bearer.py +140 -0
  7. cmsdials/auth/client.py +105 -0
  8. cmsdials/auth/exceptions.py +6 -0
  9. cmsdials/auth/models.py +25 -0
  10. cmsdials/auth/secret_key.py +42 -0
  11. cmsdials/clients/__init__.py +0 -0
  12. cmsdials/clients/dataset_index/__init__.py +0 -0
  13. cmsdials/clients/dataset_index/client.py +13 -0
  14. cmsdials/clients/dataset_index/models.py +30 -0
  15. cmsdials/clients/file_index/__init__.py +0 -0
  16. cmsdials/clients/file_index/client.py +13 -0
  17. cmsdials/clients/file_index/models.py +36 -0
  18. cmsdials/clients/h1d/__init__.py +0 -0
  19. cmsdials/clients/h1d/client.py +13 -0
  20. cmsdials/clients/h1d/models.py +47 -0
  21. cmsdials/clients/h2d/__init__.py +0 -0
  22. cmsdials/clients/h2d/client.py +13 -0
  23. cmsdials/clients/h2d/models.py +50 -0
  24. cmsdials/clients/lumisection/__init__.py +0 -0
  25. cmsdials/clients/lumisection/client.py +13 -0
  26. cmsdials/clients/lumisection/models.py +34 -0
  27. cmsdials/clients/mes/__init__.py +0 -0
  28. cmsdials/clients/mes/client.py +12 -0
  29. cmsdials/clients/mes/models.py +18 -0
  30. cmsdials/clients/ml_bad_lumisection/__init__.py +0 -0
  31. cmsdials/clients/ml_bad_lumisection/client.py +29 -0
  32. cmsdials/clients/ml_bad_lumisection/models.py +38 -0
  33. cmsdials/clients/ml_models_index/__init__.py +0 -0
  34. cmsdials/clients/ml_models_index/client.py +13 -0
  35. cmsdials/clients/ml_models_index/models.py +30 -0
  36. cmsdials/clients/oms_proxy/__init__.py +0 -0
  37. cmsdials/clients/oms_proxy/client.py +49 -0
  38. cmsdials/clients/oms_proxy/models.py +14 -0
  39. cmsdials/clients/run/__init__.py +0 -0
  40. cmsdials/clients/run/client.py +13 -0
  41. cmsdials/clients/run/models.py +29 -0
  42. cmsdials/cmsdials.py +27 -0
  43. cmsdials/filters.py +25 -0
  44. cmsdials/utils/__init__.py +0 -0
  45. cmsdials/utils/_json.py +24 -0
  46. cmsdials/utils/api_client.py +226 -0
  47. cmsdials/utils/base_model.py +33 -0
  48. cmsdials/utils/logger.py +6 -0
  49. cmsdials-0.0.0.dist-info/LICENSE +674 -0
  50. cmsdials-0.0.0.dist-info/METADATA +356 -0
  51. cmsdials-0.0.0.dist-info/RECORD +52 -0
  52. cmsdials-0.0.0.dist-info/WHEEL +4 -0
File without changes
cmsdials/auth/_base.py ADDED
@@ -0,0 +1,136 @@
1
+ import threading
2
+ from abc import ABC, abstractmethod
3
+ from collections.abc import Mapping
4
+ from datetime import datetime, timedelta
5
+ from enum import Enum
6
+
7
+ from .exceptions import ImpossibleToRefreshTokenError
8
+
9
+
10
+ class TokenState(Enum):
11
+ """
12
+ Tracks the state of a token.
13
+
14
+ FRESH: The token is valid and can be used.
15
+ STALE: The token is close to expired, it can be used but you also should trigger a non blocking refresh.
16
+ EXPIRED: The token is expired but the refresh token is valid. You must trigger a block refresh.
17
+ INVALID: Both the token and refresh token are expired.
18
+ """
19
+
20
+ FRESH = 1
21
+ STALE = 2
22
+ EXPIRED = 3
23
+ INVALID = 4
24
+
25
+
26
+ class BaseCredentials(ABC):
27
+ """
28
+ Base class for all credentials.
29
+ """
30
+
31
+ def __init__(self):
32
+ self.token = None
33
+ self.expires_at = None
34
+ self.expires_in = None
35
+ self.refresh_expires_at = None
36
+ self.refresh_expires_in = None
37
+ self.refresh_token = None
38
+ self.token_type = None
39
+ self._stale_lock = threading.Lock()
40
+ self._expired_lock = threading.Lock()
41
+
42
+ @property
43
+ def stale(self) -> bool:
44
+ """
45
+ States if token is staled.
46
+ """
47
+ if not self.expires_at:
48
+ return False
49
+ return self.expires_at > datetime.now() > (self.expires_at - timedelta(seconds=0.1 * self.expires_in))
50
+
51
+ @property
52
+ def expired(self) -> bool:
53
+ """
54
+ States if token is expired.
55
+ """
56
+ if not self.expires_at:
57
+ return False
58
+ return datetime.now() >= self.expires_at
59
+
60
+ @property
61
+ def refresh_expired(self) -> bool:
62
+ """
63
+ States if refresh token is expired.
64
+ """
65
+ if not self.refresh_expires_at:
66
+ return False
67
+ return datetime.now() >= self.refresh_expires_at
68
+
69
+ @property
70
+ def token_state(self):
71
+ """
72
+ See `:obj:`TokenState`
73
+ """
74
+ # Credentials that can't expire are always treated as fresh.
75
+ if self.expires_at is None:
76
+ return TokenState.FRESH
77
+
78
+ if self.stale:
79
+ return TokenState.STALE
80
+
81
+ if self.expired and not self.refresh_expired:
82
+ return TokenState.EXPIRED
83
+
84
+ if self.expired and self.refresh_expired:
85
+ return TokenState.INVALID
86
+
87
+ return TokenState.FRESH
88
+
89
+ @abstractmethod
90
+ def apply(self, headers: Mapping[str, str]) -> None:
91
+ """
92
+ Apply the token to the authentication header.
93
+ """
94
+ raise NotImplementedError("apply must be implemented")
95
+
96
+ @abstractmethod
97
+ def refresh(self) -> None:
98
+ """
99
+ Performs credential-specific refresh logic.
100
+ """
101
+ raise NotImplementedError("refresh must be implemented")
102
+
103
+ def _blocking_refresh(self) -> None:
104
+ if self.token_state == TokenState.EXPIRED:
105
+ self.refresh()
106
+
107
+ def _nonblocking_refresh(self) -> None:
108
+ self._stale_lock.acquire()
109
+ self.refresh()
110
+ self._stale_lock.release()
111
+
112
+ def before_request(self, headers: Mapping[str, str]):
113
+ """
114
+ Performs credential-specific before request logic.
115
+ """
116
+ # If token is invalid (both token and refresh token expired): Throw error
117
+ # User need to interactively authenticate again
118
+ if self.token_state == TokenState.INVALID:
119
+ raise ImpossibleToRefreshTokenError
120
+
121
+ # If token is stale (token is close to expire)
122
+ # Re-use the token but launch a new thread to refresh the token
123
+ if self.token_state == TokenState.STALE and self._stale_lock.locked() is False:
124
+ worker = threading.Thread(target=self._nonblocking_refresh)
125
+ worker.start()
126
+
127
+ # If token is expired (token is expired but refresh token is not expired)
128
+ # Do not re-use the token, block the main thread and refresh before continuing
129
+ # After refresh the token is fresh
130
+ if self.token_state == TokenState.EXPIRED:
131
+ self._expired_lock.acquire()
132
+ self._blocking_refresh()
133
+ self._expired_lock.release()
134
+
135
+ # Put token in the header if token is fresh
136
+ self.apply(headers)
@@ -0,0 +1,140 @@
1
+ import json
2
+ import os
3
+ import os.path
4
+ from collections.abc import Mapping
5
+ from datetime import datetime
6
+ from typing import Optional
7
+
8
+ from ..utils._json import TokenDecoder, TokenEncoder
9
+ from ..utils.logger import logger
10
+ from ._base import BaseCredentials, TokenState
11
+ from .client import AuthClient
12
+ from .models import Token
13
+
14
+
15
+ DEFAULT_CACHE_DIR = ".cache"
16
+ DEFAULT_CACHE_FILENAME = "creds"
17
+
18
+
19
+ class Credentials(BaseCredentials):
20
+ """
21
+ Token credentials.
22
+ These credential use a token to provide authorization to applications.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ token: str,
28
+ expires_at: datetime,
29
+ expires_in: int,
30
+ refresh_expires_at: datetime,
31
+ refresh_expires_in: int,
32
+ refresh_token: str,
33
+ token_type: str,
34
+ cache_dir: str = DEFAULT_CACHE_DIR,
35
+ client: Optional[AuthClient] = None,
36
+ ):
37
+ # Load parent class and overwrite token attributes
38
+ super().__init__()
39
+ self.token = token
40
+ self.expires_at = expires_at
41
+ self.expires_in = expires_in
42
+ self.refresh_expires_at = refresh_expires_at
43
+ self.refresh_expires_in = refresh_expires_in
44
+ self.refresh_token = refresh_token
45
+ self.token_type = token_type
46
+
47
+ # Setup dir for caching credentials and authentication client
48
+ self.cache_dir = cache_dir
49
+ self.client = client or AuthClient()
50
+
51
+ # If loaded token is expired, immediately refresh
52
+ if self.token_state == TokenState.EXPIRED:
53
+ self.refresh()
54
+
55
+ # If loaded token is invalid, trigger authentication
56
+ if self.token_state == TokenState.INVALID:
57
+ logger.info("Access token and refresh token are expired, triggering device authentication flow...")
58
+ token: Token = self.client.device_auth_flow()
59
+ self._set_token(token)
60
+
61
+ # Only cache if token is fresh
62
+ if self.token_state == TokenState.FRESH:
63
+ self.cache_credentials()
64
+
65
+ @property
66
+ def _token(self) -> Token:
67
+ return Token(
68
+ access_token=self.token,
69
+ expires_in=self.expires_in,
70
+ refresh_expires_in=self.refresh_expires_in,
71
+ refresh_token=self.refresh_token,
72
+ token_type=self.token_type,
73
+ expires_at=self.expires_at,
74
+ refresh_expires_at=self.refresh_expires_at,
75
+ )
76
+
77
+ @staticmethod
78
+ def _handle_creds_filepath(cache_dir: str = DEFAULT_CACHE_DIR):
79
+ cache_dir = cache_dir or DEFAULT_CACHE_DIR
80
+ if os.path.isabs(cache_dir) is False:
81
+ cache_dir = os.path.join(os.getcwd(), cache_dir)
82
+
83
+ os.makedirs(cache_dir, exist_ok=True)
84
+ return os.path.join(cache_dir, DEFAULT_CACHE_FILENAME)
85
+
86
+ @staticmethod
87
+ def from_authclient_token(
88
+ token: Token, cache_dir: str = DEFAULT_CACHE_DIR, client: Optional[AuthClient] = None
89
+ ) -> "Credentials":
90
+ client = client or AuthClient()
91
+ return Credentials(
92
+ token=token.access_token,
93
+ expires_in=token.expires_in,
94
+ refresh_expires_in=token.refresh_expires_in,
95
+ refresh_token=token.refresh_token,
96
+ token_type=token.token_type,
97
+ expires_at=token.expires_at,
98
+ refresh_expires_at=token.refresh_expires_at,
99
+ cache_dir=cache_dir,
100
+ client=client,
101
+ )
102
+
103
+ @staticmethod
104
+ def from_creds_file(cache_dir: str = DEFAULT_CACHE_DIR, client: Optional[AuthClient] = None) -> "Credentials":
105
+ client = client or AuthClient()
106
+ fpath = Credentials._handle_creds_filepath(cache_dir)
107
+ try:
108
+ with open(fpath) as filebuffer:
109
+ fcontents = json.load(filebuffer, cls=TokenDecoder)
110
+ token = Token(**fcontents)
111
+ except FileNotFoundError:
112
+ logger.info("Credentials file not found, triggering device authentication flow...")
113
+ token: Token = client.device_auth_flow()
114
+
115
+ return Credentials.from_authclient_token(token, cache_dir, client)
116
+
117
+ def _set_token(self, token: Token):
118
+ self.token = token.access_token
119
+ self.expires_at = token.expires_at
120
+ self.expires_in = token.expires_in
121
+ self.refresh_expires_at = token.refresh_expires_at
122
+ self.refresh_expires_in = token.refresh_expires_in
123
+ self.refresh_token = token.refresh_token
124
+ self.token_type = token.token_type
125
+
126
+ def cache_credentials(self) -> None:
127
+ fpath = self._handle_creds_filepath(self.cache_dir)
128
+ with open(fpath, "w") as filebuffer:
129
+ json.dump(self._token.model_dump(), filebuffer, cls=TokenEncoder)
130
+
131
+ def refresh(self) -> None:
132
+ token: Token = self.client.refresh_token(self.token_type, self.token, self.refresh_token)
133
+ self._set_token(token)
134
+ self.cache_credentials()
135
+
136
+ def apply(self, headers: Mapping[str, str]) -> None:
137
+ """
138
+ Apply the Client secret key token to the X-CLIENT-SECRET header.
139
+ """
140
+ headers["Authorization"] = f"{self.token_type} {self.token}"
@@ -0,0 +1,105 @@
1
+ import time
2
+ from datetime import datetime, timedelta
3
+
4
+ import requests
5
+
6
+ from ..utils.api_client import BaseAPIClient
7
+ from ..utils.logger import logger
8
+ from .exceptions import PendingAuthorizationError
9
+ from .models import Device, DeviceToken, RefreshToken, Token
10
+
11
+
12
+ class AuthClient(BaseAPIClient):
13
+ """
14
+ Client for DIALS auth endpoint
15
+ """
16
+
17
+ default_timeout = 30 # seconds
18
+
19
+ def __init__(self, *args, **kwargs) -> None:
20
+ super().__init__(*args, **kwargs)
21
+
22
+ def new_device(self) -> Device:
23
+ endpoint_url = self.api_url + "auth/new-device/"
24
+ response = requests.get(
25
+ url=endpoint_url,
26
+ headers={
27
+ "accept": "application/json",
28
+ "User-Agent": self._build_user_agent()
29
+ },
30
+ timeout=self.default_timeout,
31
+ )
32
+ response.raise_for_status()
33
+ response = response.json()
34
+ return Device(**response)
35
+
36
+ def device_token(self, device_code: str) -> Token:
37
+ endpoint_url = self.api_url + "auth/device-token/"
38
+ response = requests.post(
39
+ url=endpoint_url,
40
+ headers={
41
+ "accept": "application/json",
42
+ "Content-Type": "application/json",
43
+ "User-Agent": self._build_user_agent()
44
+ },
45
+ json={"device_code": device_code},
46
+ timeout=self.default_timeout,
47
+ )
48
+
49
+ if response.status_code == 400 and "authorization_pending" in response.text:
50
+ raise PendingAuthorizationError
51
+
52
+ response.raise_for_status()
53
+ response = response.json()
54
+ curr_datetime = datetime.now()
55
+ token = DeviceToken(**response)
56
+ return Token(
57
+ **token.dict(),
58
+ expires_at=curr_datetime + timedelta(seconds=token.expires_in),
59
+ refresh_expires_at=curr_datetime + timedelta(seconds=token.refresh_expires_in),
60
+ )
61
+
62
+ def refresh_token(self, token_type: str, access_token: str, refresh_token: str) -> Token:
63
+ endpoint_url = self.api_url + "auth/refresh-token/"
64
+ response = requests.post(
65
+ url=endpoint_url,
66
+ headers={
67
+ "accept": "application/json",
68
+ "Content-Type": "application/json",
69
+ "Authorization": f"{token_type} {access_token}",
70
+ "User-Agent": self._build_user_agent()
71
+ },
72
+ json={"refresh_token": refresh_token},
73
+ timeout=self.default_timeout,
74
+ )
75
+ response.raise_for_status()
76
+ response = response.json()
77
+ curr_datetime = datetime.now()
78
+ token = RefreshToken(**response)
79
+ return Token(
80
+ **token.dict(),
81
+ expires_at=curr_datetime + timedelta(seconds=token.expires_in),
82
+ refresh_expires_at=curr_datetime + timedelta(seconds=token.refresh_expires_in),
83
+ )
84
+
85
+ def device_auth_flow(self):
86
+ interval = 5 # seconds
87
+ device = self.new_device()
88
+ end_date = datetime.now() + timedelta(seconds=device.expires_in)
89
+
90
+ logger.info(f"This device will expire in {device.expires_in} seconds.")
91
+ logger.info(f"Go to the following url and authenticate: {device.verification_uri_complete}")
92
+ logger.info(f"Checking authorization status every {interval} seconds...")
93
+
94
+ while True:
95
+ time.sleep(interval)
96
+ curr_date = datetime.now()
97
+ if curr_date > end_date:
98
+ raise Exception("Action took too much time, device expired!")
99
+
100
+ try:
101
+ token = self.device_token(device_code=device.device_code)
102
+ logger.info("Device authorized, authentication finished successfully!")
103
+ return token
104
+ except PendingAuthorizationError:
105
+ logger.info("Device not authorized yet.")
@@ -0,0 +1,6 @@
1
+ class PendingAuthorizationError(Exception):
2
+ pass
3
+
4
+
5
+ class ImpossibleToRefreshTokenError(Exception):
6
+ pass
@@ -0,0 +1,25 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class Device(BaseModel):
7
+ device_code: str
8
+ verification_uri_complete: str
9
+ expires_in: int
10
+
11
+
12
+ class DeviceToken(BaseModel):
13
+ access_token: str
14
+ expires_in: int
15
+ refresh_expires_in: int
16
+ refresh_token: str
17
+ token_type: str
18
+
19
+
20
+ RefreshToken = DeviceToken
21
+
22
+
23
+ class Token(DeviceToken):
24
+ expires_at: datetime
25
+ refresh_expires_at: datetime
@@ -0,0 +1,42 @@
1
+ from collections.abc import Mapping
2
+
3
+ from ._base import BaseCredentials
4
+
5
+
6
+ class Credentials(BaseCredentials):
7
+ """
8
+ Client secret key credentials.
9
+ These credential use Client secret key to provide authorization to applications.
10
+ """
11
+
12
+ def __init__(self, token: str) -> None:
13
+ if not token:
14
+ raise ValueError("token must be a non-empty Client secret key string")
15
+ self.token = token
16
+
17
+ @property
18
+ def stale(self) -> bool:
19
+ return False
20
+
21
+ @property
22
+ def expired(self) -> bool:
23
+ return False
24
+
25
+ @property
26
+ def refresh_expired(self) -> bool:
27
+ return False
28
+
29
+ def refresh(self) -> None:
30
+ return
31
+
32
+ def apply(self, headers: Mapping[str, str]) -> None:
33
+ """
34
+ Apply the Client secret key token to the X-CLIENT-SECRET header.
35
+ """
36
+ headers["X-CLIENT-SECRET"] = self.token
37
+
38
+ def before_request(self, headers: Mapping[str, str]):
39
+ """
40
+ Performs credential-specific before request logic.
41
+ """
42
+ self.apply(headers)
File without changes
File without changes
@@ -0,0 +1,13 @@
1
+ from ...utils.api_client import BaseAuthorizedAPIClient
2
+ from .models import DatasetIndex, DatasetIndexFilters, PaginatedDatasetIndexList
3
+
4
+
5
+ class DatasetIndexClient(BaseAuthorizedAPIClient):
6
+ data_model = DatasetIndex
7
+ pagination_model = PaginatedDatasetIndexList
8
+ filter_class = DatasetIndexFilters
9
+ lookup_url = "dataset-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)
@@ -0,0 +1,30 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from pydantic import AnyUrl, BaseModel, Field
5
+
6
+ from ...utils.base_model import OBaseModel, PaginatedBaseModel
7
+
8
+
9
+ class DatasetIndex(BaseModel):
10
+ dataset_id: int
11
+ dataset: str = Field(..., max_length=255)
12
+ era: str = Field(..., max_length=255)
13
+ data_tier: str = Field(..., max_length=255)
14
+ primary_ds_name: str = Field(..., max_length=255)
15
+ processed_ds_name: str = Field(..., max_length=255)
16
+ processing_version: int
17
+ last_modification_date: datetime
18
+
19
+
20
+ class PaginatedDatasetIndexList(PaginatedBaseModel):
21
+ next: Optional[AnyUrl]
22
+ previous: Optional[AnyUrl]
23
+ results: list[DatasetIndex]
24
+
25
+
26
+ class DatasetIndexFilters(OBaseModel):
27
+ next_token: Optional[str] = None
28
+ page_size: Optional[int] = None
29
+ dataset: Optional[str] = None
30
+ dataset__regex: Optional[str] = None
File without changes
@@ -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)
@@ -0,0 +1,36 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from pydantic import AnyUrl, BaseModel, Field
5
+
6
+ from ...utils.base_model import OBaseModel, PaginatedBaseModel
7
+
8
+
9
+ class FileIndex(BaseModel):
10
+ dataset_id: int
11
+ dataset: str = Field(..., max_length=255)
12
+ file_id: int
13
+ file_size: int
14
+ creation_date: datetime
15
+ last_modification_date: datetime
16
+ logical_file_name: str
17
+ status: str = Field(..., max_length=255)
18
+ err_trace: Optional[str] = Field(..., max_length=5000)
19
+
20
+
21
+ class PaginatedFileIndexList(PaginatedBaseModel):
22
+ next: Optional[AnyUrl]
23
+ previous: Optional[AnyUrl]
24
+ results: list[FileIndex]
25
+
26
+
27
+ class FileIndexFilters(OBaseModel):
28
+ next_token: Optional[str] = None
29
+ page_size: Optional[int] = None
30
+ dataset_id: Optional[int] = None
31
+ logical_file_name: Optional[str] = None
32
+ logical_file_name__regex: Optional[str] = None
33
+ status: Optional[str] = None
34
+ min_size: Optional[int] = None
35
+ dataset: Optional[str] = None
36
+ dataset__regex: Optional[str] = None
File without changes
@@ -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,47 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import AnyUrl, BaseModel, Field
4
+
5
+ from ...utils.base_model import OBaseModel, PaginatedBaseModel
6
+
7
+
8
+ class LumisectionHistogram1D(BaseModel):
9
+ dataset: str = Field(..., max_length=255)
10
+ me: str = Field(..., max_length=255)
11
+ dataset_id: int
12
+ file_id: int
13
+ run_number: int
14
+ ls_number: int
15
+ me_id: int
16
+ x_min: float
17
+ x_max: float
18
+ x_bin: float
19
+ entries: int
20
+ data: list[float]
21
+
22
+
23
+ class PaginatedLumisectionHistogram1DList(PaginatedBaseModel):
24
+ next: Optional[AnyUrl]
25
+ previous: Optional[AnyUrl]
26
+ results: list[LumisectionHistogram1D]
27
+
28
+
29
+ class LumisectionHistogram1DFilters(OBaseModel):
30
+ next_token: Optional[str] = None
31
+ page_size: Optional[int] = None
32
+ dataset_id: Optional[int] = None
33
+ file_id: Optional[int] = None
34
+ run_number: Optional[int] = None
35
+ run_number__lte: Optional[int] = None
36
+ run_number__gte: Optional[int] = None
37
+ ls_number: Optional[int] = None
38
+ ls_numbet__lte: Optional[int] = None
39
+ ls_number__gte: Optional[int] = None
40
+ me_id: Optional[int] = None
41
+ entries__gte: Optional[int] = None
42
+ dataset: Optional[str] = None
43
+ dataset__regex: Optional[str] = None
44
+ logical_file_name: Optional[str] = None
45
+ logical_file_name__regex: Optional[str] = None
46
+ me: Optional[str] = None
47
+ me__regex: Optional[str] = None
File without changes
@@ -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)