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.
- {keymint-2.4.0/keymint.egg-info → keymint-2.5.0}/PKG-INFO +4 -8
- {keymint-2.4.0 → keymint-2.5.0}/README.md +1 -1
- {keymint-2.4.0 → keymint-2.5.0}/keymint/__init__.py +10 -6
- {keymint-2.4.0 → keymint-2.5.0}/keymint/_version.py +1 -1
- {keymint-2.4.0 → keymint-2.5.0}/keymint/types.py +9 -4
- {keymint-2.4.0 → keymint-2.5.0/keymint.egg-info}/PKG-INFO +4 -8
- {keymint-2.4.0 → keymint-2.5.0}/keymint.egg-info/SOURCES.txt +2 -1
- {keymint-2.4.0 → keymint-2.5.0}/keymint.egg-info/requires.txt +1 -1
- {keymint-2.4.0 → keymint-2.5.0}/setup.py +3 -7
- keymint-2.5.0/tests/test_contracts.py +52 -0
- {keymint-2.4.0 → keymint-2.5.0}/LICENSE +0 -0
- {keymint-2.4.0 → keymint-2.5.0}/MANIFEST.in +0 -0
- {keymint-2.4.0 → keymint-2.5.0}/keymint/identity.py +0 -0
- {keymint-2.4.0 → keymint-2.5.0}/keymint.egg-info/dependency_links.txt +0 -0
- {keymint-2.4.0 → keymint-2.5.0}/keymint.egg-info/top_level.txt +0 -0
- {keymint-2.4.0 → keymint-2.5.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: keymint
|
|
3
|
-
Version: 2.
|
|
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.
|
|
23
|
+
Requires-Python: >=3.10
|
|
28
24
|
Description-Content-Type: text/markdown
|
|
29
25
|
License-File: LICENSE
|
|
30
|
-
Requires-Dist: requests>=2.
|
|
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
|
|
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
|
|
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'
|
|
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(
|
|
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
|
-
|
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
23
|
+
Requires-Python: >=3.10
|
|
28
24
|
Description-Content-Type: text/markdown
|
|
29
25
|
License-File: LICENSE
|
|
30
|
-
Requires-Dist: requests>=2.
|
|
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
|
|
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. |
|
|
@@ -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.
|
|
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.
|
|
35
|
+
python_requires='>=3.10',
|
|
40
36
|
install_requires=[
|
|
41
|
-
'requests>=2.
|
|
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
|
|
File without changes
|
|
File without changes
|