datamasque-python 1.5.0__tar.gz → 1.5.1__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 (91) hide show
  1. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/HISTORY.rst +8 -0
  2. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/PKG-INFO +1 -1
  3. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/base.py +5 -1
  4. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/ifm.py +4 -0
  5. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/dm_instance.py +7 -0
  6. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/ifm.py +5 -0
  7. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/pyproject.toml +1 -1
  8. datamasque_python-1.5.1/tests/test_timeouts.py +58 -0
  9. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/uv.lock +1 -1
  10. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/.editorconfig +0 -0
  11. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/.github/workflows/ci.yml +0 -0
  12. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/.github/workflows/release-testpypi.yml +0 -0
  13. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/.github/workflows/release.yml +0 -0
  14. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/.gitignore +0 -0
  15. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/.readthedocs.yaml +0 -0
  16. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/CONTRIBUTING.rst +0 -0
  17. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/LICENSE +0 -0
  18. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/MANIFEST.in +0 -0
  19. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/Makefile +0 -0
  20. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/NOTICE +0 -0
  21. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/README.rst +0 -0
  22. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/RELEASING.rst +0 -0
  23. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/__init__.py +0 -0
  24. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/connections.py +0 -0
  25. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/discovery.py +0 -0
  26. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/discovery_config_libraries.py +0 -0
  27. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/discovery_configs.py +0 -0
  28. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/dmclient.py +0 -0
  29. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/exceptions.py +0 -0
  30. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/files.py +0 -0
  31. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/license.py +0 -0
  32. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/__init__.py +0 -0
  33. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/connection.py +0 -0
  34. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/data_selection.py +0 -0
  35. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/discovery.py +0 -0
  36. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/discovery_config.py +0 -0
  37. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/discovery_config_library.py +0 -0
  38. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/files.py +0 -0
  39. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/git.py +0 -0
  40. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/license.py +0 -0
  41. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/pagination.py +0 -0
  42. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/rg_config.py +0 -0
  43. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/ruleset.py +0 -0
  44. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/ruleset_library.py +0 -0
  45. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/runs.py +0 -0
  46. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/safe_data_preview.py +0 -0
  47. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/status.py +0 -0
  48. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/table_reference.py +0 -0
  49. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/models/user.py +0 -0
  50. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/py.typed +0 -0
  51. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/rg_configs.py +0 -0
  52. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/ruleset_libraries.py +0 -0
  53. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/rulesets.py +0 -0
  54. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/runs.py +0 -0
  55. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/settings.py +0 -0
  56. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/spcs.py +0 -0
  57. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/table_references.py +0 -0
  58. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/datamasque/client/users.py +0 -0
  59. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/Makefile +0 -0
  60. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/client.models.rst +0 -0
  61. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/client.rst +0 -0
  62. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/conf.py +0 -0
  63. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/contributing.rst +0 -0
  64. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/history.rst +0 -0
  65. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/index.rst +0 -0
  66. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/installation.rst +0 -0
  67. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/make.bat +0 -0
  68. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/modules.rst +0 -0
  69. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/readme.rst +0 -0
  70. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/docs/usage.rst +0 -0
  71. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/__init__.py +0 -0
  72. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/conftest.py +0 -0
  73. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/helpers.py +0 -0
  74. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_base.py +0 -0
  75. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_connections.py +0 -0
  76. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_discovery.py +0 -0
  77. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_discovery_config_libraries.py +0 -0
  78. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_discovery_configs.py +0 -0
  79. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_files.py +0 -0
  80. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_ifm.py +0 -0
  81. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_license.py +0 -0
  82. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_pagination.py +0 -0
  83. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_rg_configs.py +0 -0
  84. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_ruleset_library.py +0 -0
  85. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_rulesets.py +0 -0
  86. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_runs.py +0 -0
  87. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_safe_data_preview.py +0 -0
  88. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_settings.py +0 -0
  89. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_spcs.py +0 -0
  90. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_table_references.py +0 -0
  91. {datamasque_python-1.5.0 → datamasque_python-1.5.1}/tests/test_users.py +0 -0
@@ -2,6 +2,14 @@
2
2
  History
3
3
  =======
4
4
 
5
+ 1.5.1 (2026-11-05)
6
+ ------------------
7
+
8
+ * Every request now has a connect and a read timeout,
9
+ so a server that stops answering raises ``DataMasqueTransportError`` instead of hanging the caller.
10
+ Set them with ``connect_timeout_seconds`` (default 10) and ``read_timeout_seconds`` (default 300)
11
+ on the instance config models.
12
+
5
13
  1.5.0 (2026-10-07)
6
14
  ------------------
7
15
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: datamasque-python
3
- Version: 1.5.0
3
+ Version: 1.5.1
4
4
  Summary: Official Python client for the DataMasque data-masking API.
5
5
  Project-URL: Homepage, https://datamasque.com/
6
6
  Project-URL: Documentation, https://datamasque-python.readthedocs.io/
@@ -138,6 +138,7 @@ class BaseClient:
138
138
  self.verify_ssl = connection_config.verify_ssl
139
139
  self.token_source = connection_config.token_source
140
140
  self._session = _build_session(self.verify_ssl)
141
+ self._timeout = (connection_config.connect_timeout_seconds, connection_config.read_timeout_seconds)
141
142
  if connection_config.spcs_pat:
142
143
  install_spcs_gateway_auth(self._session, connection_config.spcs_pat)
143
144
 
@@ -252,8 +253,11 @@ class BaseClient:
252
253
  params=params,
253
254
  headers=headers,
254
255
  files=files_payload,
256
+ timeout=self._timeout,
255
257
  )
256
- return self._session.request(method, url, json=data, params=params, headers=headers)
258
+ return self._session.request(
259
+ method, url, json=data, params=params, headers=headers, timeout=self._timeout
260
+ )
257
261
  except requests.RequestException as e:
258
262
  raise DataMasqueTransportError(f"Failed to reach DataMasque server at {url}: {e}") from e
259
263
 
@@ -85,6 +85,7 @@ class DataMasqueIfmClient:
85
85
  # One session for both admin-server (JWT login/refresh) and IFM (data plane)
86
86
  # traffic -- different hosts, but a single session handles per-host pooling.
87
87
  self._session = _build_session(self.verify_ssl)
88
+ self._timeout = (connection_config.connect_timeout_seconds, connection_config.read_timeout_seconds)
88
89
 
89
90
  def authenticate(self) -> None:
90
91
  """Obtain an access (and refresh) token from the admin server, or via `token_source`."""
@@ -101,6 +102,7 @@ class DataMasqueIfmClient:
101
102
  response = self._session.post(
102
103
  login_url,
103
104
  json={"username": self.username, "password": self.password},
105
+ timeout=self._timeout,
104
106
  )
105
107
  except requests.RequestException as e:
106
108
  raise DataMasqueTransportError(f"Failed to reach admin server at {login_url}: {e}") from e
@@ -127,6 +129,7 @@ class DataMasqueIfmClient:
127
129
  response = self._session.post(
128
130
  refresh_url,
129
131
  json={"refresh": self.refresh_token},
132
+ timeout=self._timeout,
130
133
  )
131
134
  except requests.RequestException as e:
132
135
  raise DataMasqueTransportError(f"Failed to reach admin server at {refresh_url}: {e}") from e
@@ -194,6 +197,7 @@ class DataMasqueIfmClient:
194
197
  json=json_body,
195
198
  params=params,
196
199
  headers={"Authorization": f"Bearer {self.access_token}"},
200
+ timeout=self._timeout,
197
201
  )
198
202
  except requests.RequestException as e:
199
203
  raise DataMasqueTransportError(f"Failed to reach IFM server at {url}: {e}") from e
@@ -4,6 +4,9 @@ from pydantic import BaseModel, ConfigDict, model_validator
4
4
 
5
5
  from datamasque.client.exceptions import DataMasqueUserError
6
6
 
7
+ DEFAULT_CONNECT_TIMEOUT_SECONDS = 10.0
8
+ DEFAULT_READ_TIMEOUT_SECONDS = 300.0
9
+
7
10
 
8
11
  class DataMasqueInstanceConfig(BaseModel):
9
12
  """
@@ -42,6 +45,10 @@ class DataMasqueInstanceConfig(BaseModel):
42
45
  Create the token in Snowsight (User profile → Programmatic access tokens)
43
46
  for an account that can reach the SPCS app.
44
47
  Leave unset for instances that are not hosted on SPCS."""
48
+ connect_timeout_seconds: float = DEFAULT_CONNECT_TIMEOUT_SECONDS
49
+ """How long a request may take to connect, including the TLS handshake."""
50
+ read_timeout_seconds: float = DEFAULT_READ_TIMEOUT_SECONDS
51
+ """How long a request may wait for the server between bytes of its response."""
45
52
 
46
53
  @model_validator(mode="after")
47
54
  def _validate_auth_source(self) -> "DataMasqueInstanceConfig":
@@ -6,6 +6,7 @@ from typing import Any, Callable, Optional
6
6
  from pydantic import BaseModel, ConfigDict, model_validator
7
7
 
8
8
  from datamasque.client.exceptions import DataMasqueUserError
9
+ from datamasque.client.models.dm_instance import DEFAULT_CONNECT_TIMEOUT_SECONDS, DEFAULT_READ_TIMEOUT_SECONDS
9
10
 
10
11
 
11
12
  class RulesetPlanOptions(BaseModel):
@@ -167,6 +168,10 @@ class DataMasqueIfmInstanceConfig(BaseModel):
167
168
  password: Optional[str] = None
168
169
  verify_ssl: bool = True
169
170
  token_source: Optional[Callable[[], str]] = None
171
+ connect_timeout_seconds: float = DEFAULT_CONNECT_TIMEOUT_SECONDS
172
+ """How long a request may take to connect, including the TLS handshake."""
173
+ read_timeout_seconds: float = DEFAULT_READ_TIMEOUT_SECONDS
174
+ """How long a request may wait for the server between bytes of its response."""
170
175
 
171
176
  @model_validator(mode="after")
172
177
  def _validate_auth_source(self) -> "DataMasqueIfmInstanceConfig":
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datamasque-python"
3
- version = "1.5.0"
3
+ version = "1.5.1"
4
4
  description = "Official Python client for the DataMasque data-masking API."
5
5
  authors = [
6
6
  { name = "DataMasque Ltd" },
@@ -0,0 +1,58 @@
1
+ """Every request the clients send has a connect and a read timeout."""
2
+
3
+ import requests_mock
4
+
5
+ from datamasque.client import DataMasqueClient, DataMasqueIfmClient, DataMasqueIfmInstanceConfig
6
+ from datamasque.client.models.dm_instance import DEFAULT_CONNECT_TIMEOUT_SECONDS, DEFAULT_READ_TIMEOUT_SECONDS
7
+
8
+ ADMIN = "http://admin.test"
9
+ IFM = "http://ifm.test"
10
+
11
+
12
+ def test_a_request_gets_the_default_timeouts(client):
13
+ with requests_mock.Mocker() as m:
14
+ m.get("http://test-server/api/healthcheck/", status_code=200)
15
+ client.healthcheck()
16
+
17
+ assert m.request_history[0].timeout == (DEFAULT_CONNECT_TIMEOUT_SECONDS, DEFAULT_READ_TIMEOUT_SECONDS)
18
+
19
+
20
+ def test_the_timeouts_come_from_the_config(config):
21
+ client = DataMasqueClient(config.model_copy(update={"connect_timeout_seconds": 3, "read_timeout_seconds": 900}))
22
+
23
+ with requests_mock.Mocker() as m:
24
+ m.post("http://test-server/api/auth/token/login/", json={"key": "test_token"}, status_code=200)
25
+ client.authenticate()
26
+
27
+ assert m.request_history[0].timeout == (3, 900)
28
+
29
+
30
+ def test_every_ifm_request_gets_the_config_timeouts():
31
+ client = DataMasqueIfmClient(
32
+ DataMasqueIfmInstanceConfig(
33
+ admin_server_base_url=ADMIN,
34
+ ifm_base_url=IFM,
35
+ username="ifm_user",
36
+ password="ifm_password",
37
+ read_timeout_seconds=60,
38
+ )
39
+ )
40
+ client.access_token = "access-1"
41
+ client.refresh_token = "refresh-1"
42
+
43
+ with requests_mock.Mocker() as m:
44
+ m.get(
45
+ f"{IFM}/ruleset-plans/",
46
+ [{"status_code": 401}, {"json": {"items": [], "total": 0, "limit": 100, "offset": 0}, "status_code": 200}],
47
+ )
48
+ m.post(f"{ADMIN}/api/auth/jwt/refresh/", status_code=401)
49
+ m.post(f"{ADMIN}/api/auth/jwt/login/", json={"access_token": "ACC", "refresh_token": "REF"}, status_code=200)
50
+ client.list_ruleset_plans()
51
+
52
+ assert [request.path for request in m.request_history] == [
53
+ "/ruleset-plans/",
54
+ "/api/auth/jwt/refresh/",
55
+ "/api/auth/jwt/login/",
56
+ "/ruleset-plans/",
57
+ ]
58
+ assert {request.timeout for request in m.request_history} == {(DEFAULT_CONNECT_TIMEOUT_SECONDS, 60)}
@@ -419,7 +419,7 @@ toml = [
419
419
 
420
420
  [[package]]
421
421
  name = "datamasque-python"
422
- version = "1.5.0"
422
+ version = "1.5.1"
423
423
  source = { editable = "." }
424
424
  dependencies = [
425
425
  { name = "pydantic" },