pdmv-http-client 2.1.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.
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: pdmv-http-client
3
+ Version: 2.1.0
4
+ Summary: A HTTP client wrapper to handle authenticated requests to CERN internal applications
5
+ Maintainer-email: PdmV Service <pdmv.service@cern.ch>, PPD Technical Support <cms-PPD-technical-support@cern.ch>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://gitlab.cern.ch/cms-ppd/technical-support/libraries/pdmv-http-client
8
+ Classifier: Programming Language :: Python :: 3.9
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: requests>=2.32.5
16
+ Dynamic: license-file
17
+
18
+ # PdmV HTTP Client
19
+
20
+ This project provides an HTTP client based on [requests](https://github.com/psf/requests) to handle authenticated requests to CERN
21
+ internal applications. Furthermore, it includes some clients to ease the interaction with the APIs of
22
+ PdmV applications. This version is a refactor of the old [McM Scripts](https://github.com/cms-PdmV/mcm_scripts) project, and it is published at
23
+ PyPI to be public available.
24
+
25
+ ### How to use this package
26
+
27
+ #### Prerequisite
28
+
29
+ Create an isolated virtual environment using a Python version >= 3.9 like, for instance:
30
+
31
+ `python3.9 -m venv venv && source ./venv/bin/activate`
32
+
33
+ #### Development version
34
+
35
+ If you want to set up a development environment to contribute to this project:
36
+
37
+ Install `uv` and the required dependencies.
38
+
39
+ `pip install uv`
40
+
41
+ Set the current `venv` to use with `uv`:
42
+
43
+ `export UV_PROJECT_ENVIRONMENT="${VIRTUAL_ENV}"`
44
+
45
+ Install the packages via: `uv sync`
46
+
47
+ Run the test suite via:
48
+ `uv run pytest -s -vv`
49
+
50
+ > [!IMPORTANT]
51
+ > Make sure your execution environment has a valid Kerberos ticket to consume CERN services!
52
+
53
+ #### Build package
54
+
55
+ If you just want to use this package in your own project, install it via:
56
+
57
+ `pip install pdmv-http-client`
58
+
59
+ Make sure to remove the `sys.path.append(...)` statement, if you have them in your script, to avoid overloading old versions from CERN AFS.
60
+
61
+ ### Examples
62
+
63
+ At the `examples/` folder, you will find some scripts explaining how to use the clients and the HTTP client.
64
+
65
+ ### Priority change
66
+ * If you want to use priority-changing scripts or do anything else related to cmsweb, you'll have to use voms-proxy:
67
+ * `voms-proxy-init -voms cms`
68
+ * `export X509_USER_PROXY=$(voms-proxy-info --path)`
@@ -0,0 +1,26 @@
1
+ pdmv_http_client-2.1.0.dist-info/licenses/LICENSE,sha256=RYl3foijZoW2abvncpHax5Prn4sgcSFNuqMBVylpsqU,1083
2
+ rest/__init__.py,sha256=4acKnG3HT8j7UDNpvvjTRh5aidulJmlvHQ43L1AKjqQ,315
3
+ rest/_version.py,sha256=6G8yJbldQAMT0M9ZiFAqGo5OVqUMxGB1aeWeKmrwNIE,704
4
+ rest/applications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ rest/applications/base.py,sha256=uKwX1Xd2PfiuBDM90bz2EnXsuYiHbUTCBb7c1uwR0OU,8865
6
+ rest/applications/mcm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ rest/applications/mcm/core.py,sha256=3RbNac2Y3fEEJc3KhqUImk4jzTSMQE6JvEkoC2A_GLw,9465
8
+ rest/applications/mcm/invalidate_request.py,sha256=IfxTG5j0NToNQQNm93XqEjReuFDfnOrKtbws8_bDLuQ,15679
9
+ rest/applications/mcm/resubmission.py,sha256=-5ScoFBAS7qKlbBe5sHu_QQ7aRAe8ilNMqTgN7SfwDY,17531
10
+ rest/applications/rereco/core.py,sha256=g1jZpY_NMG2LpXlRdgu48kYrD2MGoNwDtakjhaAVMZI,2243
11
+ rest/applications/stats/core.py,sha256=Bm7kZNrTMCwyBS7cgo8bcUb16u-U9FjGKrleJolGXhM,2737
12
+ rest/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ rest/client/session.py,sha256=S94lXzlNE_73V65WgOv6OJwVMf_l-ErJLCsIg-oPRrg,3224
14
+ rest/client/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ rest/client/auth/auth_interface.py,sha256=z4aQaJyZNcHkyiWgLD27eyTeeDF0Z1cLAG6xIF6oEj4,3204
16
+ rest/client/auth/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ rest/client/auth/handlers/oauth2_tokens.py,sha256=kXpky06oQzMupuZl-ZOb3PG0STNLbeRulI-hwrzcT-E,10889
18
+ rest/client/auth/handlers/session_cookies.py,sha256=a4-UZSAkNrlAGdE4lhPw2wVZvcXNysHtyHq3usMZzys,3695
19
+ rest/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ rest/utils/logger.py,sha256=OOE7ktIERtV91_GuxUKmLz9c6RQR_tcZ1ugdkeG2wZM,1013
21
+ rest/utils/miscellaneous.py,sha256=r6Wmf6M3W1oZX41d0pj_4yqg8VoSncXTkAAEyk228_g,1247
22
+ rest/utils/shell.py,sha256=HlUjyLZbhkKoktR0um4Ig4cNI1F0sLEc5xvjc2xHILQ,1438
23
+ pdmv_http_client-2.1.0.dist-info/METADATA,sha256=LarL6koytstCDOI6SriHoRTuJjYY3akJ7hptP5oeDNU,2431
24
+ pdmv_http_client-2.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ pdmv_http_client-2.1.0.dist-info/top_level.txt,sha256=0QXYxm_ASalsWNLMGOoXHpjHuompr6ZCXwA-QrTZCZE,5
26
+ pdmv_http_client-2.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 The CMS PPD Technical Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ rest
rest/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """
2
+ Exposes the REST client for the McM application
3
+ like the old structure used to do.
4
+ Just to keep backward compatibility.
5
+ """
6
+
7
+ from rest.applications.base import BaseClient
8
+ from rest.applications.mcm.core import McM
9
+ from rest.applications.rereco.core import ReReco
10
+ from rest.applications.stats.core import Stats2
rest/_version.py ADDED
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '2.1.0'
32
+ __version_tuple__ = version_tuple = (2, 1, 0)
33
+
34
+ __commit_id__ = commit_id = None
File without changes
@@ -0,0 +1,238 @@
1
+ """
2
+ REST client default structure.
3
+ This sets the default schema for creating a HTTP client
4
+ object for PdmV applications.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Union
10
+
11
+ import requests
12
+
13
+ from rest.client.session import SessionFactory
14
+ from rest.utils.logger import LoggerFactory
15
+ from rest.utils.shell import describe_platform
16
+
17
+
18
+ class BaseClient:
19
+ """
20
+ Configures the internal HTTP client session.
21
+ """
22
+
23
+ # Authentication mechanisms
24
+ SSO = "sso"
25
+ OIDC = "oidc"
26
+ OAUTH = "oauth2"
27
+ COOKIE_ENV_VAR = "PDMV_COOKIE_PATH"
28
+
29
+ def __init__(
30
+ self,
31
+ app: str,
32
+ id: str = SSO,
33
+ debug: bool = False,
34
+ cookie: Union[str, None] = None,
35
+ dev: bool = True,
36
+ client_id: str = "",
37
+ client_secret: str = "",
38
+ ):
39
+ """
40
+ Initializes the HTTP client session configuring the
41
+ authentication approach and the logger.
42
+
43
+ Arguments:
44
+ app: PdmV application's endpoint. This is used to
45
+ construct the target URL.
46
+
47
+ id: The authentication mechanism to use. Supported values are:
48
+ 'sso': Request Session cookies via CERN 'auth-get-sso-cookie'
49
+ package. This approach does not work if the user's account
50
+ enforces 2FA. Make sure to provide a Kerberos ticket to consume
51
+ CERN resources in your runtime environment.
52
+ 'oidc': Request an ID token via Device Authorization Grant to consume
53
+ resources. This requires human input to complete the flow.
54
+ 'oauth': Request an Access token via Client Credentials Grant to consume
55
+ resources. This requires the user to provide the `client_id` and `client_secret`
56
+ to requests tokens. Please see the `API Access` tutorial in CERN
57
+ Authentication docs for more details.
58
+
59
+ Otherwise, the HTTP session will be configured with no credentials.
60
+
61
+ debug: Logger's level.
62
+ cookie: The target path to store/load the credential. This is a legacy
63
+ argument from the old McM REST client. To keep compatibility, the name
64
+ of the argument remains the same.
65
+ dev: Whether to use the dev or production instance.
66
+ client_id: This is an optional argument only used if `id = OAUTH`, this
67
+ is the client ID for requesting an access token.
68
+ client_secret: This is an optional argument only used if `id = OAUTH`, this
69
+ is the client secret for requesting an access token.
70
+ """
71
+ self._app = app
72
+ self._id = id
73
+ self._debug = debug
74
+ self._cookie = cookie
75
+ self._dev = dev
76
+ self._client_id = client_id
77
+ self._client_secret = client_secret
78
+
79
+ self.logger = LoggerFactory.getLogger(f"pdmv-http-client.{self._app}")
80
+ self.server = self._target_web_application()
81
+ self.credentials_path: Union[Path, None] = None
82
+ self.session = self._create_session()
83
+
84
+ def _target_web_application(self) -> str:
85
+ """
86
+ Sets the production or development web application URL.
87
+ """
88
+ if self._dev:
89
+ return f"https://cms-pdmv-dev.web.cern.ch/{self._app}/"
90
+
91
+ return f"https://cms-pdmv-prod.web.cern.ch/{self._app}/"
92
+
93
+ def _credential_name(self) -> str:
94
+ """
95
+ Provides the credential's file name.
96
+ """
97
+ suffix = "credential-dev" if self._dev else "credential-prod"
98
+ prefix = self._app
99
+ return f"{prefix}-{suffix}"
100
+
101
+ def _credentials_path(self) -> Path:
102
+ """
103
+ Sets the path from which credentials are going to be loaded and saved.
104
+ """
105
+ # If a path is provided in the `cookie` argument, use it.
106
+ if self._cookie:
107
+ self.logger.info(
108
+ "Settting the credential's path (%s) as provided in arguments",
109
+ self._cookie,
110
+ )
111
+ return Path(self._cookie)
112
+
113
+ # If the user attempts to force the `cookie` argument
114
+ # via runtime variables, use it.
115
+ from_env_var = os.getenv(self.COOKIE_ENV_VAR)
116
+ if from_env_var:
117
+ self.logger.info(
118
+ "Settting the credential's path (%s) from environment variables",
119
+ from_env_var,
120
+ )
121
+ return Path(from_env_var)
122
+
123
+ # Otherwise, use a `private` folder in the user's home
124
+ # to create the credential.
125
+ private_folder = Path.home() / Path("private")
126
+ private_folder.mkdir(mode=0o700, exist_ok=True)
127
+ path = private_folder / Path(self._credential_name())
128
+ self.logger.info("Setting default credential's path (%s)", path)
129
+
130
+ return path
131
+
132
+ def _create_session(self) -> requests.Session:
133
+ """
134
+ Configures the HTTP session depending on the chosen authentication method.
135
+ """
136
+ session: Union[requests.Session, None] = None
137
+ if self._id == self.SSO:
138
+ self.credentials_path = self._credentials_path()
139
+ session = SessionFactory.configure_by_session_cookie(
140
+ url=self.server, credential_path=self.credentials_path
141
+ )
142
+ elif self._id == self.OIDC:
143
+ self.credentials_path = self._credentials_path()
144
+ target_application = "cms-ppd-pdmv-device-flow"
145
+ session = SessionFactory.configure_by_id_token(
146
+ url=self.server,
147
+ credential_path=self.credentials_path,
148
+ target_application=target_application,
149
+ )
150
+ elif self._id == self.OAUTH:
151
+ self.credentials_path = self._credentials_path()
152
+ target_application = "cms-ppd-pdmv-dev" if self._dev else "cms-ppd-pdmv"
153
+ session = SessionFactory.configure_by_access_token(
154
+ url=self.server,
155
+ credential_path=self.credentials_path,
156
+ target_application=target_application,
157
+ client_id=self._client_id,
158
+ client_secret=self._client_secret,
159
+ )
160
+ else:
161
+ self.logger.warning("Using HTTP client without providing authentication")
162
+ session = requests.Session()
163
+
164
+ # Include some headers for the session
165
+ user_agent = {
166
+ "User-Agent": f"PdmV HTTP client (For: {self._app}): {describe_platform()}"
167
+ }
168
+ session.headers.update(user_agent)
169
+ return session
170
+
171
+ def __repr__(self):
172
+ return f"<HTTP client (For: {self._app}) id: {self._id} server: {self.server} credential: {self.credentials_path}>"
173
+
174
+ def _get(self, url: str):
175
+ """
176
+ Performs a GET request to the target resource.
177
+ This assumes the target resource's response format is JSON.
178
+
179
+ Args:
180
+ url: Resource URL. Do not include a slash at the beginning.
181
+ For example: restapi/requests/get/B2G-Run3Summer22wmLHEGS-00033.
182
+
183
+ Returns:
184
+ Target resource response.
185
+ """
186
+ full_url = f"{self.server}{url}"
187
+ response = self.session.get(url=full_url)
188
+ return response.json()
189
+
190
+ def _put(self, url: str, data: dict):
191
+ """
192
+ Performs a PUT request to the target resource.
193
+ This assumes the target resource's response format is JSON.
194
+
195
+ Args:
196
+ url: Resource URL. Do not include a slash at the beginning.
197
+ For example: restapi/requests/get/B2G-Run3Summer22wmLHEGS-00033.
198
+ data: Request's body.
199
+
200
+ Returns:
201
+ Target resource response.
202
+ """
203
+ full_url = f"{self.server}{url}"
204
+ response = self.session.put(url=full_url, json=data)
205
+ return response.json()
206
+
207
+ def _post(self, url: str, data: dict):
208
+ """
209
+ Performs a POST request to the target resource.
210
+ This assumes the target resource's response format is JSON.
211
+
212
+ Args:
213
+ url: Resource URL. Do not include a slash at the beginning.
214
+ For example: restapi/requests/get/B2G-Run3Summer22wmLHEGS-00033.
215
+ data: Request's body.
216
+
217
+ Returns:
218
+ Target resource response.
219
+ """
220
+ full_url = f"{self.server}{url}"
221
+ response = self.session.post(url=full_url, json=data)
222
+ return response.json()
223
+
224
+ def _delete(self, url: str):
225
+ """
226
+ Performs a DELETE request to the target resource.
227
+ This assumes the target resource's response format is JSON.
228
+
229
+ Args:
230
+ url: Resource URL. Do not include a slash at the beginning.
231
+ For example: restapi/requests/get/B2G-Run3Summer22wmLHEGS-00033.
232
+
233
+ Returns:
234
+ Target resource response.
235
+ """
236
+ full_url = f"{self.server}{url}"
237
+ response = self.session.delete(url=full_url)
238
+ return response.json()
File without changes