amalgam-lang 13.0.3__py3-none-manylinux_2_28_x86_64.whl
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.
Potentially problematic release.
This version of amalgam-lang might be problematic. Click here for more details.
- amalgam/__init__.py +1 -0
- amalgam/api.py +1108 -0
- amalgam/lib/linux/amd64/amalgam-mt-afmi.so +0 -0
- amalgam/lib/linux/amd64/amalgam-mt-noavx.so +0 -0
- amalgam/lib/linux/amd64/amalgam-mt.so +0 -0
- amalgam/lib/linux/amd64/amalgam-omp.so +0 -0
- amalgam/lib/linux/amd64/amalgam-st-afmi.so +0 -0
- amalgam/lib/linux/amd64/amalgam-st.so +0 -0
- amalgam/lib/linux/amd64/docs/version.json +3 -0
- amalgam/lib/version.json +9 -0
- amalgam_lang-13.0.3.dist-info/LICENSE.txt +661 -0
- amalgam_lang-13.0.3.dist-info/METADATA +766 -0
- amalgam_lang-13.0.3.dist-info/RECORD +15 -0
- amalgam_lang-13.0.3.dist-info/WHEEL +5 -0
- amalgam_lang-13.0.3.dist-info/top_level.txt +1 -0
amalgam/api.py
ADDED
|
@@ -0,0 +1,1108 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ctypes import (
|
|
4
|
+
_Pointer, Array, byref, c_bool, c_char, c_char_p, c_double, c_size_t, c_uint64, c_void_p,
|
|
5
|
+
cast, cdll, POINTER, Structure
|
|
6
|
+
)
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
import gc
|
|
9
|
+
import json as json_lib
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import platform
|
|
13
|
+
import re
|
|
14
|
+
import typing as t
|
|
15
|
+
import warnings
|
|
16
|
+
|
|
17
|
+
# Set to amalgam
|
|
18
|
+
_logger = logging.getLogger('amalgam')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class _LoadEntityStatus(Structure):
|
|
22
|
+
"""
|
|
23
|
+
A private status returned from Amalgam binary LoadEntity C API.
|
|
24
|
+
|
|
25
|
+
This is implemented with ctypes for accessing binary Amalgam builds.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
_fields_ = [
|
|
29
|
+
("loaded", c_bool),
|
|
30
|
+
("message", POINTER(c_char)),
|
|
31
|
+
("version", POINTER(c_char))
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class LoadEntityStatus:
|
|
36
|
+
"""
|
|
37
|
+
Status returned by :func:`~api.Amalgam.load_entity`.
|
|
38
|
+
|
|
39
|
+
This is implemented with python types and is meant to wrap _LoadEntityStatus
|
|
40
|
+
which uses ctypes and directly interacts with the Amalgam binaries.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
api : Amalgam
|
|
45
|
+
The Python Amalgam interface.
|
|
46
|
+
c_status : _LoadEntityStatus, optional
|
|
47
|
+
_LoadEntityStatus instance.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, api: Amalgam, c_status: t.Optional[_LoadEntityStatus] = None):
|
|
51
|
+
"""Initialize LoadEntityStatus."""
|
|
52
|
+
if c_status is None:
|
|
53
|
+
self.loaded = True
|
|
54
|
+
self.message = ""
|
|
55
|
+
self.version = ""
|
|
56
|
+
else:
|
|
57
|
+
self.loaded = bool(c_status.loaded)
|
|
58
|
+
self.message = api.char_p_to_bytes(c_status.message).decode("utf-8")
|
|
59
|
+
self.version = api.char_p_to_bytes(c_status.version).decode("utf-8")
|
|
60
|
+
|
|
61
|
+
def __str__(self) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Return a human-readable string representation.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
str
|
|
68
|
+
The human-readable string representation.
|
|
69
|
+
"""
|
|
70
|
+
return f"{self.loaded},\"{self.message}\",\"{self.version}\""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Amalgam:
|
|
74
|
+
"""
|
|
75
|
+
A general python direct interface to the Amalgam library.
|
|
76
|
+
|
|
77
|
+
This is implemented with ctypes for accessing binary Amalgam builds.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
library_path : Path or str, optional
|
|
82
|
+
Path to either the amalgam DLL, DyLib or SO (Windows, MacOS
|
|
83
|
+
or Linux, respectively). If not specified it will build a path to the
|
|
84
|
+
appropriate library bundled with the package.
|
|
85
|
+
|
|
86
|
+
append_trace_file : bool, default False
|
|
87
|
+
If True, new content will be appended to a trace file if the file
|
|
88
|
+
already exists rather than creating a new file.
|
|
89
|
+
|
|
90
|
+
arch : str, optional
|
|
91
|
+
The platform architecture of the embedded Amalgam library.
|
|
92
|
+
If not provided, it will be automatically detected.
|
|
93
|
+
(Note: arm64_8a architecture must be manually specified!)
|
|
94
|
+
|
|
95
|
+
execution_trace_dir : str, optional
|
|
96
|
+
A directory path for writing trace files. If ``None``, then
|
|
97
|
+
the current working directory will be used.
|
|
98
|
+
|
|
99
|
+
execution_trace_file : str, default "execution.trace"
|
|
100
|
+
The full or relative path to the execution trace used in debugging.
|
|
101
|
+
|
|
102
|
+
gc_interval : int, optional
|
|
103
|
+
If set, garbage collection will be forced at the specified
|
|
104
|
+
interval of amalgam operations. Note that this reduces memory
|
|
105
|
+
consumption at the compromise of performance. Only use if models are
|
|
106
|
+
exceeding your host's process memory limit or if paging to disk. As an
|
|
107
|
+
example, if this operation is set to 0 (force garbage collection every
|
|
108
|
+
operation), it results in a performance impact of 150x.
|
|
109
|
+
Default value does not force garbage collection.
|
|
110
|
+
|
|
111
|
+
library_postfix : str, optional
|
|
112
|
+
For configuring use of different amalgam builds i.e. -st for
|
|
113
|
+
single-threaded. If not provided, an attempt will be made to detect
|
|
114
|
+
it within library_path. If neither are available, -mt (multi-threaded)
|
|
115
|
+
will be used.
|
|
116
|
+
|
|
117
|
+
max_num_threads : int, optional
|
|
118
|
+
If a multithreaded Amalgam binary is used, sets the maximum
|
|
119
|
+
number of threads to the value specified. If 0, will use the number of
|
|
120
|
+
visible logical cores. Default None will not attempt to set this value.
|
|
121
|
+
|
|
122
|
+
sbf_datastore_enabled : bool, optional
|
|
123
|
+
If true, sbf tree structures are enabled.
|
|
124
|
+
|
|
125
|
+
trace : bool, optional
|
|
126
|
+
If true, enables execution trace file.
|
|
127
|
+
|
|
128
|
+
Raises
|
|
129
|
+
------
|
|
130
|
+
FileNotFoundError
|
|
131
|
+
Amalgam library not found in default location, and not configured to
|
|
132
|
+
retrieve automatically.
|
|
133
|
+
RuntimeError
|
|
134
|
+
The initializer was unable to determine a supported platform or
|
|
135
|
+
architecture to use when no explicit `library_path` was supplied.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def __init__( # noqa: C901
|
|
139
|
+
self,
|
|
140
|
+
library_path: t.Optional[Path | str] = None,
|
|
141
|
+
*,
|
|
142
|
+
arch: t.Optional[str] = None,
|
|
143
|
+
append_trace_file: bool = False,
|
|
144
|
+
execution_trace_dir: t.Optional[str] = None,
|
|
145
|
+
execution_trace_file: str = "execution.trace",
|
|
146
|
+
gc_interval: t.Optional[int] = None,
|
|
147
|
+
library_postfix: t.Optional[str] = None,
|
|
148
|
+
max_num_threads: t.Optional[int] = None,
|
|
149
|
+
sbf_datastore_enabled: t.Optional[bool] = None,
|
|
150
|
+
trace: t.Optional[bool] = None,
|
|
151
|
+
**kwargs
|
|
152
|
+
):
|
|
153
|
+
"""Initialize Amalgam instance."""
|
|
154
|
+
if len(kwargs):
|
|
155
|
+
warnings.warn(f'Unexpected keyword arguments '
|
|
156
|
+
f'[{", ".join(list(kwargs.keys()))}] '
|
|
157
|
+
f'passed to Amalgam constructor.')
|
|
158
|
+
|
|
159
|
+
# Work out path and postfix of the library from the given parameters.
|
|
160
|
+
self.library_path, self.library_postfix = self._get_library_path(
|
|
161
|
+
library_path, library_postfix, arch)
|
|
162
|
+
|
|
163
|
+
self.append_trace_file = append_trace_file
|
|
164
|
+
if trace:
|
|
165
|
+
# Determine where to put the trace files ...
|
|
166
|
+
self.base_execution_trace_file = execution_trace_file
|
|
167
|
+
# default to current directory, and expand relative paths ..
|
|
168
|
+
if execution_trace_dir is None:
|
|
169
|
+
self.execution_trace_dir = Path.cwd()
|
|
170
|
+
else:
|
|
171
|
+
self.execution_trace_dir = Path(
|
|
172
|
+
execution_trace_dir).expanduser().absolute()
|
|
173
|
+
# Create the trace directory if needed
|
|
174
|
+
if not self.execution_trace_dir.exists():
|
|
175
|
+
self.execution_trace_dir.mkdir(parents=True, exist_ok=True)
|
|
176
|
+
|
|
177
|
+
# increment a counter on the file name, if file already exists..
|
|
178
|
+
self.execution_trace_filepath = Path(
|
|
179
|
+
self.execution_trace_dir, execution_trace_file)
|
|
180
|
+
if not self.append_trace_file:
|
|
181
|
+
counter = 1
|
|
182
|
+
while self.execution_trace_filepath.exists():
|
|
183
|
+
self.execution_trace_filepath = Path(
|
|
184
|
+
self.execution_trace_dir,
|
|
185
|
+
f'{self.base_execution_trace_file}.{counter}'
|
|
186
|
+
)
|
|
187
|
+
counter += 1
|
|
188
|
+
|
|
189
|
+
self.trace = open(self.execution_trace_filepath, 'w+',
|
|
190
|
+
encoding='utf-8')
|
|
191
|
+
_logger.debug("Opening Amalgam trace file: "
|
|
192
|
+
f"{self.execution_trace_filepath}")
|
|
193
|
+
else:
|
|
194
|
+
self.trace = None
|
|
195
|
+
|
|
196
|
+
_logger.debug(f"Loading amalgam library: {self.library_path}")
|
|
197
|
+
_logger.debug(f"SBF_DATASTORE enabled: {sbf_datastore_enabled}")
|
|
198
|
+
self.amlg = cdll.LoadLibrary(str(self.library_path))
|
|
199
|
+
if sbf_datastore_enabled is not None:
|
|
200
|
+
self.set_amlg_flags(sbf_datastore_enabled)
|
|
201
|
+
if max_num_threads is not None:
|
|
202
|
+
self.set_max_num_threads(max_num_threads)
|
|
203
|
+
self.gc_interval = gc_interval
|
|
204
|
+
self.op_count = 0
|
|
205
|
+
self.load_command_log_entry = None
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def _get_allowed_postfixes(cls, library_dir: Path) -> list[str]:
|
|
209
|
+
"""
|
|
210
|
+
Return list of all library postfixes allowed given library directory.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
library_dir : Path
|
|
215
|
+
The path object to the library directory.
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
list of str
|
|
220
|
+
The allowed library postfixes.
|
|
221
|
+
"""
|
|
222
|
+
allowed_postfixes = set()
|
|
223
|
+
for file in library_dir.glob("amalgam*"):
|
|
224
|
+
postfix = cls._parse_postfix(file.name)
|
|
225
|
+
if postfix is not None:
|
|
226
|
+
allowed_postfixes.add(postfix)
|
|
227
|
+
return list(allowed_postfixes)
|
|
228
|
+
|
|
229
|
+
@classmethod
|
|
230
|
+
def _parse_postfix(cls, filename: str) -> str | None:
|
|
231
|
+
"""
|
|
232
|
+
Determine library postfix given a filename.
|
|
233
|
+
|
|
234
|
+
Parameters
|
|
235
|
+
----------
|
|
236
|
+
filename : str
|
|
237
|
+
The filename to parse.
|
|
238
|
+
|
|
239
|
+
Returns
|
|
240
|
+
-------
|
|
241
|
+
str or None
|
|
242
|
+
The library postfix of the filename, or None if no postfix.
|
|
243
|
+
"""
|
|
244
|
+
matches = re.findall(r'-([^.]+)(?:\.[^.]*)?$', filename)
|
|
245
|
+
if len(matches) > 0:
|
|
246
|
+
return f'-{matches[-1]}'
|
|
247
|
+
else:
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
@classmethod
|
|
251
|
+
def _get_library_path(
|
|
252
|
+
cls,
|
|
253
|
+
library_path: t.Optional[Path | str] = None,
|
|
254
|
+
library_postfix: t.Optional[str] = None,
|
|
255
|
+
arch: t.Optional[str] = None
|
|
256
|
+
) -> tuple[Path, str]:
|
|
257
|
+
"""
|
|
258
|
+
Return the full Amalgam library path and its library_postfix.
|
|
259
|
+
|
|
260
|
+
Using the potentially empty parameters passed into the initializer,
|
|
261
|
+
determine and return the prescribed or the correct default path and
|
|
262
|
+
library postfix for the running environment.
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
library_path : Path or str, optional
|
|
267
|
+
The path to the Amalgam shared library.
|
|
268
|
+
library_postfix : str, optional
|
|
269
|
+
The library type as specified by a postfix to the word
|
|
270
|
+
"amalgam" in the library's filename. E.g., the "-mt" in
|
|
271
|
+
`amalgam-mt.dll`. If left unspecified, "-mt" will be used where
|
|
272
|
+
supported, otherwise "-st".
|
|
273
|
+
arch : str, optional
|
|
274
|
+
The platform architecture of the embedded Amalgam
|
|
275
|
+
library. If not provided, it will be automatically detected.
|
|
276
|
+
(Note: arm64_8a architecture must be manually specified!)
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
Path
|
|
281
|
+
The path to the appropriate Amalgam shared lib (.dll, .so, .dylib).
|
|
282
|
+
str
|
|
283
|
+
The library postfix.
|
|
284
|
+
"""
|
|
285
|
+
if library_postfix and not library_postfix.startswith("-"):
|
|
286
|
+
# Library postfix must start with a dash
|
|
287
|
+
raise ValueError(
|
|
288
|
+
f'The provided `library_postfix` value of "{library_postfix}" '
|
|
289
|
+
'must start with a "-".'
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if library_path:
|
|
293
|
+
# Find the library postfix, if one is present in the given
|
|
294
|
+
# library_path.
|
|
295
|
+
filename = Path(library_path).name
|
|
296
|
+
_library_postfix = cls._parse_postfix(filename)
|
|
297
|
+
if library_postfix and library_postfix != _library_postfix:
|
|
298
|
+
warnings.warn(
|
|
299
|
+
'The supplied `library_postfix` does not match the '
|
|
300
|
+
'postfix given in `library_path` and will be ignored.',
|
|
301
|
+
UserWarning
|
|
302
|
+
)
|
|
303
|
+
library_postfix = _library_postfix
|
|
304
|
+
library_path = Path(library_path).expanduser()
|
|
305
|
+
|
|
306
|
+
if not library_path.exists():
|
|
307
|
+
raise FileNotFoundError(
|
|
308
|
+
'No Amalgam library was found at the provided '
|
|
309
|
+
f'`library_path`: "{library_path}". Please check that the '
|
|
310
|
+
'path is correct.'
|
|
311
|
+
)
|
|
312
|
+
else:
|
|
313
|
+
# No library_path was provided so, auto-determine the correct one
|
|
314
|
+
# to use for this running environment. For this, the operating
|
|
315
|
+
# system, the machine architecture and postfix are used.
|
|
316
|
+
os = platform.system().lower()
|
|
317
|
+
|
|
318
|
+
arch_supported = False
|
|
319
|
+
if not arch:
|
|
320
|
+
arch = platform.machine().lower()
|
|
321
|
+
|
|
322
|
+
if arch == 'x86_64':
|
|
323
|
+
arch = 'amd64'
|
|
324
|
+
elif arch.startswith('aarch64') or arch.startswith('arm64'):
|
|
325
|
+
# see: https://stackoverflow.com/q/45125516/440805
|
|
326
|
+
arch = 'arm64'
|
|
327
|
+
|
|
328
|
+
if os == 'windows':
|
|
329
|
+
path_os = 'windows'
|
|
330
|
+
path_ext = 'dll'
|
|
331
|
+
arch_supported = arch in ['amd64']
|
|
332
|
+
elif os == "darwin":
|
|
333
|
+
path_os = 'darwin'
|
|
334
|
+
path_ext = 'dylib'
|
|
335
|
+
arch_supported = arch in ['amd64', 'arm64']
|
|
336
|
+
elif os == "linux":
|
|
337
|
+
path_os = 'linux'
|
|
338
|
+
path_ext = 'so'
|
|
339
|
+
arch_supported = arch in ['amd64', 'arm64', 'arm64_8a']
|
|
340
|
+
else:
|
|
341
|
+
raise RuntimeError(
|
|
342
|
+
f'Detected an unsupported machine platform type "{os}". '
|
|
343
|
+
'Please specify the `library_path` to the Amalgam shared '
|
|
344
|
+
'library to use with this platform.')
|
|
345
|
+
|
|
346
|
+
if not arch_supported:
|
|
347
|
+
raise RuntimeError(
|
|
348
|
+
f'An unsupported machine architecture "{arch}" was '
|
|
349
|
+
'detected or provided. Please specify the `library_path` '
|
|
350
|
+
'to the Amalgam shared library to use with this machine '
|
|
351
|
+
'architecture.')
|
|
352
|
+
|
|
353
|
+
if not library_postfix:
|
|
354
|
+
library_postfix = '-mt' if arch != "arm64_8a" else '-st'
|
|
355
|
+
|
|
356
|
+
# Default path for Amalgam binary should be at <package_root>/lib
|
|
357
|
+
lib_root = Path(Path(__file__).parent, 'lib')
|
|
358
|
+
|
|
359
|
+
# Build path
|
|
360
|
+
dir_path = Path(lib_root, path_os, arch)
|
|
361
|
+
filename = f'amalgam{library_postfix}.{path_ext}'
|
|
362
|
+
library_path = Path(dir_path, filename)
|
|
363
|
+
|
|
364
|
+
if not library_path.exists():
|
|
365
|
+
# First check if invalid postfix, otherwise show generic error
|
|
366
|
+
allowed_postfixes = cls._get_allowed_postfixes(dir_path)
|
|
367
|
+
_library_postfix = cls._parse_postfix(filename)
|
|
368
|
+
if (
|
|
369
|
+
allowed_postfixes and
|
|
370
|
+
_library_postfix not in allowed_postfixes
|
|
371
|
+
):
|
|
372
|
+
raise RuntimeError(
|
|
373
|
+
'An unsupported `library_postfix` value of '
|
|
374
|
+
f'"{_library_postfix}" was provided. Supported options '
|
|
375
|
+
"for your machine's platform and architecture include: "
|
|
376
|
+
f'{", ".join(allowed_postfixes)}.'
|
|
377
|
+
)
|
|
378
|
+
raise FileNotFoundError(
|
|
379
|
+
'The auto-determined Amalgam library to use was not found '
|
|
380
|
+
f'at "{library_path}". This could indicate that the '
|
|
381
|
+
'combination of operating system, machine architecture and '
|
|
382
|
+
'library-postfix is not yet supported.'
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
return library_path, library_postfix
|
|
386
|
+
|
|
387
|
+
def is_sbf_datastore_enabled(self) -> bool:
|
|
388
|
+
"""
|
|
389
|
+
Return whether the SBF Datastore is implemented.
|
|
390
|
+
|
|
391
|
+
Returns
|
|
392
|
+
-------
|
|
393
|
+
bool
|
|
394
|
+
True if sbf tree structures are currently enabled.
|
|
395
|
+
"""
|
|
396
|
+
self.amlg.IsSBFDataStoreEnabled.restype = c_bool
|
|
397
|
+
return self.amlg.IsSBFDataStoreEnabled()
|
|
398
|
+
|
|
399
|
+
def set_amlg_flags(self, sbf_datastore_enabled: bool = True):
|
|
400
|
+
"""
|
|
401
|
+
Set various amalgam flags for data structure and compute features.
|
|
402
|
+
|
|
403
|
+
Parameters
|
|
404
|
+
----------
|
|
405
|
+
sbf_datastore_enabled : bool, default True
|
|
406
|
+
If true, sbf tree structures are enabled.
|
|
407
|
+
"""
|
|
408
|
+
self.amlg.SetSBFDataStoreEnabled.argtypes = [c_bool]
|
|
409
|
+
self.amlg.SetSBFDataStoreEnabled.restype = c_void_p
|
|
410
|
+
self.amlg.SetSBFDataStoreEnabled(sbf_datastore_enabled)
|
|
411
|
+
|
|
412
|
+
def get_max_num_threads(self) -> int:
|
|
413
|
+
"""
|
|
414
|
+
Get the maximum number of threads currently set.
|
|
415
|
+
|
|
416
|
+
Returns
|
|
417
|
+
-------
|
|
418
|
+
int
|
|
419
|
+
The maximum number of threads that Amalgam is configured to use.
|
|
420
|
+
"""
|
|
421
|
+
self.amlg.GetMaxNumThreads.restype = c_size_t
|
|
422
|
+
self._log_execution("GET_MAX_NUM_THREADS")
|
|
423
|
+
result = self.amlg.GetMaxNumThreads()
|
|
424
|
+
self._log_reply(result)
|
|
425
|
+
|
|
426
|
+
return result
|
|
427
|
+
|
|
428
|
+
def set_max_num_threads(self, max_num_threads: int = 0):
|
|
429
|
+
"""
|
|
430
|
+
Set the maximum number of threads.
|
|
431
|
+
|
|
432
|
+
Will have no effect if a single-threaded version of Amalgam is used.
|
|
433
|
+
|
|
434
|
+
Parameters
|
|
435
|
+
----------
|
|
436
|
+
max_num_threads : int, default 0
|
|
437
|
+
If a multithreaded Amalgam binary is used, sets the maximum number
|
|
438
|
+
of threads to the value specified. If 0, will use the number of
|
|
439
|
+
visible logical cores.
|
|
440
|
+
"""
|
|
441
|
+
self.amlg.SetMaxNumThreads.argtypes = [c_size_t]
|
|
442
|
+
self.amlg.SetMaxNumThreads.restype = c_void_p
|
|
443
|
+
|
|
444
|
+
self._log_execution(f"SET_MAX_NUM_THREADS {max_num_threads}")
|
|
445
|
+
result = self.amlg.SetMaxNumThreads(max_num_threads)
|
|
446
|
+
self._log_reply(result)
|
|
447
|
+
|
|
448
|
+
def reset_trace(self, file: str):
|
|
449
|
+
"""
|
|
450
|
+
Close the open trace file and opens a new one with the specified name.
|
|
451
|
+
|
|
452
|
+
Parameters
|
|
453
|
+
----------
|
|
454
|
+
file : str
|
|
455
|
+
The file name for the new execution trace.
|
|
456
|
+
"""
|
|
457
|
+
if self.trace is None:
|
|
458
|
+
# Trace was not enabled
|
|
459
|
+
return
|
|
460
|
+
_logger.debug(f"Execution trace file being reset: "
|
|
461
|
+
f"{self.execution_trace_filepath} to be closed ...")
|
|
462
|
+
# Write exit command.
|
|
463
|
+
self.trace.write("EXIT\n")
|
|
464
|
+
self.trace.close()
|
|
465
|
+
self.execution_trace_filepath = Path(self.execution_trace_dir, file)
|
|
466
|
+
|
|
467
|
+
# increment a counter on the file name, if file already exists..
|
|
468
|
+
if not self.append_trace_file:
|
|
469
|
+
counter = 1
|
|
470
|
+
while self.execution_trace_filepath.exists():
|
|
471
|
+
self.execution_trace_filepath = Path(
|
|
472
|
+
self.execution_trace_dir, f'{file}.{counter}')
|
|
473
|
+
counter += 1
|
|
474
|
+
|
|
475
|
+
self.trace = open(self.execution_trace_filepath, 'w+')
|
|
476
|
+
_logger.debug(f"New trace file: {self.execution_trace_filepath} "
|
|
477
|
+
f"opened.")
|
|
478
|
+
# Write load command used to instantiate the amalgam instance.
|
|
479
|
+
if self.load_command_log_entry is not None:
|
|
480
|
+
self.trace.write(self.load_command_log_entry + "\n")
|
|
481
|
+
self.trace.flush()
|
|
482
|
+
|
|
483
|
+
def __str__(self) -> str:
|
|
484
|
+
"""Return a human-readable string representation."""
|
|
485
|
+
return (f"Amalgam Path:\t\t {self.library_path}\n"
|
|
486
|
+
f"Amalgam GC Interval:\t {self.gc_interval}\n")
|
|
487
|
+
|
|
488
|
+
def __del__(self):
|
|
489
|
+
"""Implement a "destructor" method to finalize log files, if any."""
|
|
490
|
+
if (
|
|
491
|
+
getattr(self, 'debug', False) and
|
|
492
|
+
getattr(self, 'trace', None) is not None
|
|
493
|
+
):
|
|
494
|
+
try:
|
|
495
|
+
self.trace.write("EXIT\n")
|
|
496
|
+
except Exception: # noqa - deliberately broad
|
|
497
|
+
pass
|
|
498
|
+
|
|
499
|
+
def _log_comment(self, comment: str):
|
|
500
|
+
"""
|
|
501
|
+
Log a comment into the execution trace file.
|
|
502
|
+
|
|
503
|
+
Allows notes of information not captured in the raw execution commands.
|
|
504
|
+
|
|
505
|
+
Parameters
|
|
506
|
+
----------
|
|
507
|
+
reply : str
|
|
508
|
+
The raw reply string to log.
|
|
509
|
+
"""
|
|
510
|
+
if self.trace:
|
|
511
|
+
self.trace.write("# NOTE >" + str(comment) + "\n")
|
|
512
|
+
self.trace.flush()
|
|
513
|
+
|
|
514
|
+
def _log_reply(self, reply: t.Any):
|
|
515
|
+
"""
|
|
516
|
+
Log a raw reply from the amalgam process.
|
|
517
|
+
|
|
518
|
+
Uses a pre-pended '#RESULT >' so it can be filtered by tools like grep.
|
|
519
|
+
|
|
520
|
+
Parameters
|
|
521
|
+
----------
|
|
522
|
+
reply : Any
|
|
523
|
+
The raw reply string to log.
|
|
524
|
+
"""
|
|
525
|
+
if self.trace:
|
|
526
|
+
self.trace.write("# RESULT >" + str(reply) + "\n")
|
|
527
|
+
self.trace.flush()
|
|
528
|
+
|
|
529
|
+
def _log_time(self, label: str):
|
|
530
|
+
"""
|
|
531
|
+
Log a labelled timestamp to the trace file.
|
|
532
|
+
|
|
533
|
+
Parameters
|
|
534
|
+
----------
|
|
535
|
+
label: str
|
|
536
|
+
A string to annotate the timestamped trace entry
|
|
537
|
+
"""
|
|
538
|
+
if self.trace:
|
|
539
|
+
dt = datetime.now()
|
|
540
|
+
self.trace.write(f"# TIME {label} {dt:%Y-%m-%d %H:%M:%S},"
|
|
541
|
+
f"{f'{dt:%f}'[:3]}\n")
|
|
542
|
+
self.trace.flush()
|
|
543
|
+
|
|
544
|
+
def _log_execution(self, execution_string: str):
|
|
545
|
+
"""
|
|
546
|
+
Log an execution string.
|
|
547
|
+
|
|
548
|
+
Logs an execution string that is sent to the amalgam process for use in
|
|
549
|
+
command line debugging.
|
|
550
|
+
|
|
551
|
+
Parameters
|
|
552
|
+
----------
|
|
553
|
+
execution_string : str
|
|
554
|
+
A formatted string that can be piped into an amalgam command line
|
|
555
|
+
process for use in debugging.
|
|
556
|
+
|
|
557
|
+
.. NOTE::
|
|
558
|
+
No formatting checks are performed, it is assumed the execution
|
|
559
|
+
string passed is valid.
|
|
560
|
+
"""
|
|
561
|
+
if self.trace:
|
|
562
|
+
self.trace.write(execution_string + "\n")
|
|
563
|
+
self.trace.flush()
|
|
564
|
+
|
|
565
|
+
def gc(self):
|
|
566
|
+
"""Force garbage collection when called if self.force_gc is set."""
|
|
567
|
+
if (
|
|
568
|
+
self.gc_interval is not None
|
|
569
|
+
and self.op_count > self.gc_interval
|
|
570
|
+
):
|
|
571
|
+
_logger.debug("Collecting Garbage")
|
|
572
|
+
gc.collect()
|
|
573
|
+
self.op_count = 0
|
|
574
|
+
self.op_count += 1
|
|
575
|
+
|
|
576
|
+
def str_to_char_p(
|
|
577
|
+
self,
|
|
578
|
+
value: str | bytes,
|
|
579
|
+
size: t.Optional[int] = None
|
|
580
|
+
) -> Array[c_char]:
|
|
581
|
+
"""
|
|
582
|
+
Convert a string to an Array of C char.
|
|
583
|
+
|
|
584
|
+
User must call `del` on returned buffer
|
|
585
|
+
|
|
586
|
+
Parameters
|
|
587
|
+
----------
|
|
588
|
+
value : str or bytes
|
|
589
|
+
The value of the string.
|
|
590
|
+
size : int, optional
|
|
591
|
+
The size of the string. If not provided, the length of
|
|
592
|
+
the string is used.
|
|
593
|
+
|
|
594
|
+
Returns
|
|
595
|
+
-------
|
|
596
|
+
Array of c_char
|
|
597
|
+
An Array of C char datatypes which form the given string
|
|
598
|
+
"""
|
|
599
|
+
if isinstance(value, str):
|
|
600
|
+
value = value.encode('utf-8')
|
|
601
|
+
buftype = c_char * (size if size is not None else (len(value) + 1))
|
|
602
|
+
buf = buftype()
|
|
603
|
+
buf.value = value
|
|
604
|
+
return buf
|
|
605
|
+
|
|
606
|
+
def char_p_to_bytes(self, p: _Pointer[c_char] | c_char_p) -> bytes | None:
|
|
607
|
+
"""
|
|
608
|
+
Copy native C char pointer to bytes, cleaning up memory correctly.
|
|
609
|
+
|
|
610
|
+
Parameters
|
|
611
|
+
----------
|
|
612
|
+
p : c_char_p
|
|
613
|
+
The char pointer to convert
|
|
614
|
+
|
|
615
|
+
Returns
|
|
616
|
+
-------
|
|
617
|
+
bytes or None
|
|
618
|
+
The byte-encoded char
|
|
619
|
+
"""
|
|
620
|
+
bytes_str = cast(p, c_char_p).value
|
|
621
|
+
|
|
622
|
+
self.amlg.DeleteString.argtypes = [c_char_p]
|
|
623
|
+
self.amlg.DeleteString.restype = None
|
|
624
|
+
self.amlg.DeleteString(p)
|
|
625
|
+
|
|
626
|
+
return bytes_str
|
|
627
|
+
|
|
628
|
+
def get_json_from_label(self, handle: str, label: str) -> bytes:
|
|
629
|
+
"""
|
|
630
|
+
Get a label from amalgam and returns it in json format.
|
|
631
|
+
|
|
632
|
+
Parameters
|
|
633
|
+
----------
|
|
634
|
+
handle : str
|
|
635
|
+
The handle of the amalgam entity.
|
|
636
|
+
label : str
|
|
637
|
+
The label to retrieve.
|
|
638
|
+
|
|
639
|
+
Returns
|
|
640
|
+
-------
|
|
641
|
+
bytes
|
|
642
|
+
The byte-encoded json representation of the amalgam label.
|
|
643
|
+
"""
|
|
644
|
+
self.amlg.GetJSONPtrFromLabel.restype = POINTER(c_char)
|
|
645
|
+
self.amlg.GetJSONPtrFromLabel.argtypes = [c_char_p, c_char_p]
|
|
646
|
+
handle_buf = self.str_to_char_p(handle)
|
|
647
|
+
label_buf = self.str_to_char_p(label)
|
|
648
|
+
|
|
649
|
+
self._log_execution((
|
|
650
|
+
f"GET_JSON_FROM_LABEL \"{self.escape_double_quotes(handle)}\" "
|
|
651
|
+
f"\"{self.escape_double_quotes(label)}\""
|
|
652
|
+
))
|
|
653
|
+
result = self.char_p_to_bytes(self.amlg.GetJSONPtrFromLabel(handle_buf, label_buf))
|
|
654
|
+
self._log_reply(result)
|
|
655
|
+
|
|
656
|
+
del handle_buf
|
|
657
|
+
del label_buf
|
|
658
|
+
self.gc()
|
|
659
|
+
|
|
660
|
+
return result
|
|
661
|
+
|
|
662
|
+
def set_json_to_label(
|
|
663
|
+
self,
|
|
664
|
+
handle: str,
|
|
665
|
+
label: str,
|
|
666
|
+
json: str | bytes
|
|
667
|
+
):
|
|
668
|
+
"""
|
|
669
|
+
Set a label in amalgam using json.
|
|
670
|
+
|
|
671
|
+
Parameters
|
|
672
|
+
----------
|
|
673
|
+
handle : str
|
|
674
|
+
The handle of the amalgam entity.
|
|
675
|
+
label : str
|
|
676
|
+
The label to set.
|
|
677
|
+
json : str or bytes
|
|
678
|
+
The json representation of the label value.
|
|
679
|
+
"""
|
|
680
|
+
self.amlg.SetJSONToLabel.restype = c_void_p
|
|
681
|
+
self.amlg.SetJSONToLabel.argtypes = [c_char_p, c_char_p, c_char_p]
|
|
682
|
+
handle_buf = self.str_to_char_p(handle)
|
|
683
|
+
label_buf = self.str_to_char_p(label)
|
|
684
|
+
json_buf = self.str_to_char_p(json)
|
|
685
|
+
|
|
686
|
+
self._log_execution((
|
|
687
|
+
f"SET_JSON_TO_LABEL \"{self.escape_double_quotes(handle)}\" "
|
|
688
|
+
f"\"{self.escape_double_quotes(label)}\" "
|
|
689
|
+
f"{json}"
|
|
690
|
+
))
|
|
691
|
+
self.amlg.SetJSONToLabel(handle_buf, label_buf, json_buf)
|
|
692
|
+
self._log_reply(None)
|
|
693
|
+
|
|
694
|
+
del handle_buf
|
|
695
|
+
del label_buf
|
|
696
|
+
del json_buf
|
|
697
|
+
self.gc()
|
|
698
|
+
|
|
699
|
+
def load_entity(
|
|
700
|
+
self,
|
|
701
|
+
handle: str,
|
|
702
|
+
file_path: str,
|
|
703
|
+
*,
|
|
704
|
+
file_type: str = "",
|
|
705
|
+
persist: bool = False,
|
|
706
|
+
json_file_params: str = "",
|
|
707
|
+
write_log: str = "",
|
|
708
|
+
print_log: str = ""
|
|
709
|
+
) -> LoadEntityStatus:
|
|
710
|
+
"""
|
|
711
|
+
Load an entity from an amalgam source file.
|
|
712
|
+
|
|
713
|
+
Parameters
|
|
714
|
+
----------
|
|
715
|
+
handle : str
|
|
716
|
+
The handle to assign the entity.
|
|
717
|
+
file_path : str
|
|
718
|
+
The path of the file name to load.
|
|
719
|
+
file_type : str, default ""
|
|
720
|
+
If set to a nonempty string, will represent the type of file to load.
|
|
721
|
+
persist : bool, default False
|
|
722
|
+
If set to true, all transactions that update the entity will also be
|
|
723
|
+
written to the files.
|
|
724
|
+
json_file_params : str, default ""
|
|
725
|
+
Either empty string or a string of json specifying a set of key-value pairs
|
|
726
|
+
which are parameters specific to the file type. See Amalgam documentation
|
|
727
|
+
for details of allowed parameters.
|
|
728
|
+
write_log : str, default ""
|
|
729
|
+
Path to the write log. If empty string, the write log is
|
|
730
|
+
not generated.
|
|
731
|
+
print_log : str, default ""
|
|
732
|
+
Path to the print log. If empty string, the print log is
|
|
733
|
+
not generated.
|
|
734
|
+
|
|
735
|
+
Returns
|
|
736
|
+
-------
|
|
737
|
+
LoadEntityStatus
|
|
738
|
+
Status of LoadEntity call.
|
|
739
|
+
"""
|
|
740
|
+
self.amlg.LoadEntity.argtypes = [
|
|
741
|
+
c_char_p, c_char_p, c_char_p, c_bool, c_char_p, c_char_p, c_char_p]
|
|
742
|
+
self.amlg.LoadEntity.restype = _LoadEntityStatus
|
|
743
|
+
handle_buf = self.str_to_char_p(handle)
|
|
744
|
+
file_path_buf = self.str_to_char_p(file_path)
|
|
745
|
+
file_type_buf = self.str_to_char_p(file_type)
|
|
746
|
+
json_file_params_buf = self.str_to_char_p(json_file_params)
|
|
747
|
+
write_log_buf = self.str_to_char_p(write_log)
|
|
748
|
+
print_log_buf = self.str_to_char_p(print_log)
|
|
749
|
+
|
|
750
|
+
load_command_log_entry = (
|
|
751
|
+
f"LOAD_ENTITY \"{self.escape_double_quotes(handle)}\" "
|
|
752
|
+
f"\"{self.escape_double_quotes(file_path)}\" "
|
|
753
|
+
f"\"{self.escape_double_quotes(file_type)}\" {str(persist).lower()} "
|
|
754
|
+
f"{json_lib.dumps(json_file_params)} "
|
|
755
|
+
f"\"{write_log}\" \"{print_log}\""
|
|
756
|
+
)
|
|
757
|
+
self._log_execution(load_command_log_entry)
|
|
758
|
+
result = LoadEntityStatus(self, self.amlg.LoadEntity(
|
|
759
|
+
handle_buf, file_path_buf, file_type_buf, persist,
|
|
760
|
+
json_file_params_buf, write_log_buf, print_log_buf))
|
|
761
|
+
self._log_reply(result)
|
|
762
|
+
|
|
763
|
+
del handle_buf
|
|
764
|
+
del file_path_buf
|
|
765
|
+
del file_type_buf
|
|
766
|
+
del json_file_params_buf
|
|
767
|
+
del write_log_buf
|
|
768
|
+
del print_log_buf
|
|
769
|
+
self.gc()
|
|
770
|
+
|
|
771
|
+
return result
|
|
772
|
+
|
|
773
|
+
def verify_entity(
|
|
774
|
+
self,
|
|
775
|
+
file_path: str
|
|
776
|
+
) -> LoadEntityStatus:
|
|
777
|
+
"""
|
|
778
|
+
Verify an entity from an amalgam source file.
|
|
779
|
+
|
|
780
|
+
Parameters
|
|
781
|
+
----------
|
|
782
|
+
file_path : str
|
|
783
|
+
The path to the filename.amlg/caml file.
|
|
784
|
+
|
|
785
|
+
Returns
|
|
786
|
+
-------
|
|
787
|
+
LoadEntityStatus
|
|
788
|
+
Status of VerifyEntity call.
|
|
789
|
+
"""
|
|
790
|
+
self.amlg.VerifyEntity.argtypes = [c_char_p]
|
|
791
|
+
self.amlg.VerifyEntity.restype = _LoadEntityStatus
|
|
792
|
+
file_path_buf = self.str_to_char_p(file_path)
|
|
793
|
+
|
|
794
|
+
self._log_execution(f"VERIFY_ENTITY \"{self.escape_double_quotes(file_path)}\"")
|
|
795
|
+
result = LoadEntityStatus(self, self.amlg.VerifyEntity(file_path_buf))
|
|
796
|
+
self._log_reply(result)
|
|
797
|
+
|
|
798
|
+
del file_path_buf
|
|
799
|
+
self.gc()
|
|
800
|
+
|
|
801
|
+
return result
|
|
802
|
+
|
|
803
|
+
def clone_entity(
|
|
804
|
+
self,
|
|
805
|
+
handle: str,
|
|
806
|
+
clone_handle: str,
|
|
807
|
+
*,
|
|
808
|
+
file_path: str = "",
|
|
809
|
+
file_type: str = "",
|
|
810
|
+
persist: bool = False,
|
|
811
|
+
json_file_params: str = "",
|
|
812
|
+
write_log: str = "",
|
|
813
|
+
print_log: str = ""
|
|
814
|
+
) -> bool:
|
|
815
|
+
"""
|
|
816
|
+
Clones entity specified by handle into a new entity specified by clone_handle.
|
|
817
|
+
|
|
818
|
+
Parameters
|
|
819
|
+
----------
|
|
820
|
+
handle : str
|
|
821
|
+
The handle of the amalgam entity to clone.
|
|
822
|
+
clone_handle : str
|
|
823
|
+
The handle to clone the entity into.
|
|
824
|
+
file_path : str, default ""
|
|
825
|
+
The path of the file name to load.
|
|
826
|
+
file_type : str, default ""
|
|
827
|
+
If set to a nonempty string, will represent the type of file to load.
|
|
828
|
+
persist : bool, default False
|
|
829
|
+
If set to true, all transactions that update the entity will also be
|
|
830
|
+
written to the files.
|
|
831
|
+
json_file_params : str, default ""
|
|
832
|
+
Either empty string or a string of json specifying a set of key-value pairs
|
|
833
|
+
which are parameters specific to the file type. See Amalgam documentation
|
|
834
|
+
for details of allowed parameters.
|
|
835
|
+
write_log : str, default ""
|
|
836
|
+
Path to the write log. If empty string, the write log is
|
|
837
|
+
not generated.
|
|
838
|
+
print_log : str, default ""
|
|
839
|
+
Path to the print log. If empty string, the print log is
|
|
840
|
+
not generated.
|
|
841
|
+
|
|
842
|
+
Returns
|
|
843
|
+
-------
|
|
844
|
+
bool
|
|
845
|
+
True if cloned successfully, False if not.
|
|
846
|
+
"""
|
|
847
|
+
self.amlg.CloneEntity.argtypes = [
|
|
848
|
+
c_char_p, c_char_p, c_char_p, c_char_p, c_bool, c_char_p, c_char_p, c_char_p]
|
|
849
|
+
handle_buf = self.str_to_char_p(handle)
|
|
850
|
+
clone_handle_buf = self.str_to_char_p(clone_handle)
|
|
851
|
+
file_path_buf = self.str_to_char_p(file_path)
|
|
852
|
+
file_type_buf = self.str_to_char_p(file_type)
|
|
853
|
+
json_file_params_buf = self.str_to_char_p(json_file_params)
|
|
854
|
+
write_log_buf = self.str_to_char_p(write_log)
|
|
855
|
+
print_log_buf = self.str_to_char_p(print_log)
|
|
856
|
+
|
|
857
|
+
clone_command_log_entry = (
|
|
858
|
+
f'CLONE_ENTITY "{self.escape_double_quotes(handle)}" '
|
|
859
|
+
f'"{self.escape_double_quotes(clone_handle)}" '
|
|
860
|
+
f"\"{self.escape_double_quotes(file_path)}\" "
|
|
861
|
+
f"\"{self.escape_double_quotes(file_type)}\" {str(persist).lower()} "
|
|
862
|
+
f"{json_lib.dumps(json_file_params)} "
|
|
863
|
+
f"\"{write_log}\" \"{print_log}\""
|
|
864
|
+
)
|
|
865
|
+
self._log_execution(clone_command_log_entry)
|
|
866
|
+
result = self.amlg.CloneEntity(
|
|
867
|
+
handle_buf, clone_handle_buf, file_path_buf, file_type_buf, persist,
|
|
868
|
+
json_file_params_buf, write_log_buf, print_log_buf)
|
|
869
|
+
self._log_reply(result)
|
|
870
|
+
|
|
871
|
+
del handle_buf
|
|
872
|
+
del clone_handle_buf
|
|
873
|
+
del file_path_buf
|
|
874
|
+
del file_type_buf
|
|
875
|
+
del json_file_params_buf
|
|
876
|
+
del write_log_buf
|
|
877
|
+
del print_log_buf
|
|
878
|
+
self.gc()
|
|
879
|
+
|
|
880
|
+
return result
|
|
881
|
+
|
|
882
|
+
def store_entity(
|
|
883
|
+
self,
|
|
884
|
+
handle: str,
|
|
885
|
+
file_path: str,
|
|
886
|
+
*,
|
|
887
|
+
file_type: str = "",
|
|
888
|
+
persist: bool = False,
|
|
889
|
+
json_file_params: str = "",
|
|
890
|
+
):
|
|
891
|
+
"""
|
|
892
|
+
Store entity to the file type specified within file_path.
|
|
893
|
+
|
|
894
|
+
Parameters
|
|
895
|
+
----------
|
|
896
|
+
handle : str
|
|
897
|
+
The handle of the amalgam entity.
|
|
898
|
+
file_path : str
|
|
899
|
+
The path of the file name to load.
|
|
900
|
+
file_type : str, default ""
|
|
901
|
+
If set to a nonempty string, will represent the type of file to load.
|
|
902
|
+
persist : bool, default False
|
|
903
|
+
If set to true, all transactions that update the entity will also be
|
|
904
|
+
written to the files.
|
|
905
|
+
json_file_params : str, default ""
|
|
906
|
+
Either empty string or a string of json specifying a set of key-value pairs
|
|
907
|
+
which are parameters specific to the file type. See Amalgam documentation
|
|
908
|
+
for details of allowed parameters.
|
|
909
|
+
"""
|
|
910
|
+
self.amlg.StoreEntity.argtypes = [
|
|
911
|
+
c_char_p, c_char_p, c_char_p, c_bool, c_char_p]
|
|
912
|
+
handle_buf = self.str_to_char_p(handle)
|
|
913
|
+
file_path_buf = self.str_to_char_p(file_path)
|
|
914
|
+
file_type_buf = self.str_to_char_p(file_type)
|
|
915
|
+
json_file_params_buf = self.str_to_char_p(json_file_params)
|
|
916
|
+
|
|
917
|
+
store_command_log_entry = (
|
|
918
|
+
f"STORE_ENTITY \"{self.escape_double_quotes(handle)}\" "
|
|
919
|
+
f"\"{self.escape_double_quotes(file_path)}\" "
|
|
920
|
+
f"\"{self.escape_double_quotes(file_type)}\" {str(persist).lower()} "
|
|
921
|
+
f"{json_lib.dumps(json_file_params)} "
|
|
922
|
+
)
|
|
923
|
+
self._log_execution(store_command_log_entry)
|
|
924
|
+
self.amlg.StoreEntity(
|
|
925
|
+
handle_buf, file_path_buf, file_type_buf, persist, json_file_params_buf)
|
|
926
|
+
self._log_reply(None)
|
|
927
|
+
|
|
928
|
+
del handle_buf
|
|
929
|
+
del file_path_buf
|
|
930
|
+
del file_type_buf
|
|
931
|
+
del json_file_params_buf
|
|
932
|
+
self.gc()
|
|
933
|
+
|
|
934
|
+
def destroy_entity(
|
|
935
|
+
self,
|
|
936
|
+
handle: str
|
|
937
|
+
):
|
|
938
|
+
"""
|
|
939
|
+
Destroys an entity.
|
|
940
|
+
|
|
941
|
+
Parameters
|
|
942
|
+
----------
|
|
943
|
+
handle : str
|
|
944
|
+
The handle of the amalgam entity.
|
|
945
|
+
"""
|
|
946
|
+
self.amlg.DestroyEntity.argtypes = [c_char_p]
|
|
947
|
+
handle_buf = self.str_to_char_p(handle)
|
|
948
|
+
|
|
949
|
+
self._log_execution(f"DESTROY_ENTITY \"{self.escape_double_quotes(handle)}\"")
|
|
950
|
+
self.amlg.DestroyEntity(handle_buf)
|
|
951
|
+
self._log_reply(None)
|
|
952
|
+
|
|
953
|
+
del handle_buf
|
|
954
|
+
self.gc()
|
|
955
|
+
|
|
956
|
+
def set_random_seed(
|
|
957
|
+
self,
|
|
958
|
+
handle: str,
|
|
959
|
+
rand_seed: str
|
|
960
|
+
) -> bool:
|
|
961
|
+
"""
|
|
962
|
+
Set entity's random seed.
|
|
963
|
+
|
|
964
|
+
Parameters
|
|
965
|
+
----------
|
|
966
|
+
handle : str
|
|
967
|
+
The handle of the amalgam entity.
|
|
968
|
+
rand_seed : str
|
|
969
|
+
A string representing the random seed to set.
|
|
970
|
+
|
|
971
|
+
Returns
|
|
972
|
+
-------
|
|
973
|
+
bool
|
|
974
|
+
True if the set was successful, false if not.
|
|
975
|
+
"""
|
|
976
|
+
self.amlg.SetRandomSeed.argtypes = [c_char_p, c_char_p]
|
|
977
|
+
self.amlg.SetRandomSeed.restype = c_bool
|
|
978
|
+
|
|
979
|
+
handle_buf = self.str_to_char_p(handle)
|
|
980
|
+
rand_seed_buf = self.str_to_char_p(rand_seed)
|
|
981
|
+
|
|
982
|
+
self._log_execution(f'SET_RANDOM_SEED "{self.escape_double_quotes(handle)}"'
|
|
983
|
+
f'"{self.escape_double_quotes(rand_seed)}"')
|
|
984
|
+
result = self.amlg.SetRandomSeed(handle_buf, rand_seed)
|
|
985
|
+
self._log_reply(None)
|
|
986
|
+
|
|
987
|
+
del handle_buf
|
|
988
|
+
del rand_seed_buf
|
|
989
|
+
self.gc()
|
|
990
|
+
return result
|
|
991
|
+
|
|
992
|
+
def get_entities(self) -> list[str]:
|
|
993
|
+
"""
|
|
994
|
+
Get loaded top level entities.
|
|
995
|
+
|
|
996
|
+
Returns
|
|
997
|
+
-------
|
|
998
|
+
list of str
|
|
999
|
+
The list of entity handles.
|
|
1000
|
+
"""
|
|
1001
|
+
self.amlg.GetEntities.argtypes = [POINTER(c_uint64)]
|
|
1002
|
+
self.amlg.GetEntities.restype = POINTER(c_char_p)
|
|
1003
|
+
num_entities = c_uint64()
|
|
1004
|
+
entities = self.amlg.GetEntities(byref(num_entities))
|
|
1005
|
+
result = [entities[i].decode() for i in range(num_entities.value)]
|
|
1006
|
+
|
|
1007
|
+
del entities
|
|
1008
|
+
del num_entities
|
|
1009
|
+
self.gc()
|
|
1010
|
+
|
|
1011
|
+
return result
|
|
1012
|
+
|
|
1013
|
+
def execute_entity_json(
|
|
1014
|
+
self,
|
|
1015
|
+
handle: str,
|
|
1016
|
+
label: str,
|
|
1017
|
+
json: str | bytes
|
|
1018
|
+
) -> bytes:
|
|
1019
|
+
"""
|
|
1020
|
+
Execute a label with parameters provided in json format.
|
|
1021
|
+
|
|
1022
|
+
Parameters
|
|
1023
|
+
----------
|
|
1024
|
+
handle : str
|
|
1025
|
+
The handle of the amalgam entity.
|
|
1026
|
+
label : str
|
|
1027
|
+
The label to execute.
|
|
1028
|
+
json : str or bytes
|
|
1029
|
+
A json representation of parameters for the label to be executed.
|
|
1030
|
+
|
|
1031
|
+
Returns
|
|
1032
|
+
-------
|
|
1033
|
+
bytes
|
|
1034
|
+
A byte-encoded json representation of the response.
|
|
1035
|
+
"""
|
|
1036
|
+
self.amlg.ExecuteEntityJsonPtr.restype = POINTER(c_char)
|
|
1037
|
+
self.amlg.ExecuteEntityJsonPtr.argtypes = [
|
|
1038
|
+
c_char_p, c_char_p, c_char_p]
|
|
1039
|
+
handle_buf = self.str_to_char_p(handle)
|
|
1040
|
+
label_buf = self.str_to_char_p(label)
|
|
1041
|
+
json_buf = self.str_to_char_p(json)
|
|
1042
|
+
|
|
1043
|
+
self._log_time("EXECUTION START")
|
|
1044
|
+
self._log_execution((
|
|
1045
|
+
"EXECUTE_ENTITY_JSON "
|
|
1046
|
+
f"\"{self.escape_double_quotes(handle)}\" "
|
|
1047
|
+
f"\"{self.escape_double_quotes(label)}\" "
|
|
1048
|
+
f"{json}"
|
|
1049
|
+
))
|
|
1050
|
+
result = self.char_p_to_bytes(self.amlg.ExecuteEntityJsonPtr(
|
|
1051
|
+
handle_buf, label_buf, json_buf))
|
|
1052
|
+
self._log_time("EXECUTION STOP")
|
|
1053
|
+
self._log_reply(result)
|
|
1054
|
+
|
|
1055
|
+
del handle_buf
|
|
1056
|
+
del label_buf
|
|
1057
|
+
del json_buf
|
|
1058
|
+
|
|
1059
|
+
return result
|
|
1060
|
+
|
|
1061
|
+
def get_version_string(self) -> bytes:
|
|
1062
|
+
"""
|
|
1063
|
+
Get the version string of the amalgam dynamic library.
|
|
1064
|
+
|
|
1065
|
+
Returns
|
|
1066
|
+
-------
|
|
1067
|
+
bytes
|
|
1068
|
+
A version byte-encoded string with semver.
|
|
1069
|
+
"""
|
|
1070
|
+
self.amlg.GetVersionString.restype = POINTER(c_char)
|
|
1071
|
+
amlg_version = self.char_p_to_bytes(self.amlg.GetVersionString())
|
|
1072
|
+
self._log_comment(f"call to amlg.GetVersionString() - returned: "
|
|
1073
|
+
f"{amlg_version}\n")
|
|
1074
|
+
return amlg_version
|
|
1075
|
+
|
|
1076
|
+
def get_concurrency_type_string(self) -> bytes:
|
|
1077
|
+
"""
|
|
1078
|
+
Get the concurrency type string of the amalgam dynamic library.
|
|
1079
|
+
|
|
1080
|
+
Returns
|
|
1081
|
+
-------
|
|
1082
|
+
bytes
|
|
1083
|
+
A byte-encoded string with library concurrency type.
|
|
1084
|
+
Ex. b'MultiThreaded'
|
|
1085
|
+
"""
|
|
1086
|
+
self.amlg.GetConcurrencyTypeString.restype = POINTER(c_char)
|
|
1087
|
+
amlg_concurrency_type = self.char_p_to_bytes(self.amlg.GetConcurrencyTypeString())
|
|
1088
|
+
self._log_comment(
|
|
1089
|
+
f"call to amlg.GetConcurrencyTypeString() - returned: "
|
|
1090
|
+
f"{amlg_concurrency_type}\n")
|
|
1091
|
+
return amlg_concurrency_type
|
|
1092
|
+
|
|
1093
|
+
@staticmethod
|
|
1094
|
+
def escape_double_quotes(s: str) -> str:
|
|
1095
|
+
"""
|
|
1096
|
+
Get the string with backslashes preceding contained double quotes.
|
|
1097
|
+
|
|
1098
|
+
Parameters
|
|
1099
|
+
----------
|
|
1100
|
+
s : str
|
|
1101
|
+
The input string.
|
|
1102
|
+
|
|
1103
|
+
Returns
|
|
1104
|
+
-------
|
|
1105
|
+
str
|
|
1106
|
+
The modified version of s with escaped double quotes.
|
|
1107
|
+
"""
|
|
1108
|
+
return s.replace('"', '\\"')
|