keymint 2.4.0__tar.gz → 2.5.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keymint
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: License key validation, activation, and management for Python. Supports node-locking, offline licensing, and hardware fingerprinting.
5
5
  Home-page: https://github.com/keymint-dev/keymint-python
6
6
  Author: KeyMint
@@ -16,18 +16,14 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
16
  Classifier: Topic :: System :: Software Distribution
17
17
  Classifier: License :: OSI Approved :: MIT License
18
18
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.6
20
- Classifier: Programming Language :: Python :: 3.7
21
- Classifier: Programming Language :: Python :: 3.8
22
- Classifier: Programming Language :: Python :: 3.9
23
19
  Classifier: Programming Language :: Python :: 3.10
24
20
  Classifier: Programming Language :: Python :: 3.11
25
21
  Classifier: Programming Language :: Python :: 3.12
26
22
  Classifier: Operating System :: OS Independent
27
- Requires-Python: >=3.6
23
+ Requires-Python: >=3.10
28
24
  Description-Content-Type: text/markdown
29
25
  License-File: LICENSE
30
- Requires-Dist: requests>=2.25.0
26
+ Requires-Dist: requests>=2.32.4
31
27
  Provides-Extra: dev
32
28
  Requires-Dist: pytest>=6.0; extra == "dev"
33
29
  Requires-Dist: pytest-cov; extra == "dev"
@@ -110,7 +106,7 @@ Keymint provides utilities to uniquely identify machines for node-locking:
110
106
  | `update_key` | Updates an existing license key. |
111
107
  | `block_key` | Blocks a license key. |
112
108
  | `unblock_key` | Unblocks a previously blocked license key. |
113
- | `sign_key` | Signs a key for offline (air-gapped) validation.|
109
+ | `sign_key` | Signs a key for offline validation using an admin API key.|
114
110
  | `floating_checkout` | Checks out a floating license seat. |
115
111
  | `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
116
112
  | `floating_checkin` | Checks in a session, releasing the seat. |
@@ -61,7 +61,7 @@ Keymint provides utilities to uniquely identify machines for node-locking:
61
61
  | `update_key` | Updates an existing license key. |
62
62
  | `block_key` | Blocks a license key. |
63
63
  | `unblock_key` | Unblocks a previously blocked license key. |
64
- | `sign_key` | Signs a key for offline (air-gapped) validation.|
64
+ | `sign_key` | Signs a key for offline validation using an admin API key.|
65
65
  | `floating_checkout` | Checks out a floating license seat. |
66
66
  | `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
67
67
  | `floating_checkin` | Checks in a session, releasing the seat. |
@@ -17,11 +17,13 @@ class KeyMint:
17
17
  'Content-Type': 'application/json'
18
18
  }
19
19
 
20
- def _handle_request(self, method: str, endpoint: str, params: dict | None = None, query_params: dict | None = None, idempotency_key: str | None = None):
20
+ def _handle_request(self, method: str, endpoint: str, params: dict | None = None, query_params: dict | None = None, idempotency_key: str | None = None, extra_headers: dict | None = None):
21
21
  url = f'{self.base_url}{endpoint}'
22
22
  headers = self.headers.copy()
23
23
  if idempotency_key:
24
24
  headers['Idempotency-Key'] = idempotency_key
25
+ if extra_headers:
26
+ headers.update(extra_headers)
25
27
 
26
28
  try:
27
29
  if method.upper() == 'GET':
@@ -42,8 +44,9 @@ class KeyMint:
42
44
  except requests.exceptions.HTTPError as http_err:
43
45
  try:
44
46
  error_data = http_err.response.json()
47
+ nested_error = error_data.get('error') if isinstance(error_data.get('error'), dict) else {}
45
48
  raise KeyMintApiError(
46
- message=error_data.get('message', 'An API error occurred'),
49
+ message=error_data.get('message') or nested_error.get('message') or 'An API error occurred',
47
50
  code=error_data.get('code', -1),
48
51
  status=http_err.response.status_code
49
52
  )
@@ -123,10 +126,12 @@ class KeyMint:
123
126
  :returns: The license key details.
124
127
  """
125
128
  query_params = {
126
- 'productId': params['productId'],
127
- 'licenseKey': params['licenseKey']
129
+ 'productId': params['productId']
128
130
  }
129
- return self._handle_request('GET', '/key', query_params=query_params)
131
+ return self._handle_request(
132
+ 'GET', '/key', query_params=query_params,
133
+ extra_headers={'x-license-key': params['licenseKey']}
134
+ )
130
135
 
131
136
  def block_key(self, params: BlockKeyParams, idempotency_key: str | None = None) -> BlockKeyResponse:
132
137
  """
@@ -288,4 +293,3 @@ class KeyMint:
288
293
  except Exception:
289
294
  return False
290
295
 
291
-
@@ -1,6 +1,6 @@
1
1
  """KeyMint Python SDK version information."""
2
2
 
3
- __version__ = "2.4.0"
3
+ __version__ = "2.5.0"
4
4
  __author__ = "KeyMint"
5
5
  __email__ = "cliff@keymint.dev"
6
6
  __url__ = "https://github.com/keymint-dev/keymint-python"
@@ -30,9 +30,10 @@ class CreateKeyParams(TypedDict):
30
30
  heartbeatInterval: int | None
31
31
  sessionLeaseDuration: int | None
32
32
 
33
- class CreateKeyResponse(TypedDict):
33
+ class CreateKeyResponse(TypedDict, total=False):
34
34
  code: int
35
35
  key: str
36
+ keys: list[str]
36
37
 
37
38
  class KeyMintApiError(Exception):
38
39
  def __init__(self, message: str, code: int, status: int | None = None):
@@ -67,6 +68,7 @@ class DeactivateKeyParams(TypedDict):
67
68
  class DeactivateKeyResponse(TypedDict):
68
69
  message: str
69
70
  code: int
71
+ devicesRemoved: int
70
72
 
71
73
  class DeviceDetails(TypedDict):
72
74
  hostId: str
@@ -179,11 +181,13 @@ class DeleteCustomerResponse(TypedDict):
179
181
  class ToggleCustomerStatusParams(TypedDict):
180
182
  customerId: str
181
183
 
182
- class ToggleCustomerStatusResponse(TypedDict):
184
+ class ToggleCustomerStatusResponse(TypedDict, total=False):
183
185
  action: str
184
186
  status: bool
185
187
  message: str
186
188
  code: int
189
+ customerName: str
190
+ active: bool
187
191
 
188
192
  class CustomerLicenseKey(TypedDict):
189
193
  id: str
@@ -210,6 +214,8 @@ class FloatingCheckoutParams(TypedDict):
210
214
  deviceTag: str | None
211
215
  userIdentifier: str | None
212
216
  apiKey: str | None
217
+ timestamp: Any | None
218
+ signature: str | None
213
219
 
214
220
  class FloatingCheckoutResponse(TypedDict):
215
221
  code: int
@@ -278,5 +284,4 @@ class SignKeyParams(TypedDict):
278
284
  ttl: int | None
279
285
 
280
286
  class SignKeyResponse(TypedDict):
281
- code: int
282
- file: dict[str, Any] # { signedKey, keyId, publicKeyFingerprint }
287
+ file: str # Serialized signed license file returned by the API
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keymint
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: License key validation, activation, and management for Python. Supports node-locking, offline licensing, and hardware fingerprinting.
5
5
  Home-page: https://github.com/keymint-dev/keymint-python
6
6
  Author: KeyMint
@@ -16,18 +16,14 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
16
  Classifier: Topic :: System :: Software Distribution
17
17
  Classifier: License :: OSI Approved :: MIT License
18
18
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.6
20
- Classifier: Programming Language :: Python :: 3.7
21
- Classifier: Programming Language :: Python :: 3.8
22
- Classifier: Programming Language :: Python :: 3.9
23
19
  Classifier: Programming Language :: Python :: 3.10
24
20
  Classifier: Programming Language :: Python :: 3.11
25
21
  Classifier: Programming Language :: Python :: 3.12
26
22
  Classifier: Operating System :: OS Independent
27
- Requires-Python: >=3.6
23
+ Requires-Python: >=3.10
28
24
  Description-Content-Type: text/markdown
29
25
  License-File: LICENSE
30
- Requires-Dist: requests>=2.25.0
26
+ Requires-Dist: requests>=2.32.4
31
27
  Provides-Extra: dev
32
28
  Requires-Dist: pytest>=6.0; extra == "dev"
33
29
  Requires-Dist: pytest-cov; extra == "dev"
@@ -110,7 +106,7 @@ Keymint provides utilities to uniquely identify machines for node-locking:
110
106
  | `update_key` | Updates an existing license key. |
111
107
  | `block_key` | Blocks a license key. |
112
108
  | `unblock_key` | Unblocks a previously blocked license key. |
113
- | `sign_key` | Signs a key for offline (air-gapped) validation.|
109
+ | `sign_key` | Signs a key for offline validation using an admin API key.|
114
110
  | `floating_checkout` | Checks out a floating license seat. |
115
111
  | `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
116
112
  | `floating_checkin` | Checks in a session, releasing the seat. |
@@ -10,4 +10,5 @@ keymint.egg-info/PKG-INFO
10
10
  keymint.egg-info/SOURCES.txt
11
11
  keymint.egg-info/dependency_links.txt
12
12
  keymint.egg-info/requires.txt
13
- keymint.egg-info/top_level.txt
13
+ keymint.egg-info/top_level.txt
14
+ tests/test_contracts.py
@@ -1,4 +1,4 @@
1
- requests>=2.25.0
1
+ requests>=2.32.4
2
2
 
3
3
  [dev]
4
4
  pytest>=6.0
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="keymint",
8
- version="2.4.0",
8
+ version="2.5.0",
9
9
  author="KeyMint",
10
10
  author_email="cliff@keymint.dev",
11
11
  description="License key validation, activation, and management for Python. Supports node-locking, offline licensing, and hardware fingerprinting.",
@@ -26,19 +26,15 @@ setup(
26
26
  "Topic :: System :: Software Distribution",
27
27
  "License :: OSI Approved :: MIT License",
28
28
  "Programming Language :: Python :: 3",
29
- "Programming Language :: Python :: 3.6",
30
- "Programming Language :: Python :: 3.7",
31
- "Programming Language :: Python :: 3.8",
32
- "Programming Language :: Python :: 3.9",
33
29
  "Programming Language :: Python :: 3.10",
34
30
  "Programming Language :: Python :: 3.11",
35
31
  "Programming Language :: Python :: 3.12",
36
32
  "Operating System :: OS Independent",
37
33
  ],
38
34
  keywords="license-key validation activation node-lock hardware-id copy-protection product-key drm offline-licensing keymint license licensing api",
39
- python_requires='>=3.6',
35
+ python_requires='>=3.10',
40
36
  install_requires=[
41
- 'requests>=2.25.0',
37
+ 'requests>=2.32.4',
42
38
  ],
43
39
  extras_require={
44
40
  'dev': [
@@ -0,0 +1,52 @@
1
+ from unittest.mock import Mock, patch
2
+
3
+ import pytest
4
+ import requests
5
+
6
+ from keymint import KeyMint, KeyMintApiError
7
+
8
+
9
+ def response(payload, status=200):
10
+ result = Mock()
11
+ result.json.return_value = payload
12
+ result.status_code = status
13
+ if status >= 400:
14
+ error = requests.exceptions.HTTPError()
15
+ error.response = result
16
+ result.raise_for_status.side_effect = error
17
+ return result
18
+
19
+
20
+ @patch("keymint.requests.get")
21
+ def test_get_key_uses_header_not_query(mock_get):
22
+ mock_get.return_value = response({"code": 0, "data": {"license": {"productId": "product 123"}}})
23
+
24
+ KeyMint("readonly_test").get_key({"productId": "product 123", "licenseKey": "secret/license+key"})
25
+
26
+ _, kwargs = mock_get.call_args
27
+ assert kwargs["params"] == {"productId": "product 123"}
28
+ assert kwargs["headers"]["x-license-key"] == "secret/license+key"
29
+ assert "licenseKey" not in kwargs["params"]
30
+
31
+
32
+ @patch("keymint.requests.post")
33
+ def test_sign_key_returns_serialized_file(mock_post):
34
+ mock_post.return_value = response({"file": '{"signedKey":"signature","keyId":"key_123"}'})
35
+
36
+ result = KeyMint("admin_test").sign_key({
37
+ "productId": "product_123", "licenseKey": "license_123", "hostId": "host_123"
38
+ })
39
+
40
+ assert isinstance(result["file"], str)
41
+
42
+
43
+ @patch("keymint.requests.post")
44
+ def test_nested_error_preserves_server_message(mock_post):
45
+ mock_post.return_value = response({
46
+ "success": False,
47
+ "error": {"code": "CUSTOMER_EMAIL_EXISTS", "message": "Customer email already exists in team"},
48
+ "code": 1,
49
+ }, 409)
50
+
51
+ with pytest.raises(KeyMintApiError, match="Customer email already exists in team"):
52
+ KeyMint("admin_test").create_key({"productId": "product_123"})
File without changes
File without changes
File without changes
File without changes