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.
- LICENSE +674 -0
- cmsdials/__init__.py +5 -0
- cmsdials/_version.py +1 -0
- cmsdials/auth/__init__.py +0 -0
- cmsdials/auth/_base.py +136 -0
- cmsdials/auth/bearer.py +140 -0
- cmsdials/auth/client.py +105 -0
- cmsdials/auth/exceptions.py +6 -0
- cmsdials/auth/models.py +25 -0
- cmsdials/auth/secret_key.py +42 -0
- cmsdials/clients/__init__.py +0 -0
- cmsdials/clients/dataset_index/__init__.py +0 -0
- cmsdials/clients/dataset_index/client.py +13 -0
- cmsdials/clients/dataset_index/models.py +30 -0
- cmsdials/clients/file_index/__init__.py +0 -0
- cmsdials/clients/file_index/client.py +13 -0
- cmsdials/clients/file_index/models.py +36 -0
- cmsdials/clients/h1d/__init__.py +0 -0
- cmsdials/clients/h1d/client.py +13 -0
- cmsdials/clients/h1d/models.py +47 -0
- cmsdials/clients/h2d/__init__.py +0 -0
- cmsdials/clients/h2d/client.py +13 -0
- cmsdials/clients/h2d/models.py +50 -0
- cmsdials/clients/lumisection/__init__.py +0 -0
- cmsdials/clients/lumisection/client.py +13 -0
- cmsdials/clients/lumisection/models.py +34 -0
- cmsdials/clients/mes/__init__.py +0 -0
- cmsdials/clients/mes/client.py +12 -0
- cmsdials/clients/mes/models.py +18 -0
- cmsdials/clients/ml_bad_lumisection/__init__.py +0 -0
- cmsdials/clients/ml_bad_lumisection/client.py +29 -0
- cmsdials/clients/ml_bad_lumisection/models.py +38 -0
- cmsdials/clients/ml_models_index/__init__.py +0 -0
- cmsdials/clients/ml_models_index/client.py +13 -0
- cmsdials/clients/ml_models_index/models.py +30 -0
- cmsdials/clients/oms_proxy/__init__.py +0 -0
- cmsdials/clients/oms_proxy/client.py +49 -0
- cmsdials/clients/oms_proxy/models.py +14 -0
- cmsdials/clients/run/__init__.py +0 -0
- cmsdials/clients/run/client.py +13 -0
- cmsdials/clients/run/models.py +29 -0
- cmsdials/cmsdials.py +27 -0
- cmsdials/filters.py +25 -0
- cmsdials/utils/__init__.py +0 -0
- cmsdials/utils/_json.py +24 -0
- cmsdials/utils/api_client.py +226 -0
- cmsdials/utils/base_model.py +33 -0
- cmsdials/utils/logger.py +6 -0
- cmsdials-0.0.0.dist-info/LICENSE +674 -0
- cmsdials-0.0.0.dist-info/METADATA +356 -0
- cmsdials-0.0.0.dist-info/RECORD +52 -0
- 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)
|
cmsdials/auth/bearer.py
ADDED
|
@@ -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}"
|
cmsdials/auth/client.py
ADDED
|
@@ -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.")
|
cmsdials/auth/models.py
ADDED
|
@@ -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)
|