keymint 2.5.0__tar.gz → 2.6.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.5.0/keymint.egg-info → keymint-2.6.0}/PKG-INFO +25 -1
- {keymint-2.5.0 → keymint-2.6.0}/README.md +24 -0
- {keymint-2.5.0 → keymint-2.6.0}/keymint/__init__.py +6 -1
- {keymint-2.5.0 → keymint-2.6.0}/keymint/_version.py +1 -1
- {keymint-2.5.0 → keymint-2.6.0}/keymint/types.py +7 -1
- {keymint-2.5.0 → keymint-2.6.0/keymint.egg-info}/PKG-INFO +25 -1
- {keymint-2.5.0 → keymint-2.6.0}/setup.py +1 -1
- {keymint-2.5.0 → keymint-2.6.0}/tests/test_contracts.py +33 -0
- {keymint-2.5.0 → keymint-2.6.0}/LICENSE +0 -0
- {keymint-2.5.0 → keymint-2.6.0}/MANIFEST.in +0 -0
- {keymint-2.5.0 → keymint-2.6.0}/keymint/identity.py +0 -0
- {keymint-2.5.0 → keymint-2.6.0}/keymint.egg-info/SOURCES.txt +0 -0
- {keymint-2.5.0 → keymint-2.6.0}/keymint.egg-info/dependency_links.txt +0 -0
- {keymint-2.5.0 → keymint-2.6.0}/keymint.egg-info/requires.txt +0 -0
- {keymint-2.5.0 → keymint-2.6.0}/keymint.egg-info/top_level.txt +0 -0
- {keymint-2.5.0 → keymint-2.6.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.6.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
|
|
@@ -87,6 +87,30 @@ if result and 'key' in result:
|
|
|
87
87
|
print(f"Created Key: {result['key']}")
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
+
## License Expiry
|
|
91
|
+
|
|
92
|
+
`activate_key` and `floating_checkout` return `expirationDate`: the license expiry as an ISO 8601 string (e.g. `"2027-03-01T00:00:00.000Z"`), or `None` when the license never expires. On `floating_checkout`, `expiresAt` is the seat lease, not the license expiry.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from datetime import datetime, timezone
|
|
96
|
+
import math
|
|
97
|
+
|
|
98
|
+
result = sdk.activate_key({'productId': product_id, 'licenseKey': license_key, 'hostId': host_id})
|
|
99
|
+
|
|
100
|
+
expiration = result.get('expirationDate')
|
|
101
|
+
if expiration is None:
|
|
102
|
+
print('License never expires')
|
|
103
|
+
else:
|
|
104
|
+
expires_at = datetime.fromisoformat(expiration.replace('Z', '+00:00'))
|
|
105
|
+
seconds_left = (expires_at - datetime.now(timezone.utc)).total_seconds()
|
|
106
|
+
days_left = max(0, math.ceil(seconds_left / 86400))
|
|
107
|
+
print(f'License expires {expires_at:%Y-%m-%d} ({days_left} days left)')
|
|
108
|
+
|
|
109
|
+
# Store the last value you received (e.g. in your app's settings file).
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Store the last `expirationDate` you received. An expired license fails activation with the same generic invalid-key error as a wrong key, so the stored date is how your app can tell the user the license expired on that date instead of calling the key invalid.
|
|
113
|
+
|
|
90
114
|
## Machine Identity
|
|
91
115
|
Keymint provides utilities to uniquely identify machines for node-locking:
|
|
92
116
|
|
|
@@ -42,6 +42,30 @@ if result and 'key' in result:
|
|
|
42
42
|
print(f"Created Key: {result['key']}")
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
## License Expiry
|
|
46
|
+
|
|
47
|
+
`activate_key` and `floating_checkout` return `expirationDate`: the license expiry as an ISO 8601 string (e.g. `"2027-03-01T00:00:00.000Z"`), or `None` when the license never expires. On `floating_checkout`, `expiresAt` is the seat lease, not the license expiry.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from datetime import datetime, timezone
|
|
51
|
+
import math
|
|
52
|
+
|
|
53
|
+
result = sdk.activate_key({'productId': product_id, 'licenseKey': license_key, 'hostId': host_id})
|
|
54
|
+
|
|
55
|
+
expiration = result.get('expirationDate')
|
|
56
|
+
if expiration is None:
|
|
57
|
+
print('License never expires')
|
|
58
|
+
else:
|
|
59
|
+
expires_at = datetime.fromisoformat(expiration.replace('Z', '+00:00'))
|
|
60
|
+
seconds_left = (expires_at - datetime.now(timezone.utc)).total_seconds()
|
|
61
|
+
days_left = max(0, math.ceil(seconds_left / 86400))
|
|
62
|
+
print(f'License expires {expires_at:%Y-%m-%d} ({days_left} days left)')
|
|
63
|
+
|
|
64
|
+
# Store the last value you received (e.g. in your app's settings file).
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Store the last `expirationDate` you received. An expired license fails activation with the same generic invalid-key error as a wrong key, so the stored date is how your app can tell the user the license expired on that date instead of calling the key invalid.
|
|
68
|
+
|
|
45
69
|
## Machine Identity
|
|
46
70
|
Keymint provides utilities to uniquely identify machines for node-locking:
|
|
47
71
|
|
|
@@ -79,7 +79,10 @@ class KeyMint:
|
|
|
79
79
|
|
|
80
80
|
:param params: Activation parameters including productId, licenseKey, and optional hostId.
|
|
81
81
|
:param idempotency_key: Optional unique identifier to ensure request idempotency.
|
|
82
|
-
:return: Activation success message and licensee information.
|
|
82
|
+
:return: Activation success message and licensee information. Includes
|
|
83
|
+
``expirationDate``: the license expiry as an ISO 8601 string, or ``None``
|
|
84
|
+
when the license never expires. An expired license fails activation with
|
|
85
|
+
the same generic invalid-key error, so store the last value you received.
|
|
83
86
|
"""
|
|
84
87
|
return self._handle_request('POST', '/key/activate', params, idempotency_key=idempotency_key)
|
|
85
88
|
|
|
@@ -98,6 +101,8 @@ class KeyMint:
|
|
|
98
101
|
:param params: Parameters for checking out the license.
|
|
99
102
|
:param idempotency_key: Optional unique identifier to ensure request idempotency.
|
|
100
103
|
:returns: The checkout response containing sessionId and sessionSecret.
|
|
104
|
+
``expiresAt`` is the seat lease; ``expirationDate`` is the license expiry
|
|
105
|
+
as an ISO 8601 string, or ``None`` when the license never expires.
|
|
101
106
|
"""
|
|
102
107
|
return self._handle_request('POST', '/key/checkout', params, idempotency_key=idempotency_key)
|
|
103
108
|
|
|
@@ -59,6 +59,9 @@ class ActivateKeyResponse(TypedDict):
|
|
|
59
59
|
versionId: str | None
|
|
60
60
|
version: dict[str, Any] | None
|
|
61
61
|
allowedHosts: list[str] | None
|
|
62
|
+
# License expiry as an ISO 8601 string (e.g. "2027-03-01T00:00:00.000Z"),
|
|
63
|
+
# or None when the license never expires.
|
|
64
|
+
expirationDate: str | None
|
|
62
65
|
|
|
63
66
|
class DeactivateKeyParams(TypedDict):
|
|
64
67
|
productId: str
|
|
@@ -223,9 +226,12 @@ class FloatingCheckoutResponse(TypedDict):
|
|
|
223
226
|
sessionId: str
|
|
224
227
|
sessionSecret: str
|
|
225
228
|
nextNonce: str
|
|
226
|
-
expiresAt: str
|
|
229
|
+
expiresAt: str # Floating seat lease expiry (not the license expiry)
|
|
227
230
|
heartbeatInterval: int
|
|
228
231
|
metadata: dict[str, Any] | None
|
|
232
|
+
# License expiry as an ISO 8601 string, or None when the license never
|
|
233
|
+
# expires. Separate from ``expiresAt``, which is the seat lease.
|
|
234
|
+
expirationDate: str | None
|
|
229
235
|
currentSessions: int | None
|
|
230
236
|
maxSessions: int | None
|
|
231
237
|
licenseeName: str | None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: keymint
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.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
|
|
@@ -87,6 +87,30 @@ if result and 'key' in result:
|
|
|
87
87
|
print(f"Created Key: {result['key']}")
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
+
## License Expiry
|
|
91
|
+
|
|
92
|
+
`activate_key` and `floating_checkout` return `expirationDate`: the license expiry as an ISO 8601 string (e.g. `"2027-03-01T00:00:00.000Z"`), or `None` when the license never expires. On `floating_checkout`, `expiresAt` is the seat lease, not the license expiry.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from datetime import datetime, timezone
|
|
96
|
+
import math
|
|
97
|
+
|
|
98
|
+
result = sdk.activate_key({'productId': product_id, 'licenseKey': license_key, 'hostId': host_id})
|
|
99
|
+
|
|
100
|
+
expiration = result.get('expirationDate')
|
|
101
|
+
if expiration is None:
|
|
102
|
+
print('License never expires')
|
|
103
|
+
else:
|
|
104
|
+
expires_at = datetime.fromisoformat(expiration.replace('Z', '+00:00'))
|
|
105
|
+
seconds_left = (expires_at - datetime.now(timezone.utc)).total_seconds()
|
|
106
|
+
days_left = max(0, math.ceil(seconds_left / 86400))
|
|
107
|
+
print(f'License expires {expires_at:%Y-%m-%d} ({days_left} days left)')
|
|
108
|
+
|
|
109
|
+
# Store the last value you received (e.g. in your app's settings file).
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Store the last `expirationDate` you received. An expired license fails activation with the same generic invalid-key error as a wrong key, so the stored date is how your app can tell the user the license expired on that date instead of calling the key invalid.
|
|
113
|
+
|
|
90
114
|
## Machine Identity
|
|
91
115
|
Keymint provides utilities to uniquely identify machines for node-locking:
|
|
92
116
|
|
|
@@ -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.6.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.",
|
|
@@ -50,3 +50,36 @@ def test_nested_error_preserves_server_message(mock_post):
|
|
|
50
50
|
|
|
51
51
|
with pytest.raises(KeyMintApiError, match="Customer email already exists in team"):
|
|
52
52
|
KeyMint("admin_test").create_key({"productId": "product_123"})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.parametrize("expiration_date", ["2027-03-01T00:00:00.000Z", None])
|
|
56
|
+
@patch("keymint.requests.post")
|
|
57
|
+
def test_activate_key_returns_expiration_date(mock_post, expiration_date):
|
|
58
|
+
mock_post.return_value = response({
|
|
59
|
+
"code": 0, "message": "License valid", "expirationDate": expiration_date,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
result = KeyMint("client_test").activate_key({"productId": "product_123", "licenseKey": "license_123"})
|
|
63
|
+
|
|
64
|
+
assert mock_post.call_args.args[0].endswith("/key/activate")
|
|
65
|
+
assert "expirationDate" in result
|
|
66
|
+
assert result["expirationDate"] == expiration_date
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.mark.parametrize("expiration_date", ["2027-03-01T00:00:00.000Z", None])
|
|
70
|
+
@patch("keymint.requests.post")
|
|
71
|
+
def test_floating_checkout_returns_expiration_date_separate_from_lease(mock_post, expiration_date):
|
|
72
|
+
mock_post.return_value = response({
|
|
73
|
+
"code": 0, "message": "Checked out", "sessionId": "session_123",
|
|
74
|
+
"sessionSecret": "secret_123", "nextNonce": "nonce_123",
|
|
75
|
+
"expiresAt": "2026-10-26T12:05:00.000Z", "heartbeatInterval": 60,
|
|
76
|
+
"metadata": None, "expirationDate": expiration_date,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
result = KeyMint("client_test").floating_checkout({
|
|
80
|
+
"productId": "product_123", "licenseKey": "license_123", "hostId": "host_123",
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
assert mock_post.call_args.args[0].endswith("/key/checkout")
|
|
84
|
+
assert result["expirationDate"] == expiration_date
|
|
85
|
+
assert result["expiresAt"] == "2026-10-26T12:05:00.000Z"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|