keymint 2.1.0__tar.gz → 2.2.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,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keymint
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Official Python SDK for KeyMint license management with comprehensive API coverage.
5
5
  Home-page: https://github.com/keymint-dev/keymint-python
6
6
  Author: KeyMint
7
- Author-email: admin@keymint.dev
7
+ Author-email: cliff@keymint.dev
8
8
  Project-URL: Bug Reports, https://github.com/keymint-dev/keymint-python/issues
9
9
  Project-URL: Source, https://github.com/keymint-dev/keymint-python
10
10
  Project-URL: Documentation, https://docs.keymint.dev/sdks/python
@@ -47,15 +47,15 @@ Dynamic: requires-dist
47
47
  Dynamic: requires-python
48
48
  Dynamic: summary
49
49
 
50
- # KeyMint Python SDK
50
+ # Keymint Python
51
51
 
52
- A professional, production-ready SDK for integrating with the KeyMint API in Python. Provides robust access to all major KeyMint features, with type hints and modern error handling.
52
+ A professional, production-ready SDK for integrating with the Keymint API in Python. Provides robust access to all major Keymint features, with type hints and modern error handling.
53
53
 
54
54
  ## Features
55
55
  - **Type hints**: Full type hint support for better IDE integration and code safety.
56
- - **Comprehensive**: Complete API coverage for all KeyMint endpoints.
56
+ - **Comprehensive**: Complete API coverage for all Keymint endpoints.
57
57
  - **Consistent error handling**: All API errors are returned as structured objects or exceptions.
58
- - **Security**: Credentials are always loaded from environment variables.
58
+ - **Machine Identity**: Built-in utilities for hardware fingerprinting and stable installation IDs.
59
59
 
60
60
  ## Installation
61
61
  Add the SDK to your project:
@@ -68,32 +68,36 @@ pip install keymint
68
68
 
69
69
  ```python
70
70
  import os
71
- import keymint
71
+ from keymint import KeyMint, identity
72
72
 
73
- access_token = os.environ.get('KEYMINT_ACCESS_TOKEN')
73
+ api_key = os.environ.get('KEYMINT_API_KEY')
74
74
  product_id = os.environ.get('KEYMINT_PRODUCT_ID')
75
75
 
76
- if not access_token or not product_id:
77
- raise ValueError('Please set the KEYMINT_ACCESS_TOKEN and KEYMINT_PRODUCT_ID environment variables.')
76
+ if not api_key or not product_id:
77
+ raise ValueError('Please set the KEYMINT_API_KEY and KEYMINT_PRODUCT_ID environment variables.')
78
78
 
79
- sdk = keymint.KeyMint(access_token)
79
+ sdk = KeyMint(api_key)
80
+
81
+ # 1. Get a stable, unique ID for this machine
82
+ host_id = identity.get_or_create_installation_id()
83
+
84
+ # 2. Create a key authorized only for this machine
85
+ result = sdk.create_key({
86
+ 'productId': product_id,
87
+ 'allowedHosts': [host_id]
88
+ })
80
89
 
81
- # Example: Create a key
82
- result = sdk.create_key({ 'productId': product_id })
83
90
  if result and 'key' in result:
84
- key = result['key']
85
- # ...
86
- else:
87
- # Handle error
88
- pass
91
+ print(f"Created Key: {result['key']}")
89
92
  ```
90
93
 
91
- ## Error Handling
92
- All SDK methods return a dictionary. If an API call fails, the SDK raises a `KeyMintApiError` exception with `message`, `code`, and `status` attributes.
94
+ ## Machine Identity
95
+ Keymint provides utilities to uniquely identify machines for node-locking:
93
96
 
94
- ## API Methods
97
+ - `identity.get_or_create_installation_id()`: **Recommended.** Generates a stable UUID anchored to hardware and persists it to `~/.keymint/installation-id`.
98
+ - `identity.get_machine_id()`: Generates a SHA-256 fingerprint based on BIOS UUID, OS machine ID, and MAC address.
95
99
 
96
- All methods return a dictionary.
100
+ ## API Methods
97
101
 
98
102
  ### License Key Management
99
103
 
@@ -105,6 +109,9 @@ All methods return a dictionary.
105
109
  | `get_key` | Retrieves detailed information about a key. |
106
110
  | `block_key` | Blocks a license key. |
107
111
  | `unblock_key` | Unblocks a previously blocked license key. |
112
+ | `floating_checkout` | Checks out a floating license seat. |
113
+ | `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
114
+ | `floating_checkin` | Checks in a session, releasing the seat. |
108
115
 
109
116
  ### Customer Management
110
117
 
@@ -118,10 +125,14 @@ All methods return a dictionary.
118
125
  | `toggle_customer_status`| Toggles customer active status. |
119
126
  | `delete_customer` | Permanently deletes a customer and their keys. |
120
127
 
121
- For detailed parameter and response types, see the [KeyMint API docs](https://docs.keymint.dev) or use your IDE's autocomplete.
128
+ ### Webhook Verification
129
+
130
+ | Method | Description |
131
+ |-------------------------|--------------------------------------------------|
132
+ | `verify_webhook_signature`| Verifies the signature of a webhook request payload. |
122
133
 
123
134
  ## License
124
135
  MIT
125
136
 
126
137
  ## Support
127
- For help, see [KeyMint API docs](https://docs.keymint.dev) or open an issue.
138
+ For help, see [Keymint API docs](https://docs.keymint.dev) or open an issue.
@@ -1,12 +1,12 @@
1
- # KeyMint Python SDK
1
+ # Keymint Python
2
2
 
3
- A professional, production-ready SDK for integrating with the KeyMint API in Python. Provides robust access to all major KeyMint features, with type hints and modern error handling.
3
+ A professional, production-ready SDK for integrating with the Keymint API in Python. Provides robust access to all major Keymint features, with type hints and modern error handling.
4
4
 
5
5
  ## Features
6
6
  - **Type hints**: Full type hint support for better IDE integration and code safety.
7
- - **Comprehensive**: Complete API coverage for all KeyMint endpoints.
7
+ - **Comprehensive**: Complete API coverage for all Keymint endpoints.
8
8
  - **Consistent error handling**: All API errors are returned as structured objects or exceptions.
9
- - **Security**: Credentials are always loaded from environment variables.
9
+ - **Machine Identity**: Built-in utilities for hardware fingerprinting and stable installation IDs.
10
10
 
11
11
  ## Installation
12
12
  Add the SDK to your project:
@@ -19,32 +19,36 @@ pip install keymint
19
19
 
20
20
  ```python
21
21
  import os
22
- import keymint
22
+ from keymint import KeyMint, identity
23
23
 
24
- access_token = os.environ.get('KEYMINT_ACCESS_TOKEN')
24
+ api_key = os.environ.get('KEYMINT_API_KEY')
25
25
  product_id = os.environ.get('KEYMINT_PRODUCT_ID')
26
26
 
27
- if not access_token or not product_id:
28
- raise ValueError('Please set the KEYMINT_ACCESS_TOKEN and KEYMINT_PRODUCT_ID environment variables.')
27
+ if not api_key or not product_id:
28
+ raise ValueError('Please set the KEYMINT_API_KEY and KEYMINT_PRODUCT_ID environment variables.')
29
29
 
30
- sdk = keymint.KeyMint(access_token)
30
+ sdk = KeyMint(api_key)
31
+
32
+ # 1. Get a stable, unique ID for this machine
33
+ host_id = identity.get_or_create_installation_id()
34
+
35
+ # 2. Create a key authorized only for this machine
36
+ result = sdk.create_key({
37
+ 'productId': product_id,
38
+ 'allowedHosts': [host_id]
39
+ })
31
40
 
32
- # Example: Create a key
33
- result = sdk.create_key({ 'productId': product_id })
34
41
  if result and 'key' in result:
35
- key = result['key']
36
- # ...
37
- else:
38
- # Handle error
39
- pass
42
+ print(f"Created Key: {result['key']}")
40
43
  ```
41
44
 
42
- ## Error Handling
43
- All SDK methods return a dictionary. If an API call fails, the SDK raises a `KeyMintApiError` exception with `message`, `code`, and `status` attributes.
45
+ ## Machine Identity
46
+ Keymint provides utilities to uniquely identify machines for node-locking:
44
47
 
45
- ## API Methods
48
+ - `identity.get_or_create_installation_id()`: **Recommended.** Generates a stable UUID anchored to hardware and persists it to `~/.keymint/installation-id`.
49
+ - `identity.get_machine_id()`: Generates a SHA-256 fingerprint based on BIOS UUID, OS machine ID, and MAC address.
46
50
 
47
- All methods return a dictionary.
51
+ ## API Methods
48
52
 
49
53
  ### License Key Management
50
54
 
@@ -56,6 +60,9 @@ All methods return a dictionary.
56
60
  | `get_key` | Retrieves detailed information about a key. |
57
61
  | `block_key` | Blocks a license key. |
58
62
  | `unblock_key` | Unblocks a previously blocked license key. |
63
+ | `floating_checkout` | Checks out a floating license seat. |
64
+ | `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
65
+ | `floating_checkin` | Checks in a session, releasing the seat. |
59
66
 
60
67
  ### Customer Management
61
68
 
@@ -69,10 +76,14 @@ All methods return a dictionary.
69
76
  | `toggle_customer_status`| Toggles customer active status. |
70
77
  | `delete_customer` | Permanently deletes a customer and their keys. |
71
78
 
72
- For detailed parameter and response types, see the [KeyMint API docs](https://docs.keymint.dev) or use your IDE's autocomplete.
79
+ ### Webhook Verification
80
+
81
+ | Method | Description |
82
+ |-------------------------|--------------------------------------------------|
83
+ | `verify_webhook_signature`| Verifies the signature of a webhook request payload. |
73
84
 
74
85
  ## License
75
86
  MIT
76
87
 
77
88
  ## Support
78
- For help, see [KeyMint API docs](https://docs.keymint.dev) or open an issue.
89
+ For help, see [Keymint API docs](https://docs.keymint.dev) or open an issue.
@@ -5,14 +5,14 @@ from ._version import __version__
5
5
  __all__ = ['KeyMint', 'KeyMintApiError', '__version__']
6
6
 
7
7
  class KeyMint:
8
- def __init__(self, access_token: str, base_url: str = "https://api.keymint.dev"):
9
- if not access_token:
10
- raise ValueError("Access token is required to initialize the SDK.")
8
+ def __init__(self, api_key: str, base_url: str = "https://api.keymint.dev"):
9
+ if not api_key:
10
+ raise ValueError("API key is required to initialize the SDK.")
11
11
 
12
- self.access_token = access_token
12
+ self.api_key = api_key
13
13
  self.base_url = base_url
14
14
  self.headers = {
15
- 'Authorization': f'Bearer {self.access_token}',
15
+ 'Authorization': f'Bearer {self.api_key}',
16
16
  'Content-Type': 'application/json'
17
17
  }
18
18
 
@@ -79,6 +79,30 @@ class KeyMint:
79
79
  """
80
80
  return self._handle_request('POST', '/key/deactivate', params)
81
81
 
82
+ def floating_checkout(self, params: FloatingCheckoutParams) -> FloatingCheckoutResponse:
83
+ """
84
+ Checks out a floating license seat.
85
+ :param params: Parameters for checking out the license.
86
+ :returns: The checkout response containing sessionId and sessionSecret.
87
+ """
88
+ return self._handle_request('POST', '/key/checkout', params)
89
+
90
+ def floating_heartbeat(self, params: FloatingHeartbeatParams) -> FloatingHeartbeatResponse:
91
+ """
92
+ Sends a heartbeat to keep a floating license session alive.
93
+ :param params: Parameters for the heartbeat (includes rotating signature).
94
+ :returns: The heartbeat response with extended expiry and new nonce.
95
+ """
96
+ return self._handle_request('POST', '/key/heartbeat', params)
97
+
98
+ def floating_checkin(self, params: FloatingCheckinParams) -> FloatingCheckinResponse:
99
+ """
100
+ Checks in a floating license session, releasing the seat.
101
+ :param params: Parameters for checking in the license (includes rotating signature).
102
+ :returns: The checkin confirmation.
103
+ """
104
+ return self._handle_request('POST', '/key/checkin', params)
105
+
82
106
  def get_key(self, params: GetKeyParams) -> GetKeyResponse:
83
107
  """
84
108
  Retrieves detailed information about a specific license key.
@@ -169,3 +193,60 @@ class KeyMint:
169
193
  query_params = {'customerId': params['customerId']}
170
194
  return self._handle_request('POST', '/customer/disable', params=None, query_params=query_params)
171
195
 
196
+ @staticmethod
197
+ def verify_webhook_signature(payload: str, header: str, secret: str, tolerance_seconds: int = 300) -> bool:
198
+ """
199
+ Verifies a webhook payload signature received from Keymint.
200
+ :param payload: The raw request body as a string.
201
+ :param header: The value of the "Keymint-Signature" header.
202
+ :param secret: The webhook endpoint's signing secret.
203
+ :param tolerance_seconds: Time tolerance in seconds to prevent replay attacks. Defaults to 300 (5 minutes).
204
+ :returns: True if the signature is valid, False otherwise.
205
+ """
206
+ import hmac
207
+ import hashlib
208
+ import time
209
+
210
+ if not header or not secret:
211
+ return False
212
+
213
+ try:
214
+ # Parse header (e.g. t=1719374021,v1=signature)
215
+ timestamp_str = ""
216
+ signature = ""
217
+ parts = header.split(",")
218
+ for part in parts:
219
+ kv = part.strip().split("=", 1)
220
+ if len(kv) == 2:
221
+ if kv[0] == "t":
222
+ timestamp_str = kv[1]
223
+ elif kv[0] == "v1":
224
+ signature = kv[1]
225
+
226
+ if not timestamp_str or not signature:
227
+ return False
228
+
229
+ # Check timestamp validity
230
+ try:
231
+ timestamp_int = int(timestamp_str)
232
+ except ValueError:
233
+ return False
234
+
235
+ now = int(time.time())
236
+ if abs(now - timestamp_int) > tolerance_seconds:
237
+ return False
238
+
239
+ # Verify HMAC signature
240
+ signable_content = f"{timestamp_str}.{payload}".encode("utf-8")
241
+ expected_signature = hmac.new(
242
+ secret.encode("utf-8"),
243
+ signable_content,
244
+ hashlib.sha256
245
+ ).hexdigest()
246
+
247
+ # Constant-time comparison to prevent timing attacks
248
+ return hmac.compare_digest(expected_signature, signature)
249
+ except Exception:
250
+ return False
251
+
252
+
@@ -1,6 +1,6 @@
1
1
  """KeyMint Python SDK version information."""
2
2
 
3
- __version__ = "2.0.1"
3
+ __version__ = "2.2.0"
4
4
  __author__ = "KeyMint"
5
- __email__ = "admin@keymint.dev"
5
+ __email__ = "cliff@keymint.dev"
6
6
  __url__ = "https://github.com/keymint-dev/keymint-python"
@@ -7,6 +7,7 @@ Provides two methods for identifying machines:
7
7
  """
8
8
 
9
9
  import hashlib
10
+ import hmac
10
11
  import os
11
12
  import platform
12
13
  import subprocess
@@ -195,7 +196,27 @@ def get_or_create_installation_id(storage_path: Optional[str] = None) -> str:
195
196
  composite_id = f'{new_uuid}:{hardware_anchor}:{int(time.time() * 1000)}'
196
197
 
197
198
  # 3. Persist it
198
- file_path.parent.mkdir(parents=True, exist_ok=True)
199
- file_path.write_text(composite_id, encoding='utf-8')
199
+ try:
200
+ file_path.parent.mkdir(parents=True, exist_ok=True)
201
+ file_path.write_text(composite_id, encoding='utf-8')
202
+ except Exception:
203
+ pass # Silently fallback to in-memory ID if filesystem is read-only
200
204
 
201
205
  return hashlib.sha256(composite_id.encode('utf-8')).hexdigest()
206
+
207
+ def generate_session_signature(session_id: str, nonce: str, session_secret: str) -> str:
208
+ """
209
+ Generates a cryptographic signature for a heartbeat or checkin request
210
+ using the session_secret and the rotating nextNonce (passed as the timestamp).
211
+
212
+ Args:
213
+ session_id: The 22-character unique session ID.
214
+ nonce: The rotating nonce string (nextNonce) received from the previous response.
215
+ session_secret: The temporary session secret key received during checkout.
216
+
217
+ Returns:
218
+ A 64-character hexadecimal signature string.
219
+ """
220
+ key_bytes = session_secret.encode('utf-8')
221
+ msg_bytes = f"{session_id}:{nonce}".encode('utf-8')
222
+ return hmac.new(key_bytes, msg_bytes, hashlib.sha256).hexdigest()
@@ -187,3 +187,51 @@ class GetCustomerWithKeysResponse(TypedDict):
187
187
  status: bool
188
188
  data: Dict[str, Any] # Contains customer and licenseKeys
189
189
  code: int
190
+
191
+ class FloatingCheckoutParams(TypedDict):
192
+ productId: str
193
+ licenseKey: str
194
+ hostId: str
195
+ deviceTag: Optional[str]
196
+ userIdentifier: Optional[str]
197
+ apiKey: Optional[str]
198
+
199
+ class FloatingCheckoutResponse(TypedDict):
200
+ code: int
201
+ message: str
202
+ sessionId: str
203
+ sessionSecret: str
204
+ nextNonce: str
205
+ expiresAt: str
206
+ heartbeatInterval: int
207
+ metadata: Optional[Dict[str, Any]]
208
+ currentSessions: Optional[int]
209
+ maxSessions: Optional[int]
210
+ licenseeName: Optional[str]
211
+ licenseeEmail: Optional[str]
212
+
213
+ class FloatingHeartbeatParams(TypedDict):
214
+ productId: str
215
+ licenseKey: str
216
+ sessionId: str
217
+ timestamp: Any # rotating nonce (nextNonce) received from previous response
218
+ signature: str
219
+ apiKey: Optional[str]
220
+
221
+ class FloatingHeartbeatResponse(TypedDict):
222
+ code: int
223
+ message: str
224
+ expiresAt: str
225
+ nextNonce: str
226
+
227
+ class FloatingCheckinParams(TypedDict):
228
+ productId: str
229
+ licenseKey: str
230
+ sessionId: str
231
+ timestamp: Any # rotating nonce (nextNonce) received from previous response
232
+ signature: str
233
+ apiKey: Optional[str]
234
+
235
+ class FloatingCheckinResponse(TypedDict):
236
+ code: int
237
+ message: str
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keymint
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Official Python SDK for KeyMint license management with comprehensive API coverage.
5
5
  Home-page: https://github.com/keymint-dev/keymint-python
6
6
  Author: KeyMint
7
- Author-email: admin@keymint.dev
7
+ Author-email: cliff@keymint.dev
8
8
  Project-URL: Bug Reports, https://github.com/keymint-dev/keymint-python/issues
9
9
  Project-URL: Source, https://github.com/keymint-dev/keymint-python
10
10
  Project-URL: Documentation, https://docs.keymint.dev/sdks/python
@@ -47,15 +47,15 @@ Dynamic: requires-dist
47
47
  Dynamic: requires-python
48
48
  Dynamic: summary
49
49
 
50
- # KeyMint Python SDK
50
+ # Keymint Python
51
51
 
52
- A professional, production-ready SDK for integrating with the KeyMint API in Python. Provides robust access to all major KeyMint features, with type hints and modern error handling.
52
+ A professional, production-ready SDK for integrating with the Keymint API in Python. Provides robust access to all major Keymint features, with type hints and modern error handling.
53
53
 
54
54
  ## Features
55
55
  - **Type hints**: Full type hint support for better IDE integration and code safety.
56
- - **Comprehensive**: Complete API coverage for all KeyMint endpoints.
56
+ - **Comprehensive**: Complete API coverage for all Keymint endpoints.
57
57
  - **Consistent error handling**: All API errors are returned as structured objects or exceptions.
58
- - **Security**: Credentials are always loaded from environment variables.
58
+ - **Machine Identity**: Built-in utilities for hardware fingerprinting and stable installation IDs.
59
59
 
60
60
  ## Installation
61
61
  Add the SDK to your project:
@@ -68,32 +68,36 @@ pip install keymint
68
68
 
69
69
  ```python
70
70
  import os
71
- import keymint
71
+ from keymint import KeyMint, identity
72
72
 
73
- access_token = os.environ.get('KEYMINT_ACCESS_TOKEN')
73
+ api_key = os.environ.get('KEYMINT_API_KEY')
74
74
  product_id = os.environ.get('KEYMINT_PRODUCT_ID')
75
75
 
76
- if not access_token or not product_id:
77
- raise ValueError('Please set the KEYMINT_ACCESS_TOKEN and KEYMINT_PRODUCT_ID environment variables.')
76
+ if not api_key or not product_id:
77
+ raise ValueError('Please set the KEYMINT_API_KEY and KEYMINT_PRODUCT_ID environment variables.')
78
78
 
79
- sdk = keymint.KeyMint(access_token)
79
+ sdk = KeyMint(api_key)
80
+
81
+ # 1. Get a stable, unique ID for this machine
82
+ host_id = identity.get_or_create_installation_id()
83
+
84
+ # 2. Create a key authorized only for this machine
85
+ result = sdk.create_key({
86
+ 'productId': product_id,
87
+ 'allowedHosts': [host_id]
88
+ })
80
89
 
81
- # Example: Create a key
82
- result = sdk.create_key({ 'productId': product_id })
83
90
  if result and 'key' in result:
84
- key = result['key']
85
- # ...
86
- else:
87
- # Handle error
88
- pass
91
+ print(f"Created Key: {result['key']}")
89
92
  ```
90
93
 
91
- ## Error Handling
92
- All SDK methods return a dictionary. If an API call fails, the SDK raises a `KeyMintApiError` exception with `message`, `code`, and `status` attributes.
94
+ ## Machine Identity
95
+ Keymint provides utilities to uniquely identify machines for node-locking:
93
96
 
94
- ## API Methods
97
+ - `identity.get_or_create_installation_id()`: **Recommended.** Generates a stable UUID anchored to hardware and persists it to `~/.keymint/installation-id`.
98
+ - `identity.get_machine_id()`: Generates a SHA-256 fingerprint based on BIOS UUID, OS machine ID, and MAC address.
95
99
 
96
- All methods return a dictionary.
100
+ ## API Methods
97
101
 
98
102
  ### License Key Management
99
103
 
@@ -105,6 +109,9 @@ All methods return a dictionary.
105
109
  | `get_key` | Retrieves detailed information about a key. |
106
110
  | `block_key` | Blocks a license key. |
107
111
  | `unblock_key` | Unblocks a previously blocked license key. |
112
+ | `floating_checkout` | Checks out a floating license seat. |
113
+ | `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
114
+ | `floating_checkin` | Checks in a session, releasing the seat. |
108
115
 
109
116
  ### Customer Management
110
117
 
@@ -118,10 +125,14 @@ All methods return a dictionary.
118
125
  | `toggle_customer_status`| Toggles customer active status. |
119
126
  | `delete_customer` | Permanently deletes a customer and their keys. |
120
127
 
121
- For detailed parameter and response types, see the [KeyMint API docs](https://docs.keymint.dev) or use your IDE's autocomplete.
128
+ ### Webhook Verification
129
+
130
+ | Method | Description |
131
+ |-------------------------|--------------------------------------------------|
132
+ | `verify_webhook_signature`| Verifies the signature of a webhook request payload. |
122
133
 
123
134
  ## License
124
135
  MIT
125
136
 
126
137
  ## Support
127
- For help, see [KeyMint API docs](https://docs.keymint.dev) or open an issue.
138
+ For help, see [Keymint API docs](https://docs.keymint.dev) or open an issue.
@@ -5,9 +5,9 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="keymint",
8
- version="2.1.0",
8
+ version="2.2.0",
9
9
  author="KeyMint",
10
- author_email="admin@keymint.dev",
10
+ author_email="cliff@keymint.dev",
11
11
  description="Official Python SDK for KeyMint license management with comprehensive API coverage.",
12
12
  long_description=long_description,
13
13
  long_description_content_type="text/markdown",
File without changes
File without changes
File without changes