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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keymint
3
- Version: 2.5.0
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
 
@@ -1,6 +1,6 @@
1
1
  """KeyMint Python SDK version information."""
2
2
 
3
- __version__ = "2.5.0"
3
+ __version__ = "2.6.0"
4
4
  __author__ = "KeyMint"
5
5
  __email__ = "cliff@keymint.dev"
6
6
  __url__ = "https://github.com/keymint-dev/keymint-python"
@@ -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.5.0
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.5.0",
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