keymint 2.3.1__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.
- {keymint-2.3.1/keymint.egg-info → keymint-2.4.0}/PKG-INFO +3 -1
- {keymint-2.3.1 → keymint-2.4.0}/README.md +2 -0
- {keymint-2.3.1 → keymint-2.4.0}/keymint/__init__.py +44 -21
- {keymint-2.3.1 → keymint-2.4.0}/keymint/_version.py +1 -1
- {keymint-2.3.1 → keymint-2.4.0}/keymint/identity.py +9 -10
- {keymint-2.3.1 → keymint-2.4.0}/keymint/types.py +104 -59
- {keymint-2.3.1 → keymint-2.4.0/keymint.egg-info}/PKG-INFO +3 -1
- {keymint-2.3.1 → keymint-2.4.0}/setup.py +2 -2
- {keymint-2.3.1 → keymint-2.4.0}/LICENSE +0 -0
- {keymint-2.3.1 → keymint-2.4.0}/MANIFEST.in +0 -0
- {keymint-2.3.1 → keymint-2.4.0}/keymint.egg-info/SOURCES.txt +0 -0
- {keymint-2.3.1 → keymint-2.4.0}/keymint.egg-info/dependency_links.txt +0 -0
- {keymint-2.3.1 → keymint-2.4.0}/keymint.egg-info/requires.txt +0 -0
- {keymint-2.3.1 → keymint-2.4.0}/keymint.egg-info/top_level.txt +0 -0
- {keymint-2.3.1 → keymint-2.4.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.4.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
|
|
@@ -107,8 +107,10 @@ Keymint provides utilities to uniquely identify machines for node-locking:
|
|
|
107
107
|
| `activate_key` | Activates a license key for a device. |
|
|
108
108
|
| `deactivate_key` | Deactivates a device from a license key. |
|
|
109
109
|
| `get_key` | Retrieves detailed information about a key. |
|
|
110
|
+
| `update_key` | Updates an existing license key. |
|
|
110
111
|
| `block_key` | Blocks a license key. |
|
|
111
112
|
| `unblock_key` | Unblocks a previously blocked license key. |
|
|
113
|
+
| `sign_key` | Signs a key for offline (air-gapped) validation.|
|
|
112
114
|
| `floating_checkout` | Checks out a floating license seat. |
|
|
113
115
|
| `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
|
|
114
116
|
| `floating_checkin` | Checks in a session, releasing the seat. |
|
|
@@ -58,8 +58,10 @@ Keymint provides utilities to uniquely identify machines for node-locking:
|
|
|
58
58
|
| `activate_key` | Activates a license key for a device. |
|
|
59
59
|
| `deactivate_key` | Deactivates a device from a license key. |
|
|
60
60
|
| `get_key` | Retrieves detailed information about a key. |
|
|
61
|
+
| `update_key` | Updates an existing license key. |
|
|
61
62
|
| `block_key` | Blocks a license key. |
|
|
62
63
|
| `unblock_key` | Unblocks a previously blocked license key. |
|
|
64
|
+
| `sign_key` | Signs a key for offline (air-gapped) validation.|
|
|
63
65
|
| `floating_checkout` | Checks out a floating license seat. |
|
|
64
66
|
| `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
|
|
65
67
|
| `floating_checkin` | Checks in a session, releasing the seat. |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import requests
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
from ._version import __version__
|
|
4
|
+
from .types import *
|
|
4
5
|
|
|
5
6
|
__all__ = ['KeyMint', 'KeyMintApiError', '__version__']
|
|
6
7
|
|
|
@@ -16,7 +17,7 @@ class KeyMint:
|
|
|
16
17
|
'Content-Type': 'application/json'
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
def _handle_request(self, method: str, endpoint: str, params: dict = None, query_params: dict = None, idempotency_key: str = None):
|
|
20
|
+
def _handle_request(self, method: str, endpoint: str, params: dict | None = None, query_params: dict | None = None, idempotency_key: str | None = None):
|
|
20
21
|
url = f'{self.base_url}{endpoint}'
|
|
21
22
|
headers = self.headers.copy()
|
|
22
23
|
if idempotency_key:
|
|
@@ -26,9 +27,11 @@ class KeyMint:
|
|
|
26
27
|
if method.upper() == 'GET':
|
|
27
28
|
response = requests.get(url, params=query_params, headers=headers)
|
|
28
29
|
elif method.upper() == 'POST':
|
|
29
|
-
response = requests.post(url, json=params, headers=headers)
|
|
30
|
+
response = requests.post(url, json=params, params=query_params, headers=headers)
|
|
30
31
|
elif method.upper() == 'PUT':
|
|
31
|
-
response = requests.put(url, json=params, headers=headers)
|
|
32
|
+
response = requests.put(url, json=params, params=query_params, headers=headers)
|
|
33
|
+
elif method.upper() == 'PATCH':
|
|
34
|
+
response = requests.patch(url, json=params, params=query_params, headers=headers)
|
|
32
35
|
elif method.upper() == 'DELETE':
|
|
33
36
|
response = requests.delete(url, params=query_params, headers=headers)
|
|
34
37
|
else:
|
|
@@ -53,7 +56,7 @@ class KeyMint:
|
|
|
53
56
|
except Exception as err:
|
|
54
57
|
raise KeyMintApiError(message=str(err), code=-1)
|
|
55
58
|
|
|
56
|
-
def create_key(self, params: CreateKeyParams, idempotency_key: str = None) -> CreateKeyResponse:
|
|
59
|
+
def create_key(self, params: CreateKeyParams, idempotency_key: str | None = None) -> CreateKeyResponse:
|
|
57
60
|
"""
|
|
58
61
|
Creates a new license key.
|
|
59
62
|
:param params: Parameters for creating the key.
|
|
@@ -62,7 +65,7 @@ class KeyMint:
|
|
|
62
65
|
"""
|
|
63
66
|
return self._handle_request('POST', '/key', params, idempotency_key=idempotency_key)
|
|
64
67
|
|
|
65
|
-
def activate_key(self, params: ActivateKeyParams, idempotency_key: str = None) -> ActivateKeyResponse:
|
|
68
|
+
def activate_key(self, params: ActivateKeyParams, idempotency_key: str | None = None) -> ActivateKeyResponse:
|
|
66
69
|
"""
|
|
67
70
|
Activates a license key for a specific device.
|
|
68
71
|
|
|
@@ -77,7 +80,7 @@ class KeyMint:
|
|
|
77
80
|
"""
|
|
78
81
|
return self._handle_request('POST', '/key/activate', params, idempotency_key=idempotency_key)
|
|
79
82
|
|
|
80
|
-
def deactivate_key(self, params: DeactivateKeyParams, idempotency_key: str = None) -> DeactivateKeyResponse:
|
|
83
|
+
def deactivate_key(self, params: DeactivateKeyParams, idempotency_key: str | None = None) -> DeactivateKeyResponse:
|
|
81
84
|
"""
|
|
82
85
|
Deactivates a device from a license key.
|
|
83
86
|
:param params: Parameters for deactivating the key.
|
|
@@ -86,7 +89,7 @@ class KeyMint:
|
|
|
86
89
|
"""
|
|
87
90
|
return self._handle_request('POST', '/key/deactivate', params, idempotency_key=idempotency_key)
|
|
88
91
|
|
|
89
|
-
def floating_checkout(self, params: FloatingCheckoutParams, idempotency_key: str = None) -> FloatingCheckoutResponse:
|
|
92
|
+
def floating_checkout(self, params: FloatingCheckoutParams, idempotency_key: str | None = None) -> FloatingCheckoutResponse:
|
|
90
93
|
"""
|
|
91
94
|
Checks out a floating license seat.
|
|
92
95
|
:param params: Parameters for checking out the license.
|
|
@@ -95,7 +98,7 @@ class KeyMint:
|
|
|
95
98
|
"""
|
|
96
99
|
return self._handle_request('POST', '/key/checkout', params, idempotency_key=idempotency_key)
|
|
97
100
|
|
|
98
|
-
def floating_heartbeat(self, params: FloatingHeartbeatParams, idempotency_key: str = None) -> FloatingHeartbeatResponse:
|
|
101
|
+
def floating_heartbeat(self, params: FloatingHeartbeatParams, idempotency_key: str | None = None) -> FloatingHeartbeatResponse:
|
|
99
102
|
"""
|
|
100
103
|
Sends a heartbeat to keep a floating license session alive.
|
|
101
104
|
:param params: Parameters for the heartbeat (includes rotating signature).
|
|
@@ -104,7 +107,7 @@ class KeyMint:
|
|
|
104
107
|
"""
|
|
105
108
|
return self._handle_request('POST', '/key/heartbeat', params, idempotency_key=idempotency_key)
|
|
106
109
|
|
|
107
|
-
def floating_checkin(self, params: FloatingCheckinParams, idempotency_key: str = None) -> FloatingCheckinResponse:
|
|
110
|
+
def floating_checkin(self, params: FloatingCheckinParams, idempotency_key: str | None = None) -> FloatingCheckinResponse:
|
|
108
111
|
"""
|
|
109
112
|
Checks in a floating license session, releasing the seat.
|
|
110
113
|
:param params: Parameters for checking in the license (includes rotating signature).
|
|
@@ -125,7 +128,7 @@ class KeyMint:
|
|
|
125
128
|
}
|
|
126
129
|
return self._handle_request('GET', '/key', query_params=query_params)
|
|
127
130
|
|
|
128
|
-
def block_key(self, params: BlockKeyParams, idempotency_key: str = None) -> BlockKeyResponse:
|
|
131
|
+
def block_key(self, params: BlockKeyParams, idempotency_key: str | None = None) -> BlockKeyResponse:
|
|
129
132
|
"""
|
|
130
133
|
Blocks a specific license key.
|
|
131
134
|
:param params: Parameters for blocking the key.
|
|
@@ -134,7 +137,7 @@ class KeyMint:
|
|
|
134
137
|
"""
|
|
135
138
|
return self._handle_request('POST', '/key/block', params, idempotency_key=idempotency_key)
|
|
136
139
|
|
|
137
|
-
def unblock_key(self, params: UnblockKeyParams, idempotency_key: str = None) -> UnblockKeyResponse:
|
|
140
|
+
def unblock_key(self, params: UnblockKeyParams, idempotency_key: str | None = None) -> UnblockKeyResponse:
|
|
138
141
|
"""
|
|
139
142
|
Unblocks a previously blocked license key.
|
|
140
143
|
:param params: Parameters for unblocking the key.
|
|
@@ -143,9 +146,29 @@ class KeyMint:
|
|
|
143
146
|
"""
|
|
144
147
|
return self._handle_request('POST', '/key/unblock', params, idempotency_key=idempotency_key)
|
|
145
148
|
|
|
149
|
+
def update_key(self, params: 'UpdateKeyParams', idempotency_key: str | None = None) -> 'UpdateKeyResponse':
|
|
150
|
+
"""
|
|
151
|
+
Updates an existing license key.
|
|
152
|
+
Requires productId and licenseKey. All other fields are optional.
|
|
153
|
+
:param params: Parameters for updating the key.
|
|
154
|
+
:param idempotency_key: Optional unique identifier to ensure request idempotency.
|
|
155
|
+
:returns: The update confirmation.
|
|
156
|
+
"""
|
|
157
|
+
return self._handle_request('PATCH', '/key', params, idempotency_key=idempotency_key)
|
|
158
|
+
|
|
159
|
+
def sign_key(self, params: 'SignKeyParams', idempotency_key: str | None = None) -> 'SignKeyResponse':
|
|
160
|
+
"""
|
|
161
|
+
Signs a license key for offline (air-gapped) validation.
|
|
162
|
+
Requires admin API key scope and Standard plan.
|
|
163
|
+
:param params: Signing parameters (hostId is required).
|
|
164
|
+
:param idempotency_key: Optional unique identifier to ensure request idempotency.
|
|
165
|
+
:returns: The signed license file.
|
|
166
|
+
"""
|
|
167
|
+
return self._handle_request('POST', '/key/sign', params, idempotency_key=idempotency_key)
|
|
168
|
+
|
|
146
169
|
# Customer Management Methods
|
|
147
170
|
|
|
148
|
-
def create_customer(self, params: CreateCustomerParams, idempotency_key: str = None) -> CreateCustomerResponse:
|
|
171
|
+
def create_customer(self, params: CreateCustomerParams, idempotency_key: str | None = None) -> CreateCustomerResponse:
|
|
149
172
|
"""
|
|
150
173
|
Creates a new customer.
|
|
151
174
|
:param params: Parameters for creating the customer.
|
|
@@ -171,7 +194,7 @@ class KeyMint:
|
|
|
171
194
|
query_params = {'customerId': params['customerId']}
|
|
172
195
|
return self._handle_request('GET', '/customer/by-id', query_params=query_params)
|
|
173
196
|
|
|
174
|
-
def update_customer(self, params: UpdateCustomerParams, idempotency_key: str = None) -> UpdateCustomerResponse:
|
|
197
|
+
def update_customer(self, params: UpdateCustomerParams, idempotency_key: str | None = None) -> UpdateCustomerResponse:
|
|
175
198
|
"""
|
|
176
199
|
Updates an existing customer's information.
|
|
177
200
|
:param params: Parameters for updating the customer.
|
|
@@ -180,7 +203,7 @@ class KeyMint:
|
|
|
180
203
|
"""
|
|
181
204
|
return self._handle_request('PUT', '/customer/by-id', params, idempotency_key=idempotency_key)
|
|
182
205
|
|
|
183
|
-
def delete_customer(self, params: DeleteCustomerParams, idempotency_key: str = None) -> DeleteCustomerResponse:
|
|
206
|
+
def delete_customer(self, params: DeleteCustomerParams, idempotency_key: str | None = None) -> DeleteCustomerResponse:
|
|
184
207
|
"""
|
|
185
208
|
Permanently deletes a customer and all associated license keys.
|
|
186
209
|
:param params: Parameters containing the customer ID.
|
|
@@ -190,16 +213,16 @@ class KeyMint:
|
|
|
190
213
|
query_params = {'customerId': params['customerId']}
|
|
191
214
|
return self._handle_request('DELETE', '/customer/by-id', query_params=query_params, idempotency_key=idempotency_key)
|
|
192
215
|
|
|
193
|
-
def get_customer_with_keys(self, params: GetCustomerWithKeysParams) ->
|
|
216
|
+
def get_customer_with_keys(self, params: GetCustomerWithKeysParams) -> List[Dict[str, Any]]:
|
|
194
217
|
"""
|
|
195
|
-
Retrieves
|
|
218
|
+
Retrieves license keys belonging to a specific customer.
|
|
196
219
|
:param params: Parameters containing the customer ID.
|
|
197
|
-
:returns:
|
|
220
|
+
:returns: A flat list of license key objects.
|
|
198
221
|
"""
|
|
199
222
|
query_params = {'customerId': params['customerId']}
|
|
200
223
|
return self._handle_request('GET', '/customer/keys', query_params=query_params)
|
|
201
224
|
|
|
202
|
-
def toggle_customer_status(self, params: ToggleCustomerStatusParams, idempotency_key: str = None) -> ToggleCustomerStatusResponse:
|
|
225
|
+
def toggle_customer_status(self, params: ToggleCustomerStatusParams, idempotency_key: str | None = None) -> ToggleCustomerStatusResponse:
|
|
203
226
|
"""
|
|
204
227
|
Toggles the active status of a customer account (disable or enable).
|
|
205
228
|
:param params: Parameters containing the customer ID.
|
|
@@ -219,8 +242,8 @@ class KeyMint:
|
|
|
219
242
|
:param tolerance_seconds: Time tolerance in seconds to prevent replay attacks. Defaults to 300 (5 minutes).
|
|
220
243
|
:returns: True if the signature is valid, False otherwise.
|
|
221
244
|
"""
|
|
222
|
-
import hmac
|
|
223
245
|
import hashlib
|
|
246
|
+
import hmac
|
|
224
247
|
import time
|
|
225
248
|
|
|
226
249
|
if not header or not secret:
|
|
@@ -253,7 +276,7 @@ class KeyMint:
|
|
|
253
276
|
return False
|
|
254
277
|
|
|
255
278
|
# Verify HMAC signature
|
|
256
|
-
signable_content = f"{timestamp_str}.{payload}".encode(
|
|
279
|
+
signable_content = f"{timestamp_str}.{payload}".encode()
|
|
257
280
|
expected_signature = hmac.new(
|
|
258
281
|
secret.encode("utf-8"),
|
|
259
282
|
signable_content,
|
|
@@ -10,14 +10,13 @@ import hashlib
|
|
|
10
10
|
import hmac
|
|
11
11
|
import os
|
|
12
12
|
import platform
|
|
13
|
-
import subprocess
|
|
14
|
-
import uuid
|
|
15
13
|
import re
|
|
14
|
+
import subprocess
|
|
16
15
|
import time
|
|
16
|
+
import uuid
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from typing import Optional
|
|
19
19
|
|
|
20
|
-
|
|
21
20
|
# ─── Garbage Detection ──────────────────────────────────────────────────
|
|
22
21
|
|
|
23
22
|
_GARBAGE_PATTERNS = [
|
|
@@ -54,7 +53,7 @@ def _hash(value: str) -> str:
|
|
|
54
53
|
|
|
55
54
|
# ─── Fingerprint Layers ────────────────────────────────────────────────
|
|
56
55
|
|
|
57
|
-
def _get_bios_uuid() ->
|
|
56
|
+
def _get_bios_uuid() -> str | None:
|
|
58
57
|
"""Layer 1: BIOS / Hardware UUID."""
|
|
59
58
|
system = platform.system()
|
|
60
59
|
try:
|
|
@@ -82,7 +81,7 @@ def _get_bios_uuid() -> Optional[str]:
|
|
|
82
81
|
return None
|
|
83
82
|
|
|
84
83
|
|
|
85
|
-
def _get_os_machine_id() ->
|
|
84
|
+
def _get_os_machine_id() -> str | None:
|
|
86
85
|
"""Layer 2: OS-level persistent machine ID."""
|
|
87
86
|
system = platform.system()
|
|
88
87
|
try:
|
|
@@ -112,11 +111,11 @@ def _get_os_machine_id() -> Optional[str]:
|
|
|
112
111
|
return None
|
|
113
112
|
|
|
114
113
|
|
|
115
|
-
def _get_primary_mac() ->
|
|
114
|
+
def _get_primary_mac() -> str | None:
|
|
116
115
|
"""Layer 3: Primary network interface MAC address."""
|
|
117
116
|
try:
|
|
118
|
-
import socket
|
|
119
117
|
import fcntl
|
|
118
|
+
import socket
|
|
120
119
|
import struct
|
|
121
120
|
except ImportError:
|
|
122
121
|
pass
|
|
@@ -136,7 +135,7 @@ def _get_primary_mac() -> Optional[str]:
|
|
|
136
135
|
|
|
137
136
|
# ─── Public API ─────────────────────────────────────────────────────────
|
|
138
137
|
|
|
139
|
-
def get_machine_id() ->
|
|
138
|
+
def get_machine_id() -> str | None:
|
|
140
139
|
"""
|
|
141
140
|
Best-effort hardware fingerprint. Attempts to read the machine's
|
|
142
141
|
BIOS/System UUID, then falls back through OS-level IDs and network
|
|
@@ -162,7 +161,7 @@ def get_machine_id() -> Optional[str]:
|
|
|
162
161
|
return None
|
|
163
162
|
|
|
164
163
|
|
|
165
|
-
def get_or_create_installation_id(storage_path:
|
|
164
|
+
def get_or_create_installation_id(storage_path: str | None = None) -> str:
|
|
166
165
|
"""
|
|
167
166
|
Returns a guaranteed-unique, guaranteed-stable installation identifier.
|
|
168
167
|
On first call, generates a UUIDv4 seeded with whatever hardware info
|
|
@@ -218,5 +217,5 @@ def generate_session_signature(session_id: str, nonce: str, session_secret: str)
|
|
|
218
217
|
A 64-character hexadecimal signature string.
|
|
219
218
|
"""
|
|
220
219
|
key_bytes = session_secret.encode('utf-8')
|
|
221
|
-
msg_bytes = f"{session_id}:{nonce}".encode(
|
|
220
|
+
msg_bytes = f"{session_id}:{nonce}".encode()
|
|
222
221
|
return hmac.new(key_bytes, msg_bytes, hashlib.sha256).hexdigest()
|
|
@@ -1,25 +1,41 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Any, Dict, List, Optional, TypedDict
|
|
2
|
+
|
|
2
3
|
|
|
3
4
|
class NewCustomer(TypedDict):
|
|
4
5
|
name: str
|
|
5
|
-
email:
|
|
6
|
+
email: str | None
|
|
7
|
+
|
|
8
|
+
class KeyFormat(TypedDict):
|
|
9
|
+
sections: int | None
|
|
10
|
+
sectionLength: int | None
|
|
11
|
+
separator: str | None
|
|
12
|
+
charset: str | None
|
|
13
|
+
prefix: str | None
|
|
14
|
+
suffix: str | None
|
|
15
|
+
case: str | None # 'upper' | 'lower' | 'mixed'
|
|
6
16
|
|
|
7
17
|
class CreateKeyParams(TypedDict):
|
|
8
18
|
productId: str
|
|
9
|
-
maxActivations:
|
|
10
|
-
expiryDate:
|
|
11
|
-
customerId:
|
|
12
|
-
versionId:
|
|
13
|
-
metadata:
|
|
14
|
-
newCustomer:
|
|
15
|
-
allowedHosts:
|
|
19
|
+
maxActivations: str | None
|
|
20
|
+
expiryDate: str | None
|
|
21
|
+
customerId: str | None
|
|
22
|
+
versionId: str | None
|
|
23
|
+
metadata: dict[str, Any] | None
|
|
24
|
+
newCustomer: NewCustomer | None
|
|
25
|
+
allowedHosts: list[str] | None
|
|
26
|
+
format: KeyFormat | None
|
|
27
|
+
amountKeys: str | None
|
|
28
|
+
licenseType: str | None # 'node-locked' | 'floating'
|
|
29
|
+
maxConcurrentSessions: int | None
|
|
30
|
+
heartbeatInterval: int | None
|
|
31
|
+
sessionLeaseDuration: int | None
|
|
16
32
|
|
|
17
33
|
class CreateKeyResponse(TypedDict):
|
|
18
34
|
code: int
|
|
19
35
|
key: str
|
|
20
36
|
|
|
21
37
|
class KeyMintApiError(Exception):
|
|
22
|
-
def __init__(self, message: str, code: int, status:
|
|
38
|
+
def __init__(self, message: str, code: int, status: int | None = None):
|
|
23
39
|
super().__init__(message)
|
|
24
40
|
self.message = message
|
|
25
41
|
self.code = code
|
|
@@ -28,23 +44,25 @@ class KeyMintApiError(Exception):
|
|
|
28
44
|
class ActivateKeyParams(TypedDict):
|
|
29
45
|
productId: str
|
|
30
46
|
licenseKey: str
|
|
31
|
-
hostId:
|
|
32
|
-
deviceTag:
|
|
47
|
+
hostId: str | None
|
|
48
|
+
deviceTag: str | None
|
|
49
|
+
licensee: dict[str, str] | None # { name, email }
|
|
50
|
+
version: str | None
|
|
33
51
|
|
|
34
52
|
class ActivateKeyResponse(TypedDict):
|
|
35
53
|
code: int
|
|
36
54
|
message: str
|
|
37
|
-
licenseeName:
|
|
38
|
-
licenseeEmail:
|
|
39
|
-
metadata:
|
|
40
|
-
versionId:
|
|
41
|
-
version:
|
|
42
|
-
allowedHosts:
|
|
55
|
+
licenseeName: str | None
|
|
56
|
+
licenseeEmail: str | None
|
|
57
|
+
metadata: dict[str, Any] | None
|
|
58
|
+
versionId: str | None
|
|
59
|
+
version: dict[str, Any] | None
|
|
60
|
+
allowedHosts: list[str] | None
|
|
43
61
|
|
|
44
62
|
class DeactivateKeyParams(TypedDict):
|
|
45
63
|
productId: str
|
|
46
64
|
licenseKey: str
|
|
47
|
-
hostId:
|
|
65
|
+
hostId: str | None
|
|
48
66
|
|
|
49
67
|
class DeactivateKeyResponse(TypedDict):
|
|
50
68
|
message: str
|
|
@@ -52,8 +70,8 @@ class DeactivateKeyResponse(TypedDict):
|
|
|
52
70
|
|
|
53
71
|
class DeviceDetails(TypedDict):
|
|
54
72
|
hostId: str
|
|
55
|
-
deviceTag:
|
|
56
|
-
ipAddress:
|
|
73
|
+
deviceTag: str | None
|
|
74
|
+
ipAddress: str | None
|
|
57
75
|
activationTime: str
|
|
58
76
|
|
|
59
77
|
class LicenseDetails(TypedDict):
|
|
@@ -62,18 +80,18 @@ class LicenseDetails(TypedDict):
|
|
|
62
80
|
productId: str
|
|
63
81
|
maxActivations: int
|
|
64
82
|
activations: int
|
|
65
|
-
devices:
|
|
83
|
+
devices: list[DeviceDetails]
|
|
66
84
|
activated: bool
|
|
67
|
-
expirationDate:
|
|
68
|
-
versionId:
|
|
69
|
-
metadata:
|
|
70
|
-
allowedHosts:
|
|
71
|
-
version:
|
|
85
|
+
expirationDate: str | None
|
|
86
|
+
versionId: str | None
|
|
87
|
+
metadata: dict[str, Any] | None
|
|
88
|
+
allowedHosts: list[str] | None
|
|
89
|
+
version: dict[str, Any] | None
|
|
72
90
|
|
|
73
91
|
class CustomerDetails(TypedDict):
|
|
74
92
|
id: str
|
|
75
|
-
name:
|
|
76
|
-
email:
|
|
93
|
+
name: str | None
|
|
94
|
+
email: str | None
|
|
77
95
|
active: bool
|
|
78
96
|
|
|
79
97
|
class GetKeyParams(TypedDict):
|
|
@@ -81,7 +99,7 @@ class GetKeyParams(TypedDict):
|
|
|
81
99
|
licenseKey: str
|
|
82
100
|
|
|
83
101
|
class GetKeyResponse(TypedDict):
|
|
84
|
-
data:
|
|
102
|
+
data: dict[str, Any]
|
|
85
103
|
code: int
|
|
86
104
|
|
|
87
105
|
class BlockKeyParams(TypedDict):
|
|
@@ -104,19 +122,19 @@ class UnblockKeyResponse(TypedDict):
|
|
|
104
122
|
|
|
105
123
|
class CreateCustomerParams(TypedDict):
|
|
106
124
|
name: str
|
|
107
|
-
email: str
|
|
125
|
+
email: str | None
|
|
108
126
|
|
|
109
127
|
class CreateCustomerResponse(TypedDict):
|
|
110
128
|
action: str
|
|
111
129
|
status: bool
|
|
112
130
|
message: str
|
|
113
|
-
data:
|
|
131
|
+
data: dict[str, Any]
|
|
114
132
|
code: int
|
|
115
133
|
|
|
116
134
|
class GetAllCustomersParams(TypedDict):
|
|
117
|
-
page:
|
|
118
|
-
limit:
|
|
119
|
-
email:
|
|
135
|
+
page: int | None
|
|
136
|
+
limit: int | None
|
|
137
|
+
email: str | None
|
|
120
138
|
|
|
121
139
|
class PaginationMeta(TypedDict):
|
|
122
140
|
total: int
|
|
@@ -127,8 +145,8 @@ class PaginationMeta(TypedDict):
|
|
|
127
145
|
class GetAllCustomersResponse(TypedDict):
|
|
128
146
|
action: str
|
|
129
147
|
status: bool
|
|
130
|
-
data:
|
|
131
|
-
meta:
|
|
148
|
+
data: list[dict[str, Any]]
|
|
149
|
+
meta: PaginationMeta | None
|
|
132
150
|
code: int
|
|
133
151
|
|
|
134
152
|
class GetCustomerByIdParams(TypedDict):
|
|
@@ -137,13 +155,13 @@ class GetCustomerByIdParams(TypedDict):
|
|
|
137
155
|
class GetCustomerByIdResponse(TypedDict):
|
|
138
156
|
action: str
|
|
139
157
|
status: bool
|
|
140
|
-
data:
|
|
158
|
+
data: list[dict[str, Any]]
|
|
141
159
|
code: int
|
|
142
160
|
|
|
143
161
|
class UpdateCustomerParams(TypedDict):
|
|
144
|
-
name: str
|
|
145
|
-
email: str
|
|
146
162
|
customerId: str
|
|
163
|
+
name: str | None # Optional: Updated customer name
|
|
164
|
+
email: str | None # Optional: Updated customer email
|
|
147
165
|
|
|
148
166
|
class UpdateCustomerResponse(TypedDict):
|
|
149
167
|
action: str
|
|
@@ -174,27 +192,24 @@ class CustomerLicenseKey(TypedDict):
|
|
|
174
192
|
maxActivations: int
|
|
175
193
|
activations: int
|
|
176
194
|
activated: bool
|
|
177
|
-
expirationDate:
|
|
178
|
-
versionId:
|
|
179
|
-
metadata:
|
|
180
|
-
allowedHosts:
|
|
195
|
+
expirationDate: str | None
|
|
196
|
+
versionId: str | None
|
|
197
|
+
metadata: dict[str, Any] | None
|
|
198
|
+
allowedHosts: list[str] | None
|
|
181
199
|
|
|
182
200
|
class GetCustomerWithKeysParams(TypedDict):
|
|
183
201
|
customerId: str
|
|
184
202
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
status: bool
|
|
188
|
-
data: Dict[str, Any] # Contains customer and licenseKeys
|
|
189
|
-
code: int
|
|
203
|
+
# GetCustomerWithKeys returns a flat LicenseKey[] — no wrapper object.
|
|
204
|
+
# Use List[Dict[str, Any]] for the response type.
|
|
190
205
|
|
|
191
206
|
class FloatingCheckoutParams(TypedDict):
|
|
192
207
|
productId: str
|
|
193
208
|
licenseKey: str
|
|
194
209
|
hostId: str
|
|
195
|
-
deviceTag:
|
|
196
|
-
userIdentifier:
|
|
197
|
-
apiKey:
|
|
210
|
+
deviceTag: str | None
|
|
211
|
+
userIdentifier: str | None
|
|
212
|
+
apiKey: str | None
|
|
198
213
|
|
|
199
214
|
class FloatingCheckoutResponse(TypedDict):
|
|
200
215
|
code: int
|
|
@@ -204,11 +219,11 @@ class FloatingCheckoutResponse(TypedDict):
|
|
|
204
219
|
nextNonce: str
|
|
205
220
|
expiresAt: str
|
|
206
221
|
heartbeatInterval: int
|
|
207
|
-
metadata:
|
|
208
|
-
currentSessions:
|
|
209
|
-
maxSessions:
|
|
210
|
-
licenseeName:
|
|
211
|
-
licenseeEmail:
|
|
222
|
+
metadata: dict[str, Any] | None
|
|
223
|
+
currentSessions: int | None
|
|
224
|
+
maxSessions: int | None
|
|
225
|
+
licenseeName: str | None
|
|
226
|
+
licenseeEmail: str | None
|
|
212
227
|
|
|
213
228
|
class FloatingHeartbeatParams(TypedDict):
|
|
214
229
|
productId: str
|
|
@@ -216,7 +231,7 @@ class FloatingHeartbeatParams(TypedDict):
|
|
|
216
231
|
sessionId: str
|
|
217
232
|
timestamp: Any # rotating nonce (nextNonce) received from previous response
|
|
218
233
|
signature: str
|
|
219
|
-
apiKey:
|
|
234
|
+
apiKey: str | None
|
|
220
235
|
|
|
221
236
|
class FloatingHeartbeatResponse(TypedDict):
|
|
222
237
|
code: int
|
|
@@ -230,8 +245,38 @@ class FloatingCheckinParams(TypedDict):
|
|
|
230
245
|
sessionId: str
|
|
231
246
|
timestamp: Any # rotating nonce (nextNonce) received from previous response
|
|
232
247
|
signature: str
|
|
233
|
-
apiKey:
|
|
248
|
+
apiKey: str | None
|
|
234
249
|
|
|
235
250
|
class FloatingCheckinResponse(TypedDict):
|
|
236
251
|
code: int
|
|
237
252
|
message: str
|
|
253
|
+
|
|
254
|
+
class UpdateKeyParams(TypedDict):
|
|
255
|
+
productId: str
|
|
256
|
+
licenseKey: str
|
|
257
|
+
maxActivations: Any | None # string or number
|
|
258
|
+
expiryDate: str | None
|
|
259
|
+
customerId: str | None
|
|
260
|
+
newCustomer: NewCustomer | None
|
|
261
|
+
metadata: dict[str, Any] | None
|
|
262
|
+
versionId: str | None
|
|
263
|
+
allowedHosts: list[str] | None
|
|
264
|
+
licenseType: str | None # 'node-locked' | 'floating'
|
|
265
|
+
maxConcurrentSessions: int | None
|
|
266
|
+
heartbeatInterval: int | None
|
|
267
|
+
sessionLeaseDuration: int | None
|
|
268
|
+
|
|
269
|
+
class UpdateKeyResponse(TypedDict):
|
|
270
|
+
code: int
|
|
271
|
+
message: str
|
|
272
|
+
affectedCount: int | None
|
|
273
|
+
|
|
274
|
+
class SignKeyParams(TypedDict):
|
|
275
|
+
productId: str
|
|
276
|
+
licenseKey: str
|
|
277
|
+
hostId: str
|
|
278
|
+
ttl: int | None
|
|
279
|
+
|
|
280
|
+
class SignKeyResponse(TypedDict):
|
|
281
|
+
code: int
|
|
282
|
+
file: dict[str, Any] # { signedKey, keyId, publicKeyFingerprint }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: keymint
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.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
|
|
@@ -107,8 +107,10 @@ Keymint provides utilities to uniquely identify machines for node-locking:
|
|
|
107
107
|
| `activate_key` | Activates a license key for a device. |
|
|
108
108
|
| `deactivate_key` | Deactivates a device from a license key. |
|
|
109
109
|
| `get_key` | Retrieves detailed information about a key. |
|
|
110
|
+
| `update_key` | Updates an existing license key. |
|
|
110
111
|
| `block_key` | Blocks a license key. |
|
|
111
112
|
| `unblock_key` | Unblocks a previously blocked license key. |
|
|
113
|
+
| `sign_key` | Signs a key for offline (air-gapped) validation.|
|
|
112
114
|
| `floating_checkout` | Checks out a floating license seat. |
|
|
113
115
|
| `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
|
|
114
116
|
| `floating_checkin` | Checks in a session, releasing the seat. |
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from setuptools import
|
|
1
|
+
from setuptools import find_packages, setup
|
|
2
2
|
|
|
3
3
|
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
4
|
long_description = fh.read()
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="keymint",
|
|
8
|
-
version="2.
|
|
8
|
+
version="2.4.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.",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|