purem 2.1.5__tar.gz → 3.0.1__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.
- {purem-2.1.5/purem.egg-info → purem-3.0.1}/PKG-INFO +1 -2
- purem-3.0.1/VERSION +1 -0
- purem-3.0.1/purem/core.py +339 -0
- {purem-2.1.5 → purem-3.0.1}/purem/env_config.py +1 -1
- {purem-2.1.5 → purem-3.0.1}/purem/file_structure.py +1 -1
- purem-3.0.1/purem/loader.py +102 -0
- {purem-2.1.5 → purem-3.0.1}/purem/logger.py +1 -1
- {purem-2.1.5 → purem-3.0.1}/purem/utils.py +1 -1
- {purem-2.1.5 → purem-3.0.1/purem.egg-info}/PKG-INFO +1 -2
- {purem-2.1.5 → purem-3.0.1}/purem.egg-info/requires.txt +0 -1
- {purem-2.1.5 → purem-3.0.1}/pyproject.toml +1 -2
- purem-2.1.5/VERSION +0 -1
- purem-2.1.5/purem/core.py +0 -252
- purem-2.1.5/purem/loader.py +0 -43
- {purem-2.1.5 → purem-3.0.1}/LICENSE +0 -0
- {purem-2.1.5 → purem-3.0.1}/MANIFEST.in +0 -0
- {purem-2.1.5 → purem-3.0.1}/README.md +0 -0
- {purem-2.1.5 → purem-3.0.1}/README.rst +0 -0
- {purem-2.1.5 → purem-3.0.1}/purem/__init__.py +0 -0
- {purem-2.1.5 → purem-3.0.1}/purem.egg-info/SOURCES.txt +0 -0
- {purem-2.1.5 → purem-3.0.1}/purem.egg-info/dependency_links.txt +0 -0
- {purem-2.1.5 → purem-3.0.1}/purem.egg-info/top_level.txt +0 -0
- {purem-2.1.5 → purem-3.0.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: purem
|
3
|
-
Version:
|
3
|
+
Version: 3.0.1
|
4
4
|
Summary: The official high-performance mapping function for mixed-type arrays powered by Work TIF Ltd.
|
5
5
|
Author-email: Raman Marozau <raman@worktif.com>
|
6
6
|
License-Expression: BUSL-1.1
|
@@ -26,7 +26,6 @@ Description-Content-Type: text/x-rst
|
|
26
26
|
License-File: LICENSE
|
27
27
|
Requires-Dist: numpy~=2.1.3
|
28
28
|
Requires-Dist: pytest~=8.3.5
|
29
|
-
Requires-Dist: torch~=2.6.0
|
30
29
|
Requires-Dist: numba~=0.61.0
|
31
30
|
Requires-Dist: certifi~=2025.1.31
|
32
31
|
Requires-Dist: pydantic~=2.10.6
|
purem-3.0.1/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.0.1
|
@@ -0,0 +1,339 @@
|
|
1
|
+
"""
|
2
|
+
Business Source License 1.1
|
3
|
+
|
4
|
+
Copyright (C) 2025 Raman Marozau, raman@worktif.com
|
5
|
+
Use of this software is governed by the Business Source License included in the LICENSE file and at www.mariadb.com/bsl11.
|
6
|
+
|
7
|
+
Change Date: Never
|
8
|
+
On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
|
9
|
+
Additional Use Grant: Free for personal and non-commercial research use only.
|
10
|
+
|
11
|
+
SPDX-License-Identifier: BUSL-1.1
|
12
|
+
"""
|
13
|
+
|
14
|
+
import base64
|
15
|
+
import ctypes
|
16
|
+
import json
|
17
|
+
import os
|
18
|
+
import shutil
|
19
|
+
import ssl
|
20
|
+
import urllib.request
|
21
|
+
import zipfile
|
22
|
+
from typing import List, Optional, Dict
|
23
|
+
|
24
|
+
import certifi as certifi
|
25
|
+
import numpy as np
|
26
|
+
from numpy import ndarray
|
27
|
+
|
28
|
+
from purem.env_config import load_env_config
|
29
|
+
from purem.file_structure import FileStructure
|
30
|
+
from purem.loader import Loader
|
31
|
+
from purem.logger import Logger
|
32
|
+
from purem.utils import _compute_shifted_jit
|
33
|
+
|
34
|
+
|
35
|
+
class Purem:
|
36
|
+
"""
|
37
|
+
Represents the functionality of the Purem system, including initialization, configuration,
|
38
|
+
and operations using a shared library binary. The class provides methods for setting up
|
39
|
+
license keys, managing binary paths, loading configurations from a Content Delivery Network (CDN),
|
40
|
+
and executing specific computations such as the softmax function. It handles both high-level
|
41
|
+
configuration details and low-level interactions with binary libraries.
|
42
|
+
|
43
|
+
:ivar _license_key: The license key used to initialize the system.
|
44
|
+
:type _license_key: Optional[str]
|
45
|
+
:ivar _lib: Represents the dynamically loaded shared library for performing operations.
|
46
|
+
:type _lib: Optional[ctypes.CDLL]
|
47
|
+
:ivar _download_binary_url: URL for downloading the binary configuration.
|
48
|
+
:type _download_binary_url: Optional[str]
|
49
|
+
:ivar _ctx: SSL context for establishing secure connections.
|
50
|
+
:type _ctx: ssl.SSLContext
|
51
|
+
:ivar _file_structure: The handler for managing binary-related paths and file structure.
|
52
|
+
:type _file_structure: FileStructure
|
53
|
+
:ivar _binary_path: Absolute path of the binary file used for execution.
|
54
|
+
:type _binary_path: str
|
55
|
+
:ivar _binary_project_root_path: Path to the binary located in the project root.
|
56
|
+
:type _binary_project_root_path: str
|
57
|
+
:ivar _binary_archive_path: Path where binary archives are stored.
|
58
|
+
:type _binary_archive_path: str
|
59
|
+
:ivar _binary_archive_path_tmp: Temporary path for binary archive operations.
|
60
|
+
:type _binary_archive_path_tmp: str
|
61
|
+
:ivar _env: Object holding environment configurations and variables.
|
62
|
+
:type _env: Any
|
63
|
+
:ivar _config_url: URL for retrieving Purem's configuration from a remote server.
|
64
|
+
:type _config_url: str
|
65
|
+
:ivar _loader: Loader instance for displaying runtime messages during setup and initialization.
|
66
|
+
:type _loader: Loader
|
67
|
+
:ivar _log: Logger instance for recording system messages and error details.
|
68
|
+
:type _log: Logger
|
69
|
+
"""
|
70
|
+
|
71
|
+
def __init__(self, licenced_key: Optional[str] = None):
|
72
|
+
"""
|
73
|
+
Represents the initialization and configuration of an environment, license key,
|
74
|
+
and file structure required for the system's binary operations.
|
75
|
+
|
76
|
+
:param licenced_key: Optional license key string for initializing the system.
|
77
|
+
:type licenced_key: Optional[str]
|
78
|
+
"""
|
79
|
+
self._license_key = licenced_key or None
|
80
|
+
self._lib = None
|
81
|
+
self._download_binary_url = None
|
82
|
+
self._ctx = ssl.create_default_context(cafile=certifi.where())
|
83
|
+
self._file_structure = FileStructure()
|
84
|
+
self._binary_path = self._file_structure.get_binary_path()
|
85
|
+
self._binary_project_root_path = (
|
86
|
+
self._file_structure.get_binary_project_root_path()
|
87
|
+
)
|
88
|
+
self._binary_archive_path = self._file_structure.get_binary_archive_path()
|
89
|
+
self._binary_archive_path_tmp = (
|
90
|
+
self._file_structure.get_binary_archive_path_tmp()
|
91
|
+
)
|
92
|
+
self._env = load_env_config()
|
93
|
+
self._config_url = (
|
94
|
+
self._env.PUREM_CONFIG_URL
|
95
|
+
or "https://api.worktif.com/v2/portal/products/purem/config"
|
96
|
+
)
|
97
|
+
self._loader = Loader()
|
98
|
+
self._log = Logger()
|
99
|
+
|
100
|
+
def configure(self, license_key: Optional[str] = None) -> None:
|
101
|
+
"""
|
102
|
+
Configures the system with a given license key.
|
103
|
+
|
104
|
+
This method sets up the license key required for initializing the system. If no
|
105
|
+
license key is provided during configuration, the method checks if it was
|
106
|
+
already initialized. If the license key remains unset, it raises a ValueError
|
107
|
+
to indicate that a valid license key is mandatory for the setup.
|
108
|
+
|
109
|
+
:raises ValueError: Raised when no valid license key is provided during
|
110
|
+
configuration.
|
111
|
+
|
112
|
+
:param license_key: An optional string representing the license key.
|
113
|
+
:return: None
|
114
|
+
"""
|
115
|
+
if self._license_key is None and license_key is not None:
|
116
|
+
self._license_key = license_key
|
117
|
+
if self._license_key is None:
|
118
|
+
raise ValueError(
|
119
|
+
self._log.info(
|
120
|
+
"Purem requires a valid license key to initialize.\n"
|
121
|
+
"You can obtain your key at https://worktif.com or through your enterprise deployment."
|
122
|
+
)
|
123
|
+
)
|
124
|
+
|
125
|
+
self._set_binary()
|
126
|
+
|
127
|
+
def softmax(self, array: ndarray) -> ndarray:
|
128
|
+
ptr = array.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
|
129
|
+
self._lib.purem(ptr, array.size)
|
130
|
+
return array
|
131
|
+
|
132
|
+
def softmax_pure(self, ptr, size) -> None:
|
133
|
+
"""
|
134
|
+
Computes the softmax function on the provided array using pure library implementation.
|
135
|
+
|
136
|
+
This function applies the softmax transformation to the data pointed to by the
|
137
|
+
pointer and modifies it in-place. The underlying implementation is handled
|
138
|
+
by a pure library function call.
|
139
|
+
|
140
|
+
:param ptr: Pointer to the data array that the softmax is to be applied on.
|
141
|
+
:type ptr: Any
|
142
|
+
:param size: The number of elements in the data array to be processed.
|
143
|
+
:type size: int
|
144
|
+
:return: None
|
145
|
+
"""
|
146
|
+
self._lib.purem(ptr, size)
|
147
|
+
|
148
|
+
def _build_url(self, config: dict) -> Optional[str]:
|
149
|
+
"""
|
150
|
+
Constructs a URL based on the provided configuration dictionary. The method
|
151
|
+
combines protocol, base URL, path, and appends a license key to generate
|
152
|
+
a complete binary URL. If the provided configuration is None, the method
|
153
|
+
returns None immediately.
|
154
|
+
|
155
|
+
:param config: A dictionary containing the parts needed to construct the
|
156
|
+
URL. The keys usually include:
|
157
|
+
- `base_url`: The base of the URL (e.g., domain).
|
158
|
+
- `protocol`: The URL protocol (e.g., "http" or "https").
|
159
|
+
- `pathname`: The path to append to the base URL.
|
160
|
+
:return: Returns the complete binary URL as a string if the configuration
|
161
|
+
is valid. If the given configuration is None, it returns None.
|
162
|
+
"""
|
163
|
+
if config is None:
|
164
|
+
return None
|
165
|
+
base = config.get("base_url", "").rstrip("/")
|
166
|
+
protocol = config.get("protocol", "https")
|
167
|
+
path = config.get("pathname", "").lstrip("/")
|
168
|
+
binary_url = f"{protocol}://{base}/{path}{self._license_key}"
|
169
|
+
return binary_url
|
170
|
+
|
171
|
+
def _tune_binary(self):
|
172
|
+
"""
|
173
|
+
Tunes the binary by loading the specified binary file as a shared library
|
174
|
+
and setting up its function signatures for further use.
|
175
|
+
|
176
|
+
This method initializes the shared library from the specified `_binary_path`
|
177
|
+
and configures the expected argument types and return type for the `purem` function
|
178
|
+
provided by the library.
|
179
|
+
|
180
|
+
:raises OSError: If the library at `_binary_path` cannot be loaded by `ctypes.CDLL`.
|
181
|
+
|
182
|
+
:rtype: None
|
183
|
+
"""
|
184
|
+
self._lib = ctypes.CDLL(str(self._binary_path))
|
185
|
+
self._lib.purem.argtypes = [ctypes.POINTER(ctypes.c_float), ctypes.c_size_t]
|
186
|
+
self._lib.purem.restype = None
|
187
|
+
|
188
|
+
def _tune_project_root_binary(self):
|
189
|
+
"""
|
190
|
+
Tuning the project root binary configuration.
|
191
|
+
|
192
|
+
This private method initializes a connection to the binary library
|
193
|
+
located at the project's root path. It achieves this by loading the
|
194
|
+
binary through the use of the `ctypes.CDLL` method. The method also
|
195
|
+
sets expected argument types and a return type for a specific function
|
196
|
+
available in the shared library.
|
197
|
+
|
198
|
+
:return: None
|
199
|
+
"""
|
200
|
+
self._lib = ctypes.CDLL(str(self._binary_project_root_path))
|
201
|
+
self._lib.purem.argtypes = [ctypes.POINTER(ctypes.c_float), ctypes.c_size_t]
|
202
|
+
self._lib.purem.restype = None
|
203
|
+
|
204
|
+
def _load_from_latest_cdn_index(self) -> Optional[Dict]:
|
205
|
+
"""
|
206
|
+
Attempts to load the latest configuration from a CDN index, if a config URL is provided.
|
207
|
+
The method sends a request to the configured URL, reads its response, and parses it into
|
208
|
+
a dictionary. If there is no configured URL or an exception occurs during the process, it
|
209
|
+
returns None.
|
210
|
+
|
211
|
+
:raises Exception: If an error occurs during the request or while reading the response.
|
212
|
+
|
213
|
+
:return: A dictionary containing the parsed configuration data if the operation is
|
214
|
+
successful, or None if no config URL is provided or an error occurs.
|
215
|
+
:rtype: Optional[Dict]
|
216
|
+
"""
|
217
|
+
try:
|
218
|
+
if self._config_url is not None:
|
219
|
+
req = urllib.request.Request(
|
220
|
+
self._config_url,
|
221
|
+
)
|
222
|
+
|
223
|
+
with urllib.request.urlopen(req, context=self._ctx) as response:
|
224
|
+
return json.load(response)
|
225
|
+
else:
|
226
|
+
return None
|
227
|
+
|
228
|
+
except Exception:
|
229
|
+
return None
|
230
|
+
|
231
|
+
def _set_binary(self):
|
232
|
+
"""
|
233
|
+
Sets and initializes the binary for the Purem runtime environment. The method ensures that a valid binary
|
234
|
+
is available and properly configured. If a valid license key or binary is not found, the method will attempt
|
235
|
+
to download, validate, and extract the required binary files. If initialization fails at any stage,
|
236
|
+
appropriate errors are raised with detailed logging for debugging and support purposes.
|
237
|
+
|
238
|
+
:param self: Represents the instance of the class.
|
239
|
+
:type self: PuremRuntime
|
240
|
+
|
241
|
+
:raises ValueError: Raised if a valid license key is missing and cannot proceed with initialization.
|
242
|
+
:raises RuntimeError: Raised if the purem binary fails to load or cannot be initialized due to local issues,
|
243
|
+
license mismatch, or any other unexpected errors.
|
244
|
+
|
245
|
+
:return: None
|
246
|
+
"""
|
247
|
+
if os.path.exists(self._binary_path):
|
248
|
+
self._tune_binary()
|
249
|
+
elif os.path.exists(self._binary_project_root_path):
|
250
|
+
self._tune_project_root_binary()
|
251
|
+
elif not self._license_key:
|
252
|
+
raise ValueError(
|
253
|
+
self._log.info(
|
254
|
+
"Purem requires a valid license key to initialize.\n"
|
255
|
+
"You can obtain your key at https://worktif.com or through your enterprise deployment."
|
256
|
+
)
|
257
|
+
)
|
258
|
+
else:
|
259
|
+
try:
|
260
|
+
self._loader.set_message(
|
261
|
+
"Initializing Purem licensed runtime locally..."
|
262
|
+
)
|
263
|
+
self._loader.start()
|
264
|
+
self._download_binary_url = (
|
265
|
+
self._build_url(self._load_from_latest_cdn_index())
|
266
|
+
or f"{self._env.PUREM_DOWNLOAD_BINARY_URL}{self._license_key}"
|
267
|
+
)
|
268
|
+
self._download_and_extract_binary()
|
269
|
+
self._loader.stop()
|
270
|
+
|
271
|
+
except Exception as e:
|
272
|
+
raise RuntimeError(
|
273
|
+
self._log.info(
|
274
|
+
"We couldn't load your Purem binary at this time.\n"
|
275
|
+
"This may be a local issue or license mismatch.\n"
|
276
|
+
"Please try again – or contact us at support@worktif.com.\n"
|
277
|
+
"We're here to help you run at full power."
|
278
|
+
)
|
279
|
+
)
|
280
|
+
|
281
|
+
try:
|
282
|
+
self._tune_project_root_binary()
|
283
|
+
except Exception as e:
|
284
|
+
raise RuntimeError(
|
285
|
+
self._log.info(
|
286
|
+
"It appears your Purem licensed binary can not be loaded. Please try again. If the problem "
|
287
|
+
"persists, please contact us at support@worktif.com. Thank you for your patience."
|
288
|
+
)
|
289
|
+
)
|
290
|
+
|
291
|
+
def _download_and_extract_binary(self):
|
292
|
+
"""
|
293
|
+
Downloads a binary file from a given URL, saves it temporarily, and extracts its
|
294
|
+
contents to a specific directory. Handles errors related to incomplete or
|
295
|
+
corrupted downloads and archives.
|
296
|
+
|
297
|
+
Raises runtime errors with detailed context if an issue occurs during download
|
298
|
+
or extraction. Ensures successful extraction and logs the output location.
|
299
|
+
|
300
|
+
:raises RuntimeError: If the download or extraction process fails due to
|
301
|
+
corrupted archive or any other unexpected issue.
|
302
|
+
"""
|
303
|
+
req = urllib.request.Request(
|
304
|
+
self._download_binary_url, headers={"User-Agent": "Mozilla/5.0"}
|
305
|
+
)
|
306
|
+
|
307
|
+
try:
|
308
|
+
with urllib.request.urlopen(req, context=self._ctx) as response:
|
309
|
+
with open(self._binary_archive_path_tmp, "wb") as out_file:
|
310
|
+
shutil.copyfileobj(response, out_file)
|
311
|
+
|
312
|
+
shutil.move(self._binary_archive_path_tmp, self._binary_archive_path)
|
313
|
+
except Exception as e:
|
314
|
+
raise RuntimeError(
|
315
|
+
self._log.info(
|
316
|
+
f"The Purem archive appears to be corrupted or incomplete.\nDetails: {e}"
|
317
|
+
"Please ensure the package downloaded fully and is unmodified.\n"
|
318
|
+
"Need help? Contact support@worktif.com – we'll assist right away.\n"
|
319
|
+
)
|
320
|
+
)
|
321
|
+
|
322
|
+
try:
|
323
|
+
with zipfile.ZipFile(self._binary_archive_path, "r") as zip_ref:
|
324
|
+
zip_ref.extractall(
|
325
|
+
self._file_structure.dirs.project_root_binary_dir_path
|
326
|
+
)
|
327
|
+
self._log.info_new_line(
|
328
|
+
f"Purem binary extracted to: {self._file_structure.dirs.binary_dir_path}"
|
329
|
+
)
|
330
|
+
except zipfile.BadZipFile as e:
|
331
|
+
raise RuntimeError(
|
332
|
+
self._log.info(
|
333
|
+
f"The Purem archive appears to be corrupted or incomplete.\nDetails: {e}"
|
334
|
+
"Please ensure the package downloaded fully and is unmodified.\n"
|
335
|
+
"Need help? Contact support@worktif.com – we'll assist right away.\n"
|
336
|
+
)
|
337
|
+
)
|
338
|
+
|
339
|
+
self._binary_archive_path.unlink()
|
@@ -2,7 +2,7 @@
|
|
2
2
|
Business Source License 1.1
|
3
3
|
|
4
4
|
Copyright (C) 2025 Raman Marozau, raman@worktif.com
|
5
|
-
Use of this software is governed by the Business Source License included in the LICENSE
|
5
|
+
Use of this software is governed by the Business Source License included in the LICENSE file and at www.mariadb.com/bsl11.
|
6
6
|
|
7
7
|
Change Date: Never
|
8
8
|
On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
|
@@ -2,7 +2,7 @@
|
|
2
2
|
Business Source License 1.1
|
3
3
|
|
4
4
|
Copyright (C) 2025 Raman Marozau, raman@worktif.com
|
5
|
-
Use of this software is governed by the Business Source License included in the LICENSE
|
5
|
+
Use of this software is governed by the Business Source License included in the LICENSE file and at www.mariadb.com/bsl11.
|
6
6
|
|
7
7
|
Change Date: Never
|
8
8
|
On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
|
@@ -0,0 +1,102 @@
|
|
1
|
+
"""
|
2
|
+
Business Source License 1.1
|
3
|
+
|
4
|
+
Copyright (C) 2025 Raman Marozau, raman@worktif.com
|
5
|
+
Use of this software is governed by the Business Source License included in the LICENSE file and at www.mariadb.com/bsl11.
|
6
|
+
|
7
|
+
Change Date: Never
|
8
|
+
On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
|
9
|
+
Additional Use Grant: Free for personal and non-commercial research use only.
|
10
|
+
|
11
|
+
SPDX-License-Identifier: BUSL-1.1
|
12
|
+
"""
|
13
|
+
|
14
|
+
import sys
|
15
|
+
import threading
|
16
|
+
import time
|
17
|
+
|
18
|
+
|
19
|
+
class Loader:
|
20
|
+
def __init__(self, message="Downloading"):
|
21
|
+
"""
|
22
|
+
Represents a CLI-based animation that indicates a background task.
|
23
|
+
|
24
|
+
This class is designed to show a simple animation in the terminal
|
25
|
+
to convey the progress of an ongoing background task. The animation
|
26
|
+
runs in a separate thread to ensure that it does not block the main
|
27
|
+
thread's operations.
|
28
|
+
|
29
|
+
Attributes
|
30
|
+
----------
|
31
|
+
_message : str
|
32
|
+
The text message displayed along with the animation.
|
33
|
+
_thread : threading.Thread
|
34
|
+
The thread responsible for running the animation in the background.
|
35
|
+
done : bool
|
36
|
+
A control flag to stop the animation when set to True.
|
37
|
+
|
38
|
+
:param message: The message to display alongside the animation. Defaults to
|
39
|
+
"Downloading".
|
40
|
+
"""
|
41
|
+
self._message = message
|
42
|
+
self._thread = threading.Thread(target=self._animate, daemon=True)
|
43
|
+
self.done = False
|
44
|
+
|
45
|
+
def set_message(self, message: str):
|
46
|
+
"""
|
47
|
+
Sets the message attribute of the instance.
|
48
|
+
|
49
|
+
:param message: A string representing the message to be assigned.
|
50
|
+
:type message: str
|
51
|
+
"""
|
52
|
+
self._message = message
|
53
|
+
|
54
|
+
def start(self):
|
55
|
+
"""
|
56
|
+
Starts the thread associated with this instance.
|
57
|
+
|
58
|
+
This method initiates the thread's execution by invoking the `start`
|
59
|
+
method on the `self._thread` object. It assumes that the `self._thread`
|
60
|
+
attribute has been properly initialized and is a valid thread instance.
|
61
|
+
|
62
|
+
:return: None
|
63
|
+
"""
|
64
|
+
self._thread.start()
|
65
|
+
|
66
|
+
def stop(self):
|
67
|
+
"""
|
68
|
+
Represents a mechanism to stop a thread execution gracefully.
|
69
|
+
|
70
|
+
This class or function provides a controlled way to stop a running thread
|
71
|
+
by marking it as done and waiting for the thread to conclude its execution.
|
72
|
+
It ensures the thread completes its ongoing tasks correctly before stopping.
|
73
|
+
|
74
|
+
:attributes:
|
75
|
+
done: Indicates whether the thread execution is flagged to stop.
|
76
|
+
_thread: The thread instance being managed.
|
77
|
+
|
78
|
+
:return: None
|
79
|
+
"""
|
80
|
+
self.done = True
|
81
|
+
self._thread.join()
|
82
|
+
|
83
|
+
def _animate(self):
|
84
|
+
"""
|
85
|
+
Handles the animation of a loading spinner for a CLI-based task. The animation
|
86
|
+
displays a series of symbols in rotation while the task is ongoing. It
|
87
|
+
continues until the flag `self.done` is set to True. Once the task is complete,
|
88
|
+
a "done" message is displayed.
|
89
|
+
|
90
|
+
This method is designed to provide a simple user feedback mechanism during
|
91
|
+
long-running or background operations in command-line applications.
|
92
|
+
|
93
|
+
:return: None
|
94
|
+
"""
|
95
|
+
symbols = ["|", "/", "-", "\\"]
|
96
|
+
i = 0
|
97
|
+
while not self.done:
|
98
|
+
sys.stdout.write(f"\r{self._message}... {symbols[i % len(symbols)]}")
|
99
|
+
sys.stdout.flush()
|
100
|
+
i += 1
|
101
|
+
time.sleep(0.1)
|
102
|
+
sys.stdout.write(f"\r{self._message}... done.\n")
|
@@ -2,7 +2,7 @@
|
|
2
2
|
Business Source License 1.1
|
3
3
|
|
4
4
|
Copyright (C) 2025 Raman Marozau, raman@worktif.com
|
5
|
-
Use of this software is governed by the Business Source License included in the LICENSE
|
5
|
+
Use of this software is governed by the Business Source License included in the LICENSE file and at www.mariadb.com/bsl11.
|
6
6
|
|
7
7
|
Change Date: Never
|
8
8
|
On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
|
@@ -2,7 +2,7 @@
|
|
2
2
|
Business Source License 1.1
|
3
3
|
|
4
4
|
Copyright (C) 2025 Raman Marozau, raman@worktif.com
|
5
|
-
Use of this software is governed by the Business Source License included in the LICENSE
|
5
|
+
Use of this software is governed by the Business Source License included in the LICENSE file and at www.mariadb.com/bsl11.
|
6
6
|
|
7
7
|
Change Date: Never
|
8
8
|
On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: purem
|
3
|
-
Version:
|
3
|
+
Version: 3.0.1
|
4
4
|
Summary: The official high-performance mapping function for mixed-type arrays powered by Work TIF Ltd.
|
5
5
|
Author-email: Raman Marozau <raman@worktif.com>
|
6
6
|
License-Expression: BUSL-1.1
|
@@ -26,7 +26,6 @@ Description-Content-Type: text/x-rst
|
|
26
26
|
License-File: LICENSE
|
27
27
|
Requires-Dist: numpy~=2.1.3
|
28
28
|
Requires-Dist: pytest~=8.3.5
|
29
|
-
Requires-Dist: torch~=2.6.0
|
30
29
|
Requires-Dist: numba~=0.61.0
|
31
30
|
Requires-Dist: certifi~=2025.1.31
|
32
31
|
Requires-Dist: pydantic~=2.10.6
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "purem"
|
7
|
-
version = "
|
7
|
+
version = "3.0.1"
|
8
8
|
description = "The official high-performance mapping function for mixed-type arrays powered by Work TIF Ltd."
|
9
9
|
readme = { file = "README.rst", content-type = "text/x-rst" }
|
10
10
|
requires-python = ">=3.11"
|
@@ -33,7 +33,6 @@ classifiers = [
|
|
33
33
|
dependencies = [
|
34
34
|
"numpy~=2.1.3",
|
35
35
|
"pytest~=8.3.5",
|
36
|
-
"torch~=2.6.0",
|
37
36
|
"numba~=0.61.0",
|
38
37
|
"certifi~=2025.1.31",
|
39
38
|
"pydantic~=2.10.6",
|
purem-2.1.5/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
2.1.5
|
purem-2.1.5/purem/core.py
DELETED
@@ -1,252 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Business Source License 1.1
|
3
|
-
|
4
|
-
Copyright (C) 2025 Raman Marozau, raman@worktif.com
|
5
|
-
Use of this software is governed by the Business Source License included in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
6
|
-
|
7
|
-
Change Date: Never
|
8
|
-
On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
|
9
|
-
Additional Use Grant: Free for personal and non-commercial research use only.
|
10
|
-
|
11
|
-
SPDX-License-Identifier: BUSL-1.1
|
12
|
-
"""
|
13
|
-
|
14
|
-
import base64
|
15
|
-
import ctypes
|
16
|
-
import json
|
17
|
-
import os
|
18
|
-
import shutil
|
19
|
-
import ssl
|
20
|
-
import urllib.request
|
21
|
-
import zipfile
|
22
|
-
from typing import List, Optional, Dict
|
23
|
-
|
24
|
-
import certifi as certifi
|
25
|
-
import numpy as np
|
26
|
-
from numpy import ndarray
|
27
|
-
|
28
|
-
from purem.env_config import load_env_config
|
29
|
-
from purem.file_structure import FileStructure
|
30
|
-
from purem.loader import Loader
|
31
|
-
from purem.logger import Logger
|
32
|
-
from purem.utils import _compute_shifted_jit
|
33
|
-
|
34
|
-
|
35
|
-
class Purem:
|
36
|
-
"""
|
37
|
-
Summary of what the class does.
|
38
|
-
|
39
|
-
The Purem class is used to manage and handle the initialization, configuration, and
|
40
|
-
operation of the Purem environment and its associated runtime, including license management,
|
41
|
-
binary handling, and API configuration. It centralizes the setup of the Purem runtime,
|
42
|
-
ensuring that all necessary binaries, license checks, and configurations are in place.
|
43
|
-
|
44
|
-
The class supports downloading and extracting binaries, setting up runtime libraries, and providing utility
|
45
|
-
methods such as softmax computation, license validation, and URL construction to interact with remote
|
46
|
-
and local systems.
|
47
|
-
|
48
|
-
:ivar _license_key: The license key used for Purem initialization and validation.
|
49
|
-
:type _license_key: Optional[str]
|
50
|
-
:ivar _lib: The dynamically loaded library object for the Purem binary.
|
51
|
-
:type _lib: ctypes.CDLL
|
52
|
-
:ivar _download_binary_url: The URL for downloading the Purem binary.
|
53
|
-
:type _download_binary_url: Optional[str]
|
54
|
-
:ivar _ctx: SSL context used for secure connections, initialized using the system's certificates.
|
55
|
-
:type _ctx: ssl.SSLContext
|
56
|
-
:ivar _file_structure: Manages file paths and structures related to Purem binary and runtime.
|
57
|
-
:type _file_structure: FileStructure
|
58
|
-
:ivar _binary_path: The path to the main Purem binary.
|
59
|
-
:type _binary_path: pathlib.Path
|
60
|
-
:ivar _binary_project_root_path: The path to the project root Purem binary.
|
61
|
-
:type _binary_project_root_path: pathlib.Path
|
62
|
-
:ivar _binary_archive_path: The path where the binary archive is stored.
|
63
|
-
:type _binary_archive_path: pathlib.Path
|
64
|
-
:ivar _binary_archive_path_tmp: The temporary path for binary archive operations.
|
65
|
-
:type _binary_archive_path_tmp: pathlib.Path
|
66
|
-
:ivar _env: Environment configuration used in Purem operations, including URLs.
|
67
|
-
:type _env: Any
|
68
|
-
:ivar _config_url: URL for retrieving Purem configuration, either from an environment variable or a default.
|
69
|
-
:type _config_url: str
|
70
|
-
:ivar _loader: Loader utility for displaying loading messages during lengthy operations.
|
71
|
-
:type _loader: Loader
|
72
|
-
:ivar _log: Logger utility for tracking and managing logging in the Purem operations.
|
73
|
-
:type _log: Logger
|
74
|
-
"""
|
75
|
-
|
76
|
-
def __init__(self, licenced_key: Optional[str] = None):
|
77
|
-
self._license_key = licenced_key or None
|
78
|
-
self._lib = None
|
79
|
-
self._download_binary_url = None
|
80
|
-
self._ctx = ssl.create_default_context(cafile=certifi.where())
|
81
|
-
self._file_structure = FileStructure()
|
82
|
-
self._binary_path = self._file_structure.get_binary_path()
|
83
|
-
self._binary_project_root_path = (
|
84
|
-
self._file_structure.get_binary_project_root_path()
|
85
|
-
)
|
86
|
-
self._binary_archive_path = self._file_structure.get_binary_archive_path()
|
87
|
-
self._binary_archive_path_tmp = (
|
88
|
-
self._file_structure.get_binary_archive_path_tmp()
|
89
|
-
)
|
90
|
-
self._env = load_env_config()
|
91
|
-
self._config_url = (
|
92
|
-
self._env.PUREM_CONFIG_URL
|
93
|
-
or "https://api.worktif.com/v2/portal/products/purem/config"
|
94
|
-
)
|
95
|
-
self._loader = Loader()
|
96
|
-
self._log = Logger()
|
97
|
-
|
98
|
-
def configure(self, license_key: Optional[str] = None) -> None:
|
99
|
-
if self._license_key is None and license_key is not None:
|
100
|
-
self._license_key = license_key
|
101
|
-
if self._license_key is None:
|
102
|
-
raise ValueError(
|
103
|
-
self._log.info(
|
104
|
-
"Purem requires a valid license key to initialize.\n"
|
105
|
-
"You can obtain your key at https://worktif.com or through your enterprise deployment."
|
106
|
-
)
|
107
|
-
)
|
108
|
-
|
109
|
-
self._set_binary()
|
110
|
-
|
111
|
-
def softmax(self, array: ndarray) -> ndarray:
|
112
|
-
shifted_arr = np.empty(array.size, dtype=np.float32)
|
113
|
-
_compute_shifted_jit(array, shifted_arr)
|
114
|
-
ptr = shifted_arr.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
|
115
|
-
self._lib.purem(ptr, array.size)
|
116
|
-
return shifted_arr
|
117
|
-
|
118
|
-
def softmax_pure(self, arr, size) -> List[any]:
|
119
|
-
"""
|
120
|
-
Compute the softmax of an array using a pure implementation.
|
121
|
-
|
122
|
-
The softmax function is used to normalize an input array into a probability
|
123
|
-
distribution. It is often used in machine learning for classification tasks
|
124
|
-
where the output represents probabilities of different classes. This method
|
125
|
-
employs a pure implementation by calling a pre-defined library function.
|
126
|
-
|
127
|
-
:param arr: The input array containing numeric values to be transformed
|
128
|
-
into a probability distribution.
|
129
|
-
:param size: The size of the input array `arr`.
|
130
|
-
:return: A list representing the normalized probability distribution obtained
|
131
|
-
by applying the softmax function.
|
132
|
-
"""
|
133
|
-
return self._lib.purem(arr, size)
|
134
|
-
|
135
|
-
def _build_url(self, config: dict) -> Optional[str]:
|
136
|
-
if config is None:
|
137
|
-
return None
|
138
|
-
base = config.get("base_url", "").rstrip("/")
|
139
|
-
protocol = config.get("protocol", "https")
|
140
|
-
path = config.get("pathname", "").lstrip("/")
|
141
|
-
binary_url = f"{protocol}://{base}/{path}{self._license_key}"
|
142
|
-
return binary_url
|
143
|
-
|
144
|
-
def _tune_binary(self):
|
145
|
-
self._lib = ctypes.CDLL(str(self._binary_path))
|
146
|
-
self._lib.purem.argtypes = [ctypes.POINTER(ctypes.c_float), ctypes.c_size_t]
|
147
|
-
self._lib.purem.restype = None
|
148
|
-
|
149
|
-
def _tune_project_root_binary(self):
|
150
|
-
self._lib = ctypes.CDLL(str(self._binary_project_root_path))
|
151
|
-
self._lib.purem.argtypes = [ctypes.POINTER(ctypes.c_float), ctypes.c_size_t]
|
152
|
-
self._lib.purem.restype = None
|
153
|
-
|
154
|
-
def _load_from_latest_cdn_index(self) -> Optional[Dict]:
|
155
|
-
try:
|
156
|
-
if self._config_url is not None:
|
157
|
-
req = urllib.request.Request(
|
158
|
-
self._config_url,
|
159
|
-
)
|
160
|
-
|
161
|
-
with urllib.request.urlopen(req, context=self._ctx) as response:
|
162
|
-
return json.load(response)
|
163
|
-
else:
|
164
|
-
return None
|
165
|
-
|
166
|
-
except Exception:
|
167
|
-
return None
|
168
|
-
|
169
|
-
def _set_binary(self):
|
170
|
-
if os.path.exists(self._binary_path):
|
171
|
-
self._tune_binary()
|
172
|
-
elif os.path.exists(self._binary_project_root_path):
|
173
|
-
self._tune_project_root_binary()
|
174
|
-
elif not self._license_key:
|
175
|
-
raise ValueError(
|
176
|
-
self._log.info(
|
177
|
-
"Purem requires a valid license key to initialize.\n"
|
178
|
-
"You can obtain your key at https://worktif.com or through your enterprise deployment."
|
179
|
-
)
|
180
|
-
)
|
181
|
-
else:
|
182
|
-
try:
|
183
|
-
self._loader.set_message(
|
184
|
-
"Initializing Purem licensed runtime locally..."
|
185
|
-
)
|
186
|
-
self._loader.start()
|
187
|
-
self._download_binary_url = (
|
188
|
-
self._build_url(self._load_from_latest_cdn_index())
|
189
|
-
or f"{self._env.PUREM_DOWNLOAD_BINARY_URL}{self._license_key}"
|
190
|
-
)
|
191
|
-
self._download_and_extract_binary()
|
192
|
-
self._loader.stop()
|
193
|
-
|
194
|
-
except Exception as e:
|
195
|
-
raise RuntimeError(
|
196
|
-
self._log.info(
|
197
|
-
"We couldn't load your Purem binary at this time.\n"
|
198
|
-
"This may be a local issue or license mismatch.\n"
|
199
|
-
"Please try again – or contact us at support@worktif.com.\n"
|
200
|
-
"We're here to help you run at full power."
|
201
|
-
)
|
202
|
-
)
|
203
|
-
|
204
|
-
try:
|
205
|
-
self._tune_project_root_binary()
|
206
|
-
except Exception as e:
|
207
|
-
raise RuntimeError(
|
208
|
-
self._log.info(
|
209
|
-
"It appears your Purem licensed binary can not be loaded. Please try again. If the problem "
|
210
|
-
"persists, please contact us at support@worktif.com. Thank you for your patience."
|
211
|
-
)
|
212
|
-
)
|
213
|
-
|
214
|
-
def _download_and_extract_binary(self):
|
215
|
-
req = urllib.request.Request(
|
216
|
-
self._download_binary_url,
|
217
|
-
headers={"User-Agent": "Mozilla/5.0"}
|
218
|
-
)
|
219
|
-
|
220
|
-
try:
|
221
|
-
with urllib.request.urlopen(req, context=self._ctx) as response:
|
222
|
-
with open(self._binary_archive_path_tmp, "wb") as out_file:
|
223
|
-
shutil.copyfileobj(response, out_file)
|
224
|
-
|
225
|
-
shutil.move(self._binary_archive_path_tmp, self._binary_archive_path)
|
226
|
-
except Exception as e:
|
227
|
-
raise RuntimeError(
|
228
|
-
self._log.info(
|
229
|
-
f"The Purem archive appears to be corrupted or incomplete.\nDetails: {e}"
|
230
|
-
"Please ensure the package downloaded fully and is unmodified.\n"
|
231
|
-
"Need help? Contact support@worktif.com – we'll assist right away.\n"
|
232
|
-
)
|
233
|
-
)
|
234
|
-
|
235
|
-
try:
|
236
|
-
with zipfile.ZipFile(self._binary_archive_path, "r") as zip_ref:
|
237
|
-
zip_ref.extractall(
|
238
|
-
self._file_structure.dirs.project_root_binary_dir_path
|
239
|
-
)
|
240
|
-
self._log.info_new_line(
|
241
|
-
f"Purem binary extracted to: {self._file_structure.dirs.binary_dir_path}"
|
242
|
-
)
|
243
|
-
except zipfile.BadZipFile as e:
|
244
|
-
raise RuntimeError(
|
245
|
-
self._log.info(
|
246
|
-
f"The Purem archive appears to be corrupted or incomplete.\nDetails: {e}"
|
247
|
-
"Please ensure the package downloaded fully and is unmodified.\n"
|
248
|
-
"Need help? Contact support@worktif.com – we'll assist right away.\n"
|
249
|
-
)
|
250
|
-
)
|
251
|
-
|
252
|
-
self._binary_archive_path.unlink()
|
purem-2.1.5/purem/loader.py
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Business Source License 1.1
|
3
|
-
|
4
|
-
Copyright (C) 2025 Raman Marozau, raman@worktif.com
|
5
|
-
Use of this software is governed by the Business Source License included in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
6
|
-
|
7
|
-
Change Date: Never
|
8
|
-
On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
|
9
|
-
Additional Use Grant: Free for personal and non-commercial research use only.
|
10
|
-
|
11
|
-
SPDX-License-Identifier: BUSL-1.1
|
12
|
-
"""
|
13
|
-
|
14
|
-
import sys
|
15
|
-
import threading
|
16
|
-
import time
|
17
|
-
|
18
|
-
|
19
|
-
class Loader:
|
20
|
-
def __init__(self, message="Downloading"):
|
21
|
-
self._message = message
|
22
|
-
self._thread = threading.Thread(target=self._animate, daemon=True)
|
23
|
-
self.done = False
|
24
|
-
|
25
|
-
def set_message(self, message: str):
|
26
|
-
self._message = message
|
27
|
-
|
28
|
-
def start(self):
|
29
|
-
self._thread.start()
|
30
|
-
|
31
|
-
def stop(self):
|
32
|
-
self.done = True
|
33
|
-
self._thread.join()
|
34
|
-
|
35
|
-
def _animate(self):
|
36
|
-
symbols = ["|", "/", "-", "\\"]
|
37
|
-
i = 0
|
38
|
-
while not self.done:
|
39
|
-
sys.stdout.write(f"\r{self._message}... {symbols[i % len(symbols)]}")
|
40
|
-
sys.stdout.flush()
|
41
|
-
i += 1
|
42
|
-
time.sleep(0.1)
|
43
|
-
sys.stdout.write(f"\r{self._message}... done.\n")
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|