cmsdials 2.3.0__tar.gz → 2.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.
Files changed (92) hide show
  1. {cmsdials-2.3.0 → cmsdials-2.4.0}/PKG-INFO +15 -12
  2. {cmsdials-2.3.0 → cmsdials-2.4.0}/README.md +14 -11
  3. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/_version.py +3 -3
  4. cmsdials-2.4.0/cmsdials/auth/bearer.py +62 -0
  5. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/auth/client.py +18 -0
  6. cmsdials-2.4.0/cmsdials/auth/client_credentials.py +48 -0
  7. cmsdials-2.3.0/cmsdials/auth/bearer.py → cmsdials-2.4.0/cmsdials/auth/device.py +6 -3
  8. cmsdials-2.4.0/cmsdials/auth/secret_key.py +68 -0
  9. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials.egg-info/PKG-INFO +15 -12
  10. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials.egg-info/SOURCES.txt +2 -0
  11. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials.egg-info/scm_file_list.json +2 -0
  12. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials.egg-info/scm_version.json +2 -2
  13. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/integration/env.py +3 -2
  14. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/integration/test_auth_client.py +19 -10
  15. cmsdials-2.4.0/tests/integration/utils.py +32 -0
  16. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/unit/test_auth.py +101 -11
  17. cmsdials-2.3.0/cmsdials/auth/secret_key.py +0 -65
  18. cmsdials-2.3.0/tests/integration/utils.py +0 -23
  19. {cmsdials-2.3.0 → cmsdials-2.4.0}/.coveragerc +0 -0
  20. {cmsdials-2.3.0 → cmsdials-2.4.0}/.editorconfig +0 -0
  21. {cmsdials-2.3.0 → cmsdials-2.4.0}/.gitignore +0 -0
  22. {cmsdials-2.3.0 → cmsdials-2.4.0}/.gitlab-ci.yml +0 -0
  23. {cmsdials-2.3.0 → cmsdials-2.4.0}/.mailmap +0 -0
  24. {cmsdials-2.3.0 → cmsdials-2.4.0}/.pre-commit-config.yaml +0 -0
  25. {cmsdials-2.3.0 → cmsdials-2.4.0}/.tool-versions +0 -0
  26. {cmsdials-2.3.0 → cmsdials-2.4.0}/LICENSE +0 -0
  27. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/__init__.py +0 -0
  28. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/auth/__init__.py +0 -0
  29. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/auth/_base.py +0 -0
  30. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/auth/exceptions.py +0 -0
  31. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/auth/models.py +0 -0
  32. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/__init__.py +0 -0
  33. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/dataset_index/__init__.py +0 -0
  34. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/dataset_index/client.py +0 -0
  35. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/dataset_index/models.py +0 -0
  36. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/file_index/__init__.py +0 -0
  37. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/file_index/client.py +0 -0
  38. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/file_index/models.py +0 -0
  39. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/h1d/__init__.py +0 -0
  40. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/h1d/client.py +0 -0
  41. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/h1d/models.py +0 -0
  42. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/h2d/__init__.py +0 -0
  43. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/h2d/client.py +0 -0
  44. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/h2d/models.py +0 -0
  45. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/lumisection/__init__.py +0 -0
  46. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/lumisection/client.py +0 -0
  47. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/lumisection/models.py +0 -0
  48. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/mes/__init__.py +0 -0
  49. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/mes/client.py +0 -0
  50. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/mes/models.py +0 -0
  51. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/ml_bad_lumisection/__init__.py +0 -0
  52. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/ml_bad_lumisection/client.py +0 -0
  53. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/ml_bad_lumisection/models.py +0 -0
  54. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/ml_models_index/__init__.py +0 -0
  55. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/ml_models_index/client.py +0 -0
  56. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/ml_models_index/models.py +0 -0
  57. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/oms_proxy/__init__.py +0 -0
  58. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/oms_proxy/client.py +0 -0
  59. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/oms_proxy/models.py +0 -0
  60. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/run/__init__.py +0 -0
  61. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/run/client.py +0 -0
  62. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/run/models.py +0 -0
  63. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/workspace/__init__.py +0 -0
  64. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/clients/workspace/client.py +0 -0
  65. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/cmsdials.py +0 -0
  66. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/filters.py +0 -0
  67. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/utils/__init__.py +0 -0
  68. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/utils/_json.py +0 -0
  69. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/utils/api_client.py +0 -0
  70. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/utils/base_model.py +0 -0
  71. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials/utils/logger.py +0 -0
  72. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials.egg-info/dependency_links.txt +0 -0
  73. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials.egg-info/requires.txt +0 -0
  74. {cmsdials-2.3.0 → cmsdials-2.4.0}/cmsdials.egg-info/top_level.txt +0 -0
  75. {cmsdials-2.3.0 → cmsdials-2.4.0}/mypy.ini +0 -0
  76. {cmsdials-2.3.0 → cmsdials-2.4.0}/pyproject.toml +0 -0
  77. {cmsdials-2.3.0 → cmsdials-2.4.0}/pytest.ini +0 -0
  78. {cmsdials-2.3.0 → cmsdials-2.4.0}/ruff.toml +0 -0
  79. {cmsdials-2.3.0 → cmsdials-2.4.0}/setup.cfg +0 -0
  80. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/integration/__init__.py +0 -0
  81. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/integration/test_file_index_client.py +0 -0
  82. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/integration/test_h1d_client.py +0 -0
  83. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/integration/test_h2d_client.py +0 -0
  84. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/integration/test_lumisection_client.py +0 -0
  85. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/integration/test_mes_client.py +0 -0
  86. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/integration/test_oms_proxy_client.py +0 -0
  87. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/integration/test_resume.py +0 -0
  88. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/integration/test_run_client.py +0 -0
  89. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/unit/__init__.py +0 -0
  90. {cmsdials-2.3.0 → cmsdials-2.4.0}/tests/unit/test_base_api_client.py +0 -0
  91. {cmsdials-2.3.0 → cmsdials-2.4.0}/tox.ini +0 -0
  92. {cmsdials-2.3.0 → cmsdials-2.4.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmsdials
3
- Version: 2.3.0
3
+ Version: 2.4.0
4
4
  Summary: The Python api client interface to DIALS service
5
5
  Author-email: Gabriel Moreira <gabriel.moreira@cern.ch>
6
6
  Project-URL: Repository, https://gitlab.cern.ch/cms-dqmdc/libraries/dials-py
@@ -53,21 +53,21 @@ Credentials are issued by CERN's Keycloak directly, dials-py never sends your pa
53
53
  ### Generating credentials with client secret
54
54
 
55
55
  ```python
56
- from cmsdials.auth.secret_key import Credentials
56
+ from cmsdials.auth.client_credentials import Credentials
57
57
 
58
- creds = Credentials(client_id="my-application", token=".....")
58
+ creds = Credentials(client_id="my-application", client_secret=".....")
59
59
  ```
60
60
 
61
61
  The token is renewed automatically before it expires.
62
62
 
63
- To get API access:
63
+ To get API access (see [API access for applications](https://cms-ppd.docs.cern.ch/ts/software/DIALS/operations/api_access.html) for the details):
64
64
 
65
65
  1. Register an application in the [CERN Application Portal](https://application-portal.web.cern.ch) and enable "My application will need to get tokens using its own client ID and secret".
66
66
  2. Ask the DIALS team to grant API access to the application (it is added to the DIALS API access group).
67
67
  3. Use the application's client id and secret as shown above.
68
68
 
69
69
  > [!WARNING]
70
- > `Credentials(token=".....")` without `client_id` authenticates with the `X-CLIENT-SECRET` header, which is deprecated and emits a `DeprecationWarning`. DIALS will stop accepting it, so pass the `client_id` of your application.
70
+ > `cmsdials.auth.secret_key.Credentials` is deprecated and emits a `DeprecationWarning`: it will be removed in dials-py 3.0.0. Replace `Credentials(client_id=..., token=...)` with `client_credentials.Credentials(client_id=..., client_secret=...)`. Without `client_id`, it authenticates with the `X-CLIENT-SECRET` header, which DIALS will stop accepting.
71
71
 
72
72
  ### Generating credentials with device
73
73
 
@@ -75,7 +75,7 @@ To get API access:
75
75
 
76
76
  ```python
77
77
  from cmsdials.auth.client import AuthClient
78
- from cmsdials.auth.bearer import Credentials
78
+ from cmsdials.auth.device import Credentials
79
79
 
80
80
  auth = AuthClient()
81
81
  token = auth.device_auth_flow()
@@ -87,15 +87,18 @@ creds = Credentials.from_authclient_token(token)
87
87
  Credentials are always cached once you authenticate at least one time, calling this method without having a cached credential file will automatically trigger the AuthClient device flow.
88
88
 
89
89
  ```python
90
- from cmsdials.auth.bearer import Credentials
90
+ from cmsdials.auth.device import Credentials
91
91
 
92
92
  creds = Credentials.from_creds_file()
93
93
  ```
94
94
 
95
+ > [!WARNING]
96
+ > `cmsdials.auth.bearer.Credentials` is deprecated and emits a `DeprecationWarning`: it will be removed in dials-py 3.0.0. Import `Credentials` from `cmsdials.auth.device` instead, it takes the same arguments.
97
+
95
98
  ### Basic Example
96
99
 
97
100
  ```python
98
- from cmsdials.auth.bearer import Credentials
101
+ from cmsdials.auth.device import Credentials
99
102
  from cmsdials import Dials
100
103
  from cmsdials.filters import LumisectionHistogram1DFilters
101
104
 
@@ -260,7 +263,7 @@ When listing and endpoint for a long time, you may loose connection or the serve
260
263
 
261
264
  ```python
262
265
  from cmsdials.auth.client import AuthClient
263
- from cmsdials.auth.bearer import Credentials
266
+ from cmsdials.auth.device import Credentials
264
267
  from cmsdials import Dials
265
268
  from cmsdials.filters import LumisectionHistogram2DFilters
266
269
 
@@ -319,7 +322,7 @@ The `AuthClient` issues tokens for the production DIALS applications by default.
319
322
 
320
323
  ```python
321
324
  from cmsdials.auth.client import AuthClient
322
- from cmsdials.auth.bearer import Credentials
325
+ from cmsdials.auth.device import Credentials
323
326
  from cmsdials import Dials
324
327
  from cmsdials.filters import LumisectionHistogram2DFilters
325
328
 
@@ -338,9 +341,9 @@ dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="EEOT digi occupancy
338
341
  When authenticating with a client secret, pass the same `AuthClient` so the token is issued for that instance:
339
342
 
340
343
  ```python
341
- from cmsdials.auth.secret_key import Credentials as SecretKeyCredentials
344
+ from cmsdials.auth.client_credentials import Credentials as ClientCredentials
342
345
 
343
- creds = SecretKeyCredentials(client_id="my-application", token=".....", client=auth)
346
+ creds = ClientCredentials(client_id="my-application", client_secret=".....", client=auth)
344
347
  dials = Dials(creds, base_url=DIALS_URL)
345
348
  ```
346
349
 
@@ -30,21 +30,21 @@ Credentials are issued by CERN's Keycloak directly, dials-py never sends your pa
30
30
  ### Generating credentials with client secret
31
31
 
32
32
  ```python
33
- from cmsdials.auth.secret_key import Credentials
33
+ from cmsdials.auth.client_credentials import Credentials
34
34
 
35
- creds = Credentials(client_id="my-application", token=".....")
35
+ creds = Credentials(client_id="my-application", client_secret=".....")
36
36
  ```
37
37
 
38
38
  The token is renewed automatically before it expires.
39
39
 
40
- To get API access:
40
+ To get API access (see [API access for applications](https://cms-ppd.docs.cern.ch/ts/software/DIALS/operations/api_access.html) for the details):
41
41
 
42
42
  1. Register an application in the [CERN Application Portal](https://application-portal.web.cern.ch) and enable "My application will need to get tokens using its own client ID and secret".
43
43
  2. Ask the DIALS team to grant API access to the application (it is added to the DIALS API access group).
44
44
  3. Use the application's client id and secret as shown above.
45
45
 
46
46
  > [!WARNING]
47
- > `Credentials(token=".....")` without `client_id` authenticates with the `X-CLIENT-SECRET` header, which is deprecated and emits a `DeprecationWarning`. DIALS will stop accepting it, so pass the `client_id` of your application.
47
+ > `cmsdials.auth.secret_key.Credentials` is deprecated and emits a `DeprecationWarning`: it will be removed in dials-py 3.0.0. Replace `Credentials(client_id=..., token=...)` with `client_credentials.Credentials(client_id=..., client_secret=...)`. Without `client_id`, it authenticates with the `X-CLIENT-SECRET` header, which DIALS will stop accepting.
48
48
 
49
49
  ### Generating credentials with device
50
50
 
@@ -52,7 +52,7 @@ To get API access:
52
52
 
53
53
  ```python
54
54
  from cmsdials.auth.client import AuthClient
55
- from cmsdials.auth.bearer import Credentials
55
+ from cmsdials.auth.device import Credentials
56
56
 
57
57
  auth = AuthClient()
58
58
  token = auth.device_auth_flow()
@@ -64,15 +64,18 @@ creds = Credentials.from_authclient_token(token)
64
64
  Credentials are always cached once you authenticate at least one time, calling this method without having a cached credential file will automatically trigger the AuthClient device flow.
65
65
 
66
66
  ```python
67
- from cmsdials.auth.bearer import Credentials
67
+ from cmsdials.auth.device import Credentials
68
68
 
69
69
  creds = Credentials.from_creds_file()
70
70
  ```
71
71
 
72
+ > [!WARNING]
73
+ > `cmsdials.auth.bearer.Credentials` is deprecated and emits a `DeprecationWarning`: it will be removed in dials-py 3.0.0. Import `Credentials` from `cmsdials.auth.device` instead, it takes the same arguments.
74
+
72
75
  ### Basic Example
73
76
 
74
77
  ```python
75
- from cmsdials.auth.bearer import Credentials
78
+ from cmsdials.auth.device import Credentials
76
79
  from cmsdials import Dials
77
80
  from cmsdials.filters import LumisectionHistogram1DFilters
78
81
 
@@ -237,7 +240,7 @@ When listing and endpoint for a long time, you may loose connection or the serve
237
240
 
238
241
  ```python
239
242
  from cmsdials.auth.client import AuthClient
240
- from cmsdials.auth.bearer import Credentials
243
+ from cmsdials.auth.device import Credentials
241
244
  from cmsdials import Dials
242
245
  from cmsdials.filters import LumisectionHistogram2DFilters
243
246
 
@@ -296,7 +299,7 @@ The `AuthClient` issues tokens for the production DIALS applications by default.
296
299
 
297
300
  ```python
298
301
  from cmsdials.auth.client import AuthClient
299
- from cmsdials.auth.bearer import Credentials
302
+ from cmsdials.auth.device import Credentials
300
303
  from cmsdials import Dials
301
304
  from cmsdials.filters import LumisectionHistogram2DFilters
302
305
 
@@ -315,9 +318,9 @@ dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="EEOT digi occupancy
315
318
  When authenticating with a client secret, pass the same `AuthClient` so the token is issued for that instance:
316
319
 
317
320
  ```python
318
- from cmsdials.auth.secret_key import Credentials as SecretKeyCredentials
321
+ from cmsdials.auth.client_credentials import Credentials as ClientCredentials
319
322
 
320
- creds = SecretKeyCredentials(client_id="my-application", token=".....", client=auth)
323
+ creds = ClientCredentials(client_id="my-application", client_secret=".....", client=auth)
321
324
  dials = Dials(creds, base_url=DIALS_URL)
322
325
  ```
323
326
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.3.0'
22
- __version_tuple__ = version_tuple = (2, 3, 0)
21
+ __version__ = version = '2.4.0'
22
+ __version_tuple__ = version_tuple = (2, 4, 0)
23
23
 
24
- __commit_id__ = commit_id = 'g044d28ceb'
24
+ __commit_id__ = commit_id = 'g9cd07880c'
@@ -0,0 +1,62 @@
1
+ import warnings
2
+ from datetime import datetime
3
+ from typing import Optional
4
+
5
+ from .client import AuthClient
6
+ from .device import Credentials as DeviceCredentials
7
+ from .models import Token
8
+
9
+
10
+ _DEPRECATION = (
11
+ "cmsdials.auth.bearer.Credentials is deprecated and will be removed in "
12
+ "dials-py 3.0.0, use cmsdials.auth.device.Credentials instead."
13
+ )
14
+
15
+
16
+ class Credentials(DeviceCredentials):
17
+ """
18
+ Deprecated, use `cmsdials.auth.device.Credentials`.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ token: str,
24
+ expires_at: datetime,
25
+ expires_in: int,
26
+ refresh_expires_at: datetime,
27
+ refresh_expires_in: int,
28
+ refresh_token: str,
29
+ token_type: str,
30
+ cache_dir: Optional[str] = None,
31
+ client: Optional[AuthClient] = None,
32
+ ):
33
+ warnings.warn(_DEPRECATION, DeprecationWarning, stacklevel=2)
34
+ super().__init__(
35
+ token=token,
36
+ expires_at=expires_at,
37
+ expires_in=expires_in,
38
+ refresh_expires_at=refresh_expires_at,
39
+ refresh_expires_in=refresh_expires_in,
40
+ refresh_token=refresh_token,
41
+ token_type=token_type,
42
+ cache_dir=cache_dir,
43
+ client=client,
44
+ )
45
+
46
+ @staticmethod
47
+ def from_authclient_token(
48
+ token: Token,
49
+ cache_dir: Optional[str] = None,
50
+ client: Optional[AuthClient] = None,
51
+ ) -> DeviceCredentials:
52
+ warnings.warn(_DEPRECATION, DeprecationWarning, stacklevel=2)
53
+ return DeviceCredentials.from_authclient_token(
54
+ token, cache_dir, client
55
+ )
56
+
57
+ @staticmethod
58
+ def from_creds_file(
59
+ cache_dir: Optional[str] = None, client: Optional[AuthClient] = None
60
+ ) -> DeviceCredentials:
61
+ warnings.warn(_DEPRECATION, DeprecationWarning, stacklevel=2)
62
+ return DeviceCredentials.from_creds_file(cache_dir, client)
@@ -1,4 +1,5 @@
1
1
  import time
2
+ import warnings
2
3
  from datetime import datetime, timedelta
3
4
  from typing import Optional
4
5
 
@@ -47,6 +48,23 @@ class AuthClient(BaseAPIClient):
47
48
  audience: Optional[str] = None,
48
49
  **kwargs,
49
50
  ) -> None:
51
+ # Leftovers from releases that logged in through DIALS: tokens are now
52
+ # issued by Keycloak, and the instance is selected by client_id and
53
+ # audience, so these would silently target production.
54
+ ignored = [
55
+ name
56
+ for name in ("base_url", "route", "version")
57
+ if kwargs.get(name) is not None
58
+ ]
59
+ if args or ignored:
60
+ warnings.warn(
61
+ "AuthClient ignores base_url, route and version, which will "
62
+ "raise TypeError in dials-py 3.0.0. Tokens are issued by CERN "
63
+ "Keycloak: to target another DIALS instance, pass its "
64
+ "client_id and audience instead.",
65
+ DeprecationWarning,
66
+ stacklevel=2,
67
+ )
50
68
  super().__init__(*args, **kwargs)
51
69
  keycloak_url = keycloak_url or self.PRODUCTION_KEYCLOAK_URL
52
70
  if not keycloak_url.endswith("/"):
@@ -0,0 +1,48 @@
1
+ from typing import Optional
2
+
3
+ from ._base import BaseCredentials
4
+ from .client import AuthClient
5
+
6
+
7
+ class Credentials(BaseCredentials):
8
+ """
9
+ Client credentials.
10
+ These credentials use an application's client id and secret to provide
11
+ authorization to automation scripts.
12
+
13
+ An api-access token is issued by CERN's Keycloak and sent as a Bearer
14
+ token, and a new one is issued before it expires. The application must
15
+ have been granted API access in DIALS.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ client_id: str,
21
+ client_secret: str,
22
+ client: Optional[AuthClient] = None,
23
+ ) -> None:
24
+ if not client_id:
25
+ raise ValueError(
26
+ "client_id must be the non-empty client id of the application"
27
+ )
28
+ if not client_secret:
29
+ raise ValueError(
30
+ "client_secret must be the non-empty client secret of the application"
31
+ )
32
+ super().__init__()
33
+ self.client_id = client_id
34
+ self.client_secret = client_secret
35
+ self.client = client or AuthClient()
36
+ self.refresh()
37
+
38
+ def refresh(self) -> None:
39
+ api_token = self.client.api_access_token(
40
+ self.client_id, self.client_secret
41
+ )
42
+ self.token = api_token.access_token
43
+ self.token_type = api_token.token_type
44
+ self.expires_in = api_token.expires_in
45
+ self.expires_at = api_token.expires_at
46
+
47
+ def apply(self, headers: dict[str, str]) -> None:
48
+ headers["Authorization"] = f"{self.token_type} {self.token}"
@@ -19,8 +19,11 @@ DEFAULT_CACHE_FILENAME = "creds"
19
19
 
20
20
  class Credentials(BaseCredentials):
21
21
  """
22
- Token credentials.
23
- These credential use a token to provide authorization to applications.
22
+ Device credentials.
23
+ These credentials authenticate a person with the device authorization
24
+ grant on CERN's Keycloak, and renew the token with its refresh token.
25
+ Tokens are cached in a file, and the device flow is triggered again
26
+ when they can no longer be renewed.
24
27
  """
25
28
 
26
29
  def __init__(
@@ -170,6 +173,6 @@ class Credentials(BaseCredentials):
170
173
 
171
174
  def apply(self, headers: dict[str, str]) -> None:
172
175
  """
173
- Apply the Client secret key token to the X-CLIENT-SECRET header.
176
+ Apply the access token to the Authorization header.
174
177
  """
175
178
  headers["Authorization"] = f"{self.token_type} {self.token}"
@@ -0,0 +1,68 @@
1
+ import warnings
2
+ from typing import Optional
3
+
4
+ from ._base import BaseCredentials
5
+ from .client import AuthClient
6
+ from .client_credentials import Credentials as ClientCredentials
7
+
8
+
9
+ class Credentials(ClientCredentials):
10
+ """
11
+ Deprecated, use `cmsdials.auth.client_credentials.Credentials`.
12
+
13
+ With `client_id`, it behaves as client credentials, with `token` as the
14
+ client secret.
15
+
16
+ Without `client_id`, the secret is sent in the X-CLIENT-SECRET header.
17
+ This will stop working once DIALS removes the header.
18
+ """
19
+
20
+ def __init__(
21
+ self,
22
+ token: Optional[str],
23
+ client_id: Optional[str] = None,
24
+ client: Optional[AuthClient] = None,
25
+ ) -> None:
26
+ if not token:
27
+ raise ValueError(
28
+ "token must be a non-empty Client secret key string"
29
+ )
30
+
31
+ replacement = (
32
+ "use cmsdials.auth.client_credentials.Credentials("
33
+ "client_id=..., client_secret=...) instead."
34
+ )
35
+ self.legacy_header = client_id is None
36
+ if client_id is None:
37
+ warnings.warn(
38
+ "Credentials without client_id authenticate with the "
39
+ "X-CLIENT-SECRET header, which DIALS will stop accepting, "
40
+ + replacement,
41
+ DeprecationWarning,
42
+ stacklevel=2,
43
+ )
44
+ BaseCredentials.__init__(self)
45
+ self.client_secret = token
46
+ self.token = token
47
+ return
48
+
49
+ warnings.warn(
50
+ "cmsdials.auth.secret_key.Credentials is deprecated and will be "
51
+ "removed in dials-py 3.0.0, " + replacement,
52
+ DeprecationWarning,
53
+ stacklevel=2,
54
+ )
55
+ super().__init__(
56
+ client_id=client_id, client_secret=token, client=client
57
+ )
58
+
59
+ def refresh(self) -> None:
60
+ if self.legacy_header:
61
+ return
62
+ super().refresh()
63
+
64
+ def apply(self, headers: dict[str, str]) -> None:
65
+ if self.legacy_header:
66
+ headers["X-CLIENT-SECRET"] = self.token
67
+ else:
68
+ super().apply(headers)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmsdials
3
- Version: 2.3.0
3
+ Version: 2.4.0
4
4
  Summary: The Python api client interface to DIALS service
5
5
  Author-email: Gabriel Moreira <gabriel.moreira@cern.ch>
6
6
  Project-URL: Repository, https://gitlab.cern.ch/cms-dqmdc/libraries/dials-py
@@ -53,21 +53,21 @@ Credentials are issued by CERN's Keycloak directly, dials-py never sends your pa
53
53
  ### Generating credentials with client secret
54
54
 
55
55
  ```python
56
- from cmsdials.auth.secret_key import Credentials
56
+ from cmsdials.auth.client_credentials import Credentials
57
57
 
58
- creds = Credentials(client_id="my-application", token=".....")
58
+ creds = Credentials(client_id="my-application", client_secret=".....")
59
59
  ```
60
60
 
61
61
  The token is renewed automatically before it expires.
62
62
 
63
- To get API access:
63
+ To get API access (see [API access for applications](https://cms-ppd.docs.cern.ch/ts/software/DIALS/operations/api_access.html) for the details):
64
64
 
65
65
  1. Register an application in the [CERN Application Portal](https://application-portal.web.cern.ch) and enable "My application will need to get tokens using its own client ID and secret".
66
66
  2. Ask the DIALS team to grant API access to the application (it is added to the DIALS API access group).
67
67
  3. Use the application's client id and secret as shown above.
68
68
 
69
69
  > [!WARNING]
70
- > `Credentials(token=".....")` without `client_id` authenticates with the `X-CLIENT-SECRET` header, which is deprecated and emits a `DeprecationWarning`. DIALS will stop accepting it, so pass the `client_id` of your application.
70
+ > `cmsdials.auth.secret_key.Credentials` is deprecated and emits a `DeprecationWarning`: it will be removed in dials-py 3.0.0. Replace `Credentials(client_id=..., token=...)` with `client_credentials.Credentials(client_id=..., client_secret=...)`. Without `client_id`, it authenticates with the `X-CLIENT-SECRET` header, which DIALS will stop accepting.
71
71
 
72
72
  ### Generating credentials with device
73
73
 
@@ -75,7 +75,7 @@ To get API access:
75
75
 
76
76
  ```python
77
77
  from cmsdials.auth.client import AuthClient
78
- from cmsdials.auth.bearer import Credentials
78
+ from cmsdials.auth.device import Credentials
79
79
 
80
80
  auth = AuthClient()
81
81
  token = auth.device_auth_flow()
@@ -87,15 +87,18 @@ creds = Credentials.from_authclient_token(token)
87
87
  Credentials are always cached once you authenticate at least one time, calling this method without having a cached credential file will automatically trigger the AuthClient device flow.
88
88
 
89
89
  ```python
90
- from cmsdials.auth.bearer import Credentials
90
+ from cmsdials.auth.device import Credentials
91
91
 
92
92
  creds = Credentials.from_creds_file()
93
93
  ```
94
94
 
95
+ > [!WARNING]
96
+ > `cmsdials.auth.bearer.Credentials` is deprecated and emits a `DeprecationWarning`: it will be removed in dials-py 3.0.0. Import `Credentials` from `cmsdials.auth.device` instead, it takes the same arguments.
97
+
95
98
  ### Basic Example
96
99
 
97
100
  ```python
98
- from cmsdials.auth.bearer import Credentials
101
+ from cmsdials.auth.device import Credentials
99
102
  from cmsdials import Dials
100
103
  from cmsdials.filters import LumisectionHistogram1DFilters
101
104
 
@@ -260,7 +263,7 @@ When listing and endpoint for a long time, you may loose connection or the serve
260
263
 
261
264
  ```python
262
265
  from cmsdials.auth.client import AuthClient
263
- from cmsdials.auth.bearer import Credentials
266
+ from cmsdials.auth.device import Credentials
264
267
  from cmsdials import Dials
265
268
  from cmsdials.filters import LumisectionHistogram2DFilters
266
269
 
@@ -319,7 +322,7 @@ The `AuthClient` issues tokens for the production DIALS applications by default.
319
322
 
320
323
  ```python
321
324
  from cmsdials.auth.client import AuthClient
322
- from cmsdials.auth.bearer import Credentials
325
+ from cmsdials.auth.device import Credentials
323
326
  from cmsdials import Dials
324
327
  from cmsdials.filters import LumisectionHistogram2DFilters
325
328
 
@@ -338,9 +341,9 @@ dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="EEOT digi occupancy
338
341
  When authenticating with a client secret, pass the same `AuthClient` so the token is issued for that instance:
339
342
 
340
343
  ```python
341
- from cmsdials.auth.secret_key import Credentials as SecretKeyCredentials
344
+ from cmsdials.auth.client_credentials import Credentials as ClientCredentials
342
345
 
343
- creds = SecretKeyCredentials(client_id="my-application", token=".....", client=auth)
346
+ creds = ClientCredentials(client_id="my-application", client_secret=".....", client=auth)
344
347
  dials = Dials(creds, base_url=DIALS_URL)
345
348
  ```
346
349
 
@@ -28,6 +28,8 @@ cmsdials/auth/__init__.py
28
28
  cmsdials/auth/_base.py
29
29
  cmsdials/auth/bearer.py
30
30
  cmsdials/auth/client.py
31
+ cmsdials/auth/client_credentials.py
32
+ cmsdials/auth/device.py
31
33
  cmsdials/auth/exceptions.py
32
34
  cmsdials/auth/models.py
33
35
  cmsdials/auth/secret_key.py
@@ -14,6 +14,8 @@
14
14
  "cmsdials/auth/_base.py",
15
15
  "cmsdials/auth/bearer.py",
16
16
  "cmsdials/auth/client.py",
17
+ "cmsdials/auth/client_credentials.py",
18
+ "cmsdials/auth/device.py",
17
19
  "cmsdials/auth/exceptions.py",
18
20
  "cmsdials/auth/models.py",
19
21
  "cmsdials/auth/secret_key.py",
@@ -1,7 +1,7 @@
1
1
  {
2
- "tag": "2.3.0",
2
+ "tag": "2.4.0",
3
3
  "distance": 0,
4
- "node": "g044d28ceb686efd620bbbb7c6a4d87fbda5d076c",
4
+ "node": "g9cd07880ca4c733ad91f8794d91a5d384be58fbf",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-10-21"
@@ -1,9 +1,10 @@
1
1
  import os
2
2
 
3
3
 
4
- CLIENT_SECRET = os.getenv("CLIENT_SECRET")
5
- if not CLIENT_SECRET:
4
+ _client_secret = os.getenv("CLIENT_SECRET")
5
+ if not _client_secret:
6
6
  raise OSError("CLIENT_SECRET environment variable is not set.")
7
+ CLIENT_SECRET: str = _client_secret
7
8
 
8
9
  BASE_URL = os.getenv("BASE_URL", "https://dev-cmsdials-api.web.cern.ch/")
9
10
  # Client id of the application owning CLIENT_SECRET. When set, tests authenticate
@@ -5,7 +5,10 @@ import pytest
5
5
  from requests.exceptions import HTTPError
6
6
 
7
7
  from cmsdials import Dials
8
- from cmsdials.auth.bearer import Credentials as BearerCredentials
8
+ from cmsdials.auth.client_credentials import (
9
+ Credentials as ClientCredentials,
10
+ )
11
+ from cmsdials.auth.device import Credentials as DeviceCredentials
9
12
  from cmsdials.auth.models import Token
10
13
  from cmsdials.auth.secret_key import Credentials as SecretKeyCredentials
11
14
 
@@ -14,7 +17,7 @@ from .utils import build_auth_client
14
17
 
15
18
 
16
19
  def __setup_dials_from_creds_and_test(
17
- creds: Union[SecretKeyCredentials, BearerCredentials],
20
+ creds: Union[ClientCredentials, SecretKeyCredentials, DeviceCredentials],
18
21
  ) -> None:
19
22
  dials = Dials(creds, workspace=TEST_WORKSPACE, base_url=BASE_URL)
20
23
  # just test if we can access the workspace endpoints
@@ -29,8 +32,11 @@ def test_secret_key_success() -> None:
29
32
 
30
33
  @pytest.mark.skipif(CLIENT_ID is None, reason="CLIENT_ID is not set")
31
34
  def test_api_access_success() -> None:
32
- creds = SecretKeyCredentials(
33
- token=CLIENT_SECRET, client_id=CLIENT_ID, client=build_auth_client()
35
+ assert CLIENT_ID is not None
36
+ creds = ClientCredentials(
37
+ client_id=CLIENT_ID,
38
+ client_secret=CLIENT_SECRET,
39
+ client=build_auth_client(),
34
40
  )
35
41
  headers: dict[str, str] = {}
36
42
  creds.before_request(headers)
@@ -40,9 +46,12 @@ def test_api_access_success() -> None:
40
46
 
41
47
  @pytest.mark.skipif(CLIENT_ID is None, reason="CLIENT_ID is not set")
42
48
  def test_api_access_fail() -> None:
49
+ assert CLIENT_ID is not None
43
50
  with pytest.raises(HTTPError, match=r"401 Client Error: Unauthorized"):
44
- SecretKeyCredentials(
45
- token="123456789", client_id=CLIENT_ID, client=build_auth_client()
51
+ ClientCredentials(
52
+ client_id=CLIENT_ID,
53
+ client_secret="123456789",
54
+ client=build_auth_client(),
46
55
  )
47
56
 
48
57
 
@@ -57,14 +66,14 @@ def test_secret_key_fail() -> None:
57
66
  # This cannot be tested automatically since the `device_auth_flow` prints to stdout the url
58
67
  # the user needs to access in order to authenticate and pytest only enable "live log call"
59
68
  # if you specify the parameter "--log-cli-level=INFO".
60
- # def test_bearer_success() -> None:
69
+ # def test_device_success() -> None:
61
70
  # auth = build_auth_client()
62
71
  # token = auth.device_auth_flow()
63
- # creds = BearerCredentials.from_authclient_token(token, client=auth)
72
+ # creds = DeviceCredentials.from_authclient_token(token, client=auth)
64
73
  # __setup_dials_from_creds_and_test(creds)
65
74
 
66
75
 
67
- def test_bearer_fail() -> None:
76
+ def test_device_fail() -> None:
68
77
  token = Token(
69
78
  access_token="123456789",
70
79
  expires_in=600,
@@ -75,7 +84,7 @@ def test_bearer_fail() -> None:
75
84
  refresh_expires_at=datetime.now() + timedelta(seconds=12000),
76
85
  )
77
86
  auth = build_auth_client()
78
- creds = BearerCredentials.from_authclient_token(token, client=auth)
87
+ creds = DeviceCredentials.from_authclient_token(token, client=auth)
79
88
  with pytest.raises(
80
89
  HTTPError, match=r"403 Client Error: Forbidden for url"
81
90
  ):
@@ -0,0 +1,32 @@
1
+ from cmsdials import Dials
2
+ from cmsdials.auth._base import BaseCredentials
3
+ from cmsdials.auth.client import AuthClient
4
+ from cmsdials.auth.client_credentials import Credentials
5
+ from cmsdials.auth.secret_key import Credentials as SecretKeyCredentials
6
+
7
+ from .env import (
8
+ AUTH_AUDIENCE,
9
+ AUTH_CLIENT_ID,
10
+ BASE_URL,
11
+ CLIENT_ID,
12
+ CLIENT_SECRET,
13
+ TEST_WORKSPACE,
14
+ )
15
+
16
+
17
+ def build_auth_client() -> AuthClient:
18
+ return AuthClient(client_id=AUTH_CLIENT_ID, audience=AUTH_AUDIENCE)
19
+
20
+
21
+ def setup_dials_object() -> Dials:
22
+ creds: BaseCredentials
23
+ if CLIENT_ID is None:
24
+ # Deprecated X-CLIENT-SECRET header, until DIALS stops accepting it
25
+ creds = SecretKeyCredentials(token=CLIENT_SECRET)
26
+ else:
27
+ creds = Credentials(
28
+ client_id=CLIENT_ID,
29
+ client_secret=CLIENT_SECRET,
30
+ client=build_auth_client(),
31
+ )
32
+ return Dials(creds, base_url=BASE_URL, workspace=TEST_WORKSPACE)
@@ -7,6 +7,10 @@ from requests.exceptions import HTTPError
7
7
  from cmsdials.auth import client as client_module
8
8
  from cmsdials.auth.bearer import Credentials as BearerCredentials
9
9
  from cmsdials.auth.client import AuthClient
10
+ from cmsdials.auth.client_credentials import (
11
+ Credentials as ClientCredentials,
12
+ )
13
+ from cmsdials.auth.device import Credentials as DeviceCredentials
10
14
  from cmsdials.auth.exceptions import PendingAuthorizationError
11
15
  from cmsdials.auth.models import ApiToken, Token
12
16
  from cmsdials.auth.secret_key import Credentials as SecretKeyCredentials
@@ -70,6 +74,21 @@ def test_defaults_target_production():
70
74
  assert auth.audience == "cms-dials-prod-confidential-app"
71
75
 
72
76
 
77
+ @pytest.mark.parametrize(
78
+ ("args", "kwargs"),
79
+ [
80
+ (("https://dev-cmsdials-api.web.cern.ch/",), {}),
81
+ ((), {"base_url": "https://dev-cmsdials-api.web.cern.ch/"}),
82
+ ((), {"route": "api/"}),
83
+ ((), {"version": "v1/"}),
84
+ ],
85
+ )
86
+ def test_auth_client_warns_on_ignored_api_arguments(args, kwargs):
87
+ with pytest.warns(DeprecationWarning, match="client_id and audience"):
88
+ auth = AuthClient(*args, **kwargs)
89
+ assert auth.audience == AuthClient.PRODUCTION_AUDIENCE
90
+
91
+
73
92
  def test_api_access_token_requests_dials_audience(keycloak):
74
93
  calls, responses = keycloak
75
94
  responses.append(FakeResponse(200, API_TOKEN))
@@ -137,7 +156,7 @@ def test_refresh_token_refreshes_then_exchanges(keycloak):
137
156
 
138
157
 
139
158
  def test_secret_key_without_client_id_is_deprecated():
140
- with pytest.warns(DeprecationWarning):
159
+ with pytest.warns(DeprecationWarning, match="X-CLIENT-SECRET"):
141
160
  creds = SecretKeyCredentials(token="my-secret")
142
161
  headers = {}
143
162
  creds.before_request(headers)
@@ -159,10 +178,21 @@ class FakeAuthClient:
159
178
  )
160
179
 
161
180
 
162
- def test_secret_key_with_client_id_sends_bearer():
181
+ def test_client_credentials_require_client_id_and_secret():
182
+ with pytest.raises(ValueError, match="client_id"):
183
+ ClientCredentials(
184
+ client_id="", client_secret="my-secret", client=FakeAuthClient()
185
+ )
186
+ with pytest.raises(ValueError, match="client_secret"):
187
+ ClientCredentials(
188
+ client_id="my-app", client_secret="", client=FakeAuthClient()
189
+ )
190
+
191
+
192
+ def test_client_credentials_send_bearer():
163
193
  auth = FakeAuthClient()
164
- creds = SecretKeyCredentials(
165
- token="my-secret", client_id="my-app", client=auth
194
+ creds = ClientCredentials(
195
+ client_id="my-app", client_secret="my-secret", client=auth
166
196
  )
167
197
 
168
198
  headers = {}
@@ -174,10 +204,10 @@ def test_secret_key_with_client_id_sends_bearer():
174
204
  assert auth.issued == 1
175
205
 
176
206
 
177
- def test_secret_key_reissues_expired_token():
207
+ def test_client_credentials_reissue_expired_token():
178
208
  auth = FakeAuthClient()
179
- creds = SecretKeyCredentials(
180
- token="my-secret", client_id="my-app", client=auth
209
+ creds = ClientCredentials(
210
+ client_id="my-app", client_secret="my-secret", client=auth
181
211
  )
182
212
  creds.expires_at = datetime.now() - timedelta(seconds=1)
183
213
 
@@ -188,6 +218,21 @@ def test_secret_key_reissues_expired_token():
188
218
  assert auth.issued == 2
189
219
 
190
220
 
221
+ def test_secret_key_with_client_id_is_deprecated_alias():
222
+ auth = FakeAuthClient()
223
+ with pytest.warns(DeprecationWarning, match="client_credentials"):
224
+ creds = SecretKeyCredentials(
225
+ token="my-secret", client_id="my-app", client=auth
226
+ )
227
+
228
+ headers = {}
229
+ creds.before_request(headers)
230
+
231
+ assert isinstance(creds, ClientCredentials)
232
+ assert creds.client_secret == "my-secret"
233
+ assert headers == {"Authorization": "Bearer api-access-1"}
234
+
235
+
191
236
  class RejectingAuthClient:
192
237
  def __init__(self):
193
238
  self.device_flows = 0
@@ -209,10 +254,10 @@ class RejectingAuthClient:
209
254
  )
210
255
 
211
256
 
212
- def test_bearer_falls_back_to_device_flow_when_refresh_rejected(tmp_path):
257
+ def test_device_falls_back_to_device_flow_when_refresh_rejected(tmp_path):
213
258
  now = datetime.now()
214
259
  auth = RejectingAuthClient()
215
- creds = BearerCredentials(
260
+ creds = DeviceCredentials(
216
261
  token="old-access",
217
262
  expires_at=now - timedelta(seconds=1),
218
263
  expires_in=1200,
@@ -245,13 +290,13 @@ class UnreachableAuthClient(RejectingAuthClient):
245
290
  (UnreachableAuthClient, requests.exceptions.ConnectionError),
246
291
  ],
247
292
  )
248
- def test_bearer_does_not_hide_other_refresh_errors(
293
+ def test_device_does_not_hide_other_refresh_errors(
249
294
  tmp_path, auth_class, error
250
295
  ):
251
296
  now = datetime.now()
252
297
  auth = auth_class()
253
298
  with pytest.raises(error):
254
- BearerCredentials(
299
+ DeviceCredentials(
255
300
  token="old-access",
256
301
  expires_at=now - timedelta(seconds=1),
257
302
  expires_in=1200,
@@ -263,3 +308,48 @@ def test_bearer_does_not_hide_other_refresh_errors(
263
308
  client=auth,
264
309
  )
265
310
  assert auth.device_flows == 0
311
+
312
+
313
+ def fresh_token():
314
+ now = datetime.now()
315
+ return Token(
316
+ access_token="dials-access",
317
+ expires_in=1200,
318
+ refresh_expires_in=36000,
319
+ refresh_token="public-refresh",
320
+ token_type="Bearer",
321
+ expires_at=now + timedelta(seconds=1200),
322
+ refresh_expires_at=now + timedelta(seconds=36000),
323
+ )
324
+
325
+
326
+ def test_bearer_is_deprecated_alias(tmp_path):
327
+ token = fresh_token()
328
+ with pytest.warns(DeprecationWarning, match="auth.device"):
329
+ creds = BearerCredentials(
330
+ token=token.access_token,
331
+ expires_at=token.expires_at,
332
+ expires_in=token.expires_in,
333
+ refresh_expires_at=token.refresh_expires_at,
334
+ refresh_expires_in=token.refresh_expires_in,
335
+ refresh_token=token.refresh_token,
336
+ token_type=token.token_type,
337
+ cache_dir=str(tmp_path),
338
+ client=RejectingAuthClient(),
339
+ )
340
+ assert isinstance(creds, DeviceCredentials)
341
+
342
+ with pytest.warns(DeprecationWarning, match="auth.device"):
343
+ creds = BearerCredentials.from_authclient_token(
344
+ token, cache_dir=str(tmp_path), client=RejectingAuthClient()
345
+ )
346
+ assert isinstance(creds, DeviceCredentials)
347
+
348
+ # Reads the credentials cached by the calls above
349
+ with pytest.warns(DeprecationWarning, match="auth.device"):
350
+ creds = BearerCredentials.from_creds_file(
351
+ cache_dir=str(tmp_path), client=RejectingAuthClient()
352
+ )
353
+ headers = {}
354
+ creds.before_request(headers)
355
+ assert headers == {"Authorization": "Bearer dials-access"}
@@ -1,65 +0,0 @@
1
- import warnings
2
- from typing import Optional
3
-
4
- from ._base import BaseCredentials
5
- from .client import AuthClient
6
-
7
-
8
- class Credentials(BaseCredentials):
9
- """
10
- Client secret key credentials.
11
- These credentials use an application's client id and secret to provide
12
- authorization to automation scripts.
13
-
14
- With `client_id`, an api-access token is issued by CERN's Keycloak and sent
15
- as a Bearer token, and a new one is issued before it expires. The
16
- application must have been granted API access in DIALS.
17
-
18
- Without `client_id`, the secret is sent in the X-CLIENT-SECRET header.
19
- This is deprecated and will stop working once DIALS removes the header.
20
- """
21
-
22
- def __init__(
23
- self,
24
- token: Optional[str],
25
- client_id: Optional[str] = None,
26
- client: Optional[AuthClient] = None,
27
- ) -> None:
28
- if not token:
29
- raise ValueError(
30
- "token must be a non-empty Client secret key string"
31
- )
32
- super().__init__()
33
- self.client_id = client_id
34
- self.client_secret = token
35
-
36
- if client_id is None:
37
- warnings.warn(
38
- "Credentials without client_id authenticate with the "
39
- "X-CLIENT-SECRET header, which DIALS will stop accepting. "
40
- "Pass the application's client_id as well.",
41
- DeprecationWarning,
42
- stacklevel=2,
43
- )
44
- self.token = token
45
- return
46
-
47
- self.client = client or AuthClient()
48
- self.refresh()
49
-
50
- def refresh(self) -> None:
51
- if self.client_id is None:
52
- return
53
- api_token = self.client.api_access_token(
54
- self.client_id, self.client_secret
55
- )
56
- self.token = api_token.access_token
57
- self.token_type = api_token.token_type
58
- self.expires_in = api_token.expires_in
59
- self.expires_at = api_token.expires_at
60
-
61
- def apply(self, headers: dict[str, str]) -> None:
62
- if self.client_id is None:
63
- headers["X-CLIENT-SECRET"] = self.token
64
- else:
65
- headers["Authorization"] = f"{self.token_type} {self.token}"
@@ -1,23 +0,0 @@
1
- from cmsdials import Dials
2
- from cmsdials.auth.client import AuthClient
3
- from cmsdials.auth.secret_key import Credentials
4
-
5
- from .env import (
6
- AUTH_AUDIENCE,
7
- AUTH_CLIENT_ID,
8
- BASE_URL,
9
- CLIENT_ID,
10
- CLIENT_SECRET,
11
- TEST_WORKSPACE,
12
- )
13
-
14
-
15
- def build_auth_client() -> AuthClient:
16
- return AuthClient(client_id=AUTH_CLIENT_ID, audience=AUTH_AUDIENCE)
17
-
18
-
19
- def setup_dials_object() -> Dials:
20
- creds = Credentials(
21
- token=CLIENT_SECRET, client_id=CLIENT_ID, client=build_auth_client()
22
- )
23
- return Dials(creds, base_url=BASE_URL, workspace=TEST_WORKSPACE)
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