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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keymint
3
- Version: 2.3.1
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
- from .types import *
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) -> GetCustomerWithKeysResponse:
216
+ def get_customer_with_keys(self, params: GetCustomerWithKeysParams) -> List[Dict[str, Any]]:
194
217
  """
195
- Retrieves detailed information about a customer along with their license keys.
218
+ Retrieves license keys belonging to a specific customer.
196
219
  :param params: Parameters containing the customer ID.
197
- :returns: The customer information with associated license keys.
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("utf-8")
279
+ signable_content = f"{timestamp_str}.{payload}".encode()
257
280
  expected_signature = hmac.new(
258
281
  secret.encode("utf-8"),
259
282
  signable_content,
@@ -1,6 +1,6 @@
1
1
  """KeyMint Python SDK version information."""
2
2
 
3
- __version__ = "2.3.0"
3
+ __version__ = "2.4.0"
4
4
  __author__ = "KeyMint"
5
5
  __email__ = "cliff@keymint.dev"
6
6
  __url__ = "https://github.com/keymint-dev/keymint-python"
@@ -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() -> Optional[str]:
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() -> Optional[str]:
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() -> Optional[str]:
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() -> Optional[str]:
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: Optional[str] = None) -> str:
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('utf-8')
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 TypedDict, Optional, List, Dict, Any
1
+ from typing import Any, Dict, List, Optional, TypedDict
2
+
2
3
 
3
4
  class NewCustomer(TypedDict):
4
5
  name: str
5
- email: Optional[str]
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: Optional[str]
10
- expiryDate: Optional[str]
11
- customerId: Optional[str]
12
- versionId: Optional[str]
13
- metadata: Optional[Dict[str, Any]]
14
- newCustomer: Optional[NewCustomer]
15
- allowedHosts: Optional[List[str]]
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: Optional[int] = None):
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: Optional[str]
32
- deviceTag: Optional[str]
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: Optional[str]
38
- licenseeEmail: Optional[str]
39
- metadata: Optional[Dict[str, Any]]
40
- versionId: Optional[str]
41
- version: Optional[Dict[str, Any]]
42
- allowedHosts: Optional[List[str]]
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: Optional[str]
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: Optional[str]
56
- ipAddress: Optional[str]
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: List[DeviceDetails]
83
+ devices: list[DeviceDetails]
66
84
  activated: bool
67
- expirationDate: Optional[str]
68
- versionId: Optional[str]
69
- metadata: Optional[Dict[str, Any]]
70
- allowedHosts: Optional[List[str]]
71
- version: Optional[Dict[str, Any]]
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: Optional[str]
76
- email: Optional[str]
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: Dict[str, Any]
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: Dict[str, Any]
131
+ data: dict[str, Any]
114
132
  code: int
115
133
 
116
134
  class GetAllCustomersParams(TypedDict):
117
- page: Optional[int]
118
- limit: Optional[int]
119
- email: Optional[str]
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: List[Dict[str, Any]]
131
- meta: Optional[PaginationMeta]
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: List[Dict[str, Any]]
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: Optional[str]
178
- versionId: Optional[str]
179
- metadata: Optional[Dict[str, Any]]
180
- allowedHosts: Optional[List[str]]
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
- class GetCustomerWithKeysResponse(TypedDict):
186
- action: str
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: Optional[str]
196
- userIdentifier: Optional[str]
197
- apiKey: Optional[str]
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: Optional[Dict[str, Any]]
208
- currentSessions: Optional[int]
209
- maxSessions: Optional[int]
210
- licenseeName: Optional[str]
211
- licenseeEmail: Optional[str]
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: Optional[str]
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: Optional[str]
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.1
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 setup, find_packages
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.3.1",
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