declib 3.8.0__py3-none-any.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.
Files changed (71) hide show
  1. declib/__init__.py +9 -0
  2. declib/__main__.py +190 -0
  3. declib/api/__init__.py +13 -0
  4. declib/api/artifact_dict.py +153 -0
  5. declib/api/artifact_lifter.py +161 -0
  6. declib/api/decompiler_client.py +1219 -0
  7. declib/api/decompiler_interface.py +1261 -0
  8. declib/api/decompiler_server.py +782 -0
  9. declib/api/server_registry.py +171 -0
  10. declib/api/type_definition_parser.py +201 -0
  11. declib/api/type_parser.py +409 -0
  12. declib/api/utils.py +31 -0
  13. declib/artifacts/__init__.py +93 -0
  14. declib/artifacts/artifact.py +311 -0
  15. declib/artifacts/comment.py +49 -0
  16. declib/artifacts/context.py +61 -0
  17. declib/artifacts/decompilation.py +35 -0
  18. declib/artifacts/enum.py +53 -0
  19. declib/artifacts/formatting.py +27 -0
  20. declib/artifacts/func.py +433 -0
  21. declib/artifacts/global_variable.py +31 -0
  22. declib/artifacts/patch.py +49 -0
  23. declib/artifacts/segment.py +37 -0
  24. declib/artifacts/stack_variable.py +50 -0
  25. declib/artifacts/struct.py +184 -0
  26. declib/artifacts/typedef.py +59 -0
  27. declib/cli/__init__.py +3 -0
  28. declib/cli/decompiler_cli.py +1487 -0
  29. declib/configuration.py +184 -0
  30. declib/decompiler_stubs/__init__.py +0 -0
  31. declib/decompiler_stubs/angr_declib/__init__.py +4 -0
  32. declib/decompiler_stubs/binja_declib/__init__.py +4 -0
  33. declib/decompiler_stubs/ida_declib.py +8 -0
  34. declib/decompilers/__init__.py +8 -0
  35. declib/decompilers/angr/__init__.py +11 -0
  36. declib/decompilers/angr/artifact_lifter.py +46 -0
  37. declib/decompilers/angr/compat.py +262 -0
  38. declib/decompilers/angr/interface.py +949 -0
  39. declib/decompilers/binja/__init__.py +0 -0
  40. declib/decompilers/binja/artifact_lifter.py +32 -0
  41. declib/decompilers/binja/hooks.py +201 -0
  42. declib/decompilers/binja/interface.py +795 -0
  43. declib/decompilers/ghidra/__init__.py +0 -0
  44. declib/decompilers/ghidra/artifact_lifter.py +60 -0
  45. declib/decompilers/ghidra/compat/__init__.py +0 -0
  46. declib/decompilers/ghidra/compat/headless.py +156 -0
  47. declib/decompilers/ghidra/compat/imports.py +78 -0
  48. declib/decompilers/ghidra/compat/state.py +54 -0
  49. declib/decompilers/ghidra/compat/transaction.py +30 -0
  50. declib/decompilers/ghidra/hooks.py +242 -0
  51. declib/decompilers/ghidra/interface.py +1433 -0
  52. declib/decompilers/ida/__init__.py +0 -0
  53. declib/decompilers/ida/artifact_lifter.py +51 -0
  54. declib/decompilers/ida/compat.py +2054 -0
  55. declib/decompilers/ida/hooks.py +700 -0
  56. declib/decompilers/ida/ida_ui.py +80 -0
  57. declib/decompilers/ida/interface.py +659 -0
  58. declib/logger.py +101 -0
  59. declib/plugin_installer.py +259 -0
  60. declib/skills/__init__.py +24 -0
  61. declib/skills/decompiler/SKILL.md +316 -0
  62. declib/ui/__init__.py +33 -0
  63. declib/ui/qt_objects.py +146 -0
  64. declib/ui/utils.py +115 -0
  65. declib/ui/version.py +14 -0
  66. declib-3.8.0.dist-info/METADATA +138 -0
  67. declib-3.8.0.dist-info/RECORD +71 -0
  68. declib-3.8.0.dist-info/WHEEL +5 -0
  69. declib-3.8.0.dist-info/entry_points.txt +3 -0
  70. declib-3.8.0.dist-info/licenses/LICENSE +24 -0
  71. declib-3.8.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,409 @@
1
+ import re
2
+ import logging
3
+ from collections import OrderedDict, defaultdict, ChainMap
4
+ from typing import Optional
5
+
6
+ import pycparser
7
+ from pycparser import c_ast
8
+ from pycparser.c_parser import ParseError
9
+
10
+ # pycparser hack to parse type expressions
11
+ errorlog = logging.getLogger(name=__name__ + ".yacc")
12
+ errorlog.setLevel(logging.ERROR)
13
+
14
+
15
+ l = logging.getLogger(__name__)
16
+
17
+
18
+ def _patch_pycparser():
19
+ """
20
+ Adds a `parse_type_with_name` method to pycparser.CParser that parses a bare
21
+ type expression (like "int *") rather than a full translation unit. pycparser
22
+ 3.0 removed the ability to customize the start production via ply.yacc.
23
+ """
24
+ if hasattr(pycparser.CParser, "parse_type_with_name"):
25
+ return
26
+
27
+ def parse_type_with_name(self, text, filename="", scope_stack=None) -> c_ast.Typename:
28
+ self.clex._filename = filename
29
+ self.clex._lineno = 1
30
+ self._scope_stack = [{}] if scope_stack is None else scope_stack
31
+
32
+ self.clex.input(text, filename)
33
+ self._tokens = pycparser.c_parser._TokenStream(self.clex)
34
+
35
+ return self._parse_type_name()
36
+
37
+ pycparser.CParser.parse_type_with_name = parse_type_with_name
38
+
39
+
40
+ _patch_pycparser()
41
+
42
+
43
+ class CType:
44
+ def __init__(
45
+ self,
46
+ type_=None,
47
+ size=0,
48
+ is_primitive=True,
49
+ is_array=False,
50
+ is_ptr=False,
51
+ is_unknown=False
52
+ ):
53
+ self.type = type_
54
+ self._size = size
55
+
56
+ self.is_primitive = is_primitive
57
+ self.is_array = is_array
58
+ self.is_ptr = is_ptr
59
+ self.is_unknown = is_unknown
60
+
61
+ def __str__(self):
62
+ return f"<{self.__class__.__name__}: {self.type} {'[]' if self.is_array else ''}{'*' if self.is_ptr else ''}" \
63
+ f"{'U' if self.is_unknown else ''} ({self._size})>"
64
+
65
+ def __repr__(self):
66
+ return self.__str__()
67
+
68
+ @property
69
+ def type_str(self):
70
+ if isinstance(self.type, CType) and self.is_array:
71
+ return self.type.type_str + f"[{self._size}]"
72
+
73
+ return self.type
74
+
75
+ @property
76
+ def base_type(self):
77
+ if isinstance(self.type, str):
78
+ return self
79
+ elif isinstance(self.type, CType):
80
+ return self.type.base_type
81
+
82
+ return self.type
83
+
84
+ @property
85
+ def size(self):
86
+ if isinstance(self.type, CType) and self.is_array:
87
+ return self.type.size * self._size
88
+
89
+ return self._size
90
+
91
+
92
+ class CTypeParser:
93
+ """
94
+ Most of this code is ripped from angr's sim_type:
95
+ https://github.com/angr/angr/blob/master/angr/sim_type.py
96
+
97
+ It is highly simplified and drops a lot of support for real declaration parsing (like a struct dec).
98
+ Instead, we just use it to parse types.
99
+ """
100
+ def __init__(
101
+ self,
102
+ sizeof_ptr=8,
103
+ sizeof_long=8,
104
+ sizeof_double=8,
105
+ sizeof_int=4,
106
+ sizeof_float=4,
107
+ sizeof_short=2,
108
+ sizeof_char=1,
109
+ sizeof_bool=1,
110
+ extra_types=None
111
+ ):
112
+ # sizes
113
+ self.sizeof_ptr = sizeof_ptr
114
+ self.sizeof_long = sizeof_long
115
+ self.sizeof_double = sizeof_double
116
+ self.sizeof_int = sizeof_int
117
+ self.sizeof_float = sizeof_float
118
+ self.sizeof_short = sizeof_short
119
+ self.sizeof_char = sizeof_char
120
+ self.sizeof_bool = sizeof_bool
121
+
122
+ # hack in type parsing
123
+ self._type_parser_singleton = pycparser.CParser()
124
+ self.ALL_TYPES = {}
125
+ self.BASIC_TYPES = {}
126
+ self.STDINT_TYPES = {}
127
+ self.extra_types = extra_types or {}
128
+ self.SIZE_TO_TYPES = {}
129
+ self._init_all_types()
130
+
131
+ def _init_all_types(self):
132
+ self.BASIC_TYPES = {
133
+ "char": CType(type_="char", size=self.sizeof_char),
134
+ "signed char": CType(type_="signed char", size=self.sizeof_char),
135
+ "unsigned char": CType(type_="unsigned char", size=self.sizeof_char),
136
+ "short": CType(type_="short", size=self.sizeof_short),
137
+ "signed short": CType(type_="signed short", size=self.sizeof_short),
138
+ "unsigned short": CType(type_="unsigned short", size=self.sizeof_short),
139
+ "short int": CType(type_="short int", size=self.sizeof_short),
140
+ "signed short int": CType(type_="signed short int", size=self.sizeof_short),
141
+ "unsigned short int": CType(type_="unsigned short int", size=self.sizeof_short),
142
+ "int": CType(type_="int", size=self.sizeof_int),
143
+ "signed": CType(type_="signed", size=self.sizeof_int),
144
+ "unsigned": CType(type_="unsigned", size=self.sizeof_int),
145
+ "signed int": CType(type_="signed int", size=self.sizeof_int),
146
+ "unsigned int": CType(type_="unsigned int", size=self.sizeof_int),
147
+ "long": CType(type_="long", size=self.sizeof_long),
148
+ "signed long": CType(type_="signed long", size=self.sizeof_long),
149
+ "long signed": CType(type_="long signed", size=self.sizeof_long),
150
+ "unsigned long": CType(type_="unsigned long", size=self.sizeof_long),
151
+ "long int": CType(type_="long int", size=self.sizeof_long),
152
+ "signed long int": CType(type_="signed long int", size=self.sizeof_long),
153
+ "unsigned long int": CType(type_="unsigned long int", size=self.sizeof_long),
154
+ "long unsigned int": CType(type_="long unsigned int", size=self.sizeof_long),
155
+ "long long": CType(type_="long long", size=self.sizeof_long),
156
+ "signed long long": CType(type_="signed long long", size=self.sizeof_long),
157
+ "unsigned long long": CType(type_="unsigned long long", size=self.sizeof_long),
158
+ "long long int": CType(type_="long long int", size=self.sizeof_long),
159
+ "signed long long int": CType(type_="signed long long int", size=self.sizeof_long),
160
+ "unsigned long long int": CType(type_="unsigned long long int", size=self.sizeof_long),
161
+ "__int128": CType(type_="__int128", size=16),
162
+ "unsigned __int128": CType(type_="unsigned __int128", size=16),
163
+ "__int256": CType(type_="__int256", size=32),
164
+ "unsigned __int256": CType(type_="unsigned __int256", size=32),
165
+ "bool": CType(type_="bool", size=self.sizeof_bool),
166
+ "_Bool": CType(type_="_Bool", size=self.sizeof_bool),
167
+ "float": CType(type_="float", size=self.sizeof_float),
168
+ "double": CType(type_="double", size=self.sizeof_double),
169
+ "long double": CType(type_="double", size=self.sizeof_double),
170
+ "void": CType(type_="void", size=self.sizeof_ptr),
171
+ }
172
+ self.ALL_TYPES.update(self.BASIC_TYPES)
173
+
174
+ self.STDINT_TYPES = {
175
+ "int8_t": CType(type_="int8_t", size=1),
176
+ "uint8_t": CType(type_="uint8_t", size=1),
177
+ "byte": CType(type_="byte", size=1),
178
+ "int16_t": CType(type_="int16_t", size=2),
179
+ "uint16_t": CType(type_="uint16_t", size=2),
180
+ "word": CType(type_="word", size=2),
181
+ "int32_t": CType(type_="int32_t", size=4),
182
+ "uint32_t": CType(type_="uint32_t", size=4),
183
+ "dword": CType(type_="dword", size=4),
184
+ "int64_t": CType(type_="int64_t", size=8),
185
+ "uint64_t": CType(type_="uint64_t", size=8),
186
+ }
187
+ self.ALL_TYPES.update(self.STDINT_TYPES)
188
+ self.ALL_TYPES.update(self.extra_types)
189
+
190
+ for name, ctype in self.ALL_TYPES.items():
191
+ if name.startswith("unsigned"):
192
+ self.SIZE_TO_TYPES[ctype.size] = ctype
193
+
194
+ def extract_type_name(self, type_str: str) -> str | None:
195
+ """
196
+ Normalizes types that may come in as declarations, removing any extraneous information that may be present
197
+ and just getting the name of that type.
198
+
199
+ In the case of:
200
+ "char *" that would return "char *"
201
+ "struct foo *" that would return "foo *"
202
+ "typedef int my_type" that would return "my_type"
203
+ """
204
+ # XXX: this could be subverted by a type name that contains a scope or external ref ("extern")
205
+ is_defined = any(type_str.strip().startswith(t) for t in ["struct", "enum", "union", "typedef"])
206
+ if not is_defined:
207
+ return type_str
208
+
209
+ parsable_type = type_str.replace(";", "").strip()
210
+ type_name = None
211
+ try:
212
+ ast = self._type_parser_singleton.parse(text=parsable_type + ";")
213
+ if ast.ext:
214
+ type_name = ast.ext[0].name
215
+ except ParseError:
216
+ pass
217
+
218
+ if type_name is None:
219
+ # do a hackish parse to get the type name, which may be inside a defined type-in-place
220
+ # remove "struct", "enum", "union", and "typedef" keywords, select the final type
221
+ if any(parsable_type.startswith(t) for t in ["struct", "enum", "union", "typedef"]):
222
+ final_type = type_str.split(" ")[-1]
223
+ final_type = final_type.replace(";", "").strip()
224
+ if " " not in final_type:
225
+ # final sanity check that it really is just a name
226
+ type_name = final_type
227
+
228
+ return type_name
229
+
230
+ def parse_type(self, defn, predefined_types=None, arch=None) -> Optional[CType]: # pylint:disable=unused-argument
231
+ """
232
+ Parse a simple type expression into a SimType
233
+
234
+ >>> self.parse_type('int *')
235
+ """
236
+ return self.parse_type_with_name(defn, predefined_types=predefined_types, arch=arch)[0]
237
+
238
+ def parse_type_with_name(self, defn, predefined_types=None, arch=None): # pylint:disable=unused-argument
239
+ """
240
+ Parse a simple type expression into a SimType, returning the a tuple of the type object and any associated name
241
+ that might be found in the place a name would go in a type declaration.
242
+
243
+ >>> self.parse_type_with_name('int *foo')
244
+ """
245
+ if not defn:
246
+ return None
247
+
248
+ if pycparser is None:
249
+ raise ImportError("Please install pycparser in order to parse C definitions")
250
+
251
+ defn = re.sub(r"/\*.*?\*/", r"", defn, flags=re.DOTALL)
252
+ defn = re.sub(r"//.*?$", r"", defn, flags=re.MULTILINE)
253
+
254
+ failed_parse = False
255
+ try:
256
+ node = self._type_parser_singleton.parse_type_with_name(text=defn)
257
+ except ParseError:
258
+ failed_parse = True
259
+
260
+ #
261
+ # in the event of a failed type parse it may just be a custom type, so we should try again
262
+ # with the struct specifier and see if it works out
263
+ #
264
+ if failed_parse:
265
+ try:
266
+ node = self._type_parser_singleton.parse_type_with_name(text="struct " + defn)
267
+ except Exception:
268
+ return (None, )
269
+
270
+ if not isinstance(node, pycparser.c_ast.Typename) and \
271
+ not isinstance(node, pycparser.c_ast.Decl):
272
+ raise pycparser.c_parser.ParseError("Got an unexpected type out of pycparser")
273
+
274
+ decl = node.type
275
+ extra_types = {} if not predefined_types else dict(predefined_types)
276
+ return self._decl_to_type(decl, extra_types=extra_types), node.name
277
+
278
+ def _decl_to_type(self, decl, extra_types=None) -> Optional[CType]:
279
+ if not decl:
280
+ return decl
281
+
282
+ if extra_types is None: extra_types = {}
283
+
284
+ if isinstance(decl, pycparser.c_ast.FuncDecl):
285
+ return None
286
+
287
+ elif isinstance(decl, pycparser.c_ast.TypeDecl):
288
+ return self._decl_to_type(decl.type, extra_types)
289
+
290
+ elif isinstance(decl, pycparser.c_ast.Typedef):
291
+ return self._decl_to_type(decl.type, extra_types)
292
+
293
+ elif isinstance(decl, pycparser.c_ast.PtrDecl):
294
+ pts_to = self._decl_to_type(decl.type, extra_types)
295
+ if not pts_to:
296
+ return None
297
+
298
+ return CType(type_=pts_to.type, size=self.sizeof_ptr, is_ptr=True, is_unknown=pts_to.is_unknown)
299
+
300
+ elif isinstance(decl, pycparser.c_ast.ArrayDecl):
301
+ elem_type = self._decl_to_type(decl.type, extra_types)
302
+
303
+ if decl.dim is None:
304
+ """
305
+ r = SimTypeArray(elem_type)
306
+ r._arch = arch
307
+ return r
308
+ """
309
+ return CType(type_=elem_type, is_array=True, size=0)
310
+ try:
311
+ size = self._parse_const(decl.dim, extra_types=extra_types)
312
+ except ValueError as e:
313
+ #l.warning("Got error parsing array dimension, defaulting to zero: %s", e)
314
+ size = 0
315
+ """
316
+ r = SimTypeFixedSizeArray(elem_type, size)
317
+ r._arch = arch
318
+ """
319
+ return CType(type_=elem_type, is_array=True, size=size)
320
+
321
+ elif isinstance(decl, pycparser.c_ast.Struct):
322
+ if decl is None:
323
+ return None
324
+
325
+ return CType(type_=decl.name, is_unknown=True)
326
+
327
+ elif isinstance(decl, pycparser.c_ast.Union):
328
+ return None
329
+
330
+ elif isinstance(decl, pycparser.c_ast.IdentifierType):
331
+ key = ' '.join(decl.names)
332
+ if key in extra_types:
333
+ return extra_types[key]
334
+ elif key in self.ALL_TYPES:
335
+ return self.ALL_TYPES[key]
336
+ else:
337
+ #raise TypeError("Unknown type '%s'" % key)
338
+ return CType(type_=key, is_unknown=True)
339
+
340
+ elif isinstance(decl, pycparser.c_ast.Enum):
341
+ # See C99 at 6.7.2.2
342
+ return self.ALL_TYPES['int']
343
+
344
+ raise ValueError("Unknown type!")
345
+
346
+ def _make_scope(self, predefined_types=None):
347
+ """
348
+ Generate CParser scope_stack argument to parse method
349
+ """
350
+ all_types = ChainMap(predefined_types or {}, self.ALL_TYPES)
351
+ scope = dict()
352
+ for ty in all_types:
353
+ if ty in self.BASIC_TYPES:
354
+ continue
355
+ if ' ' in ty:
356
+ continue
357
+
358
+ typ = all_types[ty]
359
+ scope[ty] = True
360
+ return [scope]
361
+
362
+ def _parse_const(self, c, extra_types=None):
363
+ if type(c) is pycparser.c_ast.Constant:
364
+ return int(c.value, base=0)
365
+ elif type(c) is pycparser.c_ast.BinaryOp:
366
+ if c.op == '+':
367
+ return self._parse_const(c.children()[0][1], extra_types=extra_types) + self._parse_const(
368
+ c.children()[1][1], extra_types=extra_types)
369
+ if c.op == '-':
370
+ return self._parse_const(c.children()[0][1], extra_types=extra_types) - self._parse_const(
371
+ c.children()[1][1], extra_types=extra_types)
372
+ if c.op == '*':
373
+ return self._parse_const(c.children()[0][1], extra_types=extra_types) * self._parse_const(
374
+ c.children()[1][1], extra_types=extra_types)
375
+ if c.op == '/':
376
+ return self._parse_const(c.children()[0][1], extra_types=extra_types) // self._parse_const(
377
+ c.children()[1][1], extra_types=extra_types)
378
+ if c.op == '<<':
379
+ return self._parse_const(c.children()[0][1], extra_types=extra_types) << self._parse_const(
380
+ c.children()[1][1], extra_types=extra_types)
381
+ if c.op == '>>':
382
+ return self._parse_const(c.children()[0][1], extra_types=extra_types) >> self._parse_const(
383
+ c.children()[1][1], extra_types=extra_types)
384
+ raise ValueError('Binary op %s' % c.op)
385
+ elif type(c) is pycparser.c_ast.UnaryOp:
386
+ if c.op == 'sizeof':
387
+ return self._decl_to_type(c.expr.type, extra_types=extra_types).size
388
+ else:
389
+ raise ValueError("Unary op %s" % c.op)
390
+ elif type(c) is pycparser.c_ast.Cast:
391
+ return self._parse_const(c.expr, extra_types=extra_types)
392
+ else:
393
+ raise ValueError(c)
394
+
395
+ def size_to_type(self, size: int) -> CType:
396
+ if not size:
397
+ raise ValueError("A type size must be greater than 0")
398
+
399
+ ctype = self.SIZE_TO_TYPES.get(size, None)
400
+ if ctype is None:
401
+ # one of two possible things have happend here:
402
+ # 1. this is a type that is larger than the simple types, in which case it's an array
403
+ # 2. this is a type with a non-aligned size, in which case we default to an array as well
404
+ array_size = size // self.sizeof_char
405
+ ctype = self.parse_type(f"char[{array_size}]")
406
+ if ctype is None:
407
+ raise RuntimeError(f"Failed to create a CType of array size {array_size}")
408
+
409
+ return ctype
declib/api/utils.py ADDED
@@ -0,0 +1,31 @@
1
+ import math
2
+
3
+ import tqdm
4
+
5
+
6
+ def progress_bar(items, gui=True, desc="Progressing..."):
7
+ if gui:
8
+ from declib.ui.utils import QProgressBarDialog
9
+ pbar = QProgressBarDialog(label_text=desc)
10
+ pbar.show()
11
+ callback_stub = pbar.update_progress
12
+ else:
13
+ t = tqdm.tqdm(desc=desc)
14
+ callback_stub = t.update
15
+
16
+ bucket_size = len(items) / 100.0
17
+ if bucket_size < 1:
18
+ callback_amt = int(1 / (bucket_size))
19
+ bucket_size = 1
20
+ else:
21
+ callback_amt = 1
22
+ bucket_size = math.ceil(bucket_size)
23
+
24
+ for i, item in enumerate(items):
25
+ yield item
26
+ if i % bucket_size == 0:
27
+ callback_stub(callback_amt)
28
+
29
+ if gui:
30
+ # close the progress bar since it may not hit 100%
31
+ pbar.close()
@@ -0,0 +1,93 @@
1
+ import json
2
+
3
+ import toml
4
+
5
+ from .formatting import TomlHexEncoder, ArtifactFormat
6
+ from .artifact import Artifact
7
+ from .comment import Comment
8
+ from .decompilation import Decompilation
9
+ from .enum import Enum
10
+ from .func import Function, FunctionHeader, FunctionArgument
11
+ from .global_variable import GlobalVariable
12
+ from .patch import Patch
13
+ from .segment import Segment
14
+ from .stack_variable import StackVariable
15
+ from .struct import Struct, StructMember
16
+ from .context import Context
17
+ from .typedef import Typedef
18
+
19
+ ART_NAME_TO_CLS = {
20
+ Function.__name__: Function,
21
+ FunctionHeader.__name__: FunctionHeader,
22
+ FunctionArgument.__name__: FunctionArgument,
23
+ StackVariable.__name__: StackVariable,
24
+ Comment.__name__: Comment,
25
+ GlobalVariable.__name__: GlobalVariable,
26
+ Enum.__name__: Enum,
27
+ Struct.__name__: Struct,
28
+ StructMember.__name__: StructMember,
29
+ Patch.__name__: Patch,
30
+ Decompilation.__name__: Decompilation,
31
+ Context.__name__: Context,
32
+ Typedef.__name__: Typedef,
33
+ Segment.__name__: Segment,
34
+ }
35
+
36
+ ALL_ARTIFACTS = list(ART_NAME_TO_CLS.values())
37
+
38
+
39
+ def _dict_from_str(art_str: str, fmt=ArtifactFormat.TOML) -> dict:
40
+ if fmt == ArtifactFormat.TOML:
41
+ return toml.loads(art_str)
42
+ elif fmt == ArtifactFormat.JSON:
43
+ return json.loads(art_str)
44
+ else:
45
+ raise ValueError(f"Loading from format {fmt} is not yet supported.")
46
+
47
+
48
+ def _art_from_dict(art_dict: dict) -> Artifact:
49
+ art_type_str = art_dict.get(Artifact.ART_TYPE_STR, None)
50
+ if art_type_str is None:
51
+ raise ValueError(f"Artifact type string not found in artifact data: {art_dict}. Is this a valid artifact?")
52
+
53
+ art_cls = ART_NAME_TO_CLS[art_type_str]
54
+ art = art_cls()
55
+ art.__setstate__(art_dict)
56
+ return art
57
+
58
+
59
+ def _load_arts_from_list(art_strs: list[str], fmt=ArtifactFormat.TOML) -> list[Artifact]:
60
+ arts = []
61
+ for art_str in art_strs:
62
+ data_dict = _dict_from_str(art_str, fmt=fmt)
63
+ art = _art_from_dict(data_dict)
64
+ arts.append(art)
65
+ return arts
66
+
67
+
68
+ def _load_arts_from_string(art_str: str, fmt=ArtifactFormat.TOML) -> list[Artifact]:
69
+ data_dict = _dict_from_str(art_str, fmt=fmt)
70
+ if isinstance(data_dict, dict):
71
+ data_dicts = list(data_dict.values())
72
+ elif isinstance(data_dict, list):
73
+ data_dicts = data_dict
74
+ else:
75
+ raise ValueError(f"Unexpected data type: {type(data_dict)}")
76
+
77
+ arts = []
78
+ for v in data_dicts:
79
+ art = _art_from_dict(v)
80
+ arts.append(art)
81
+
82
+ return arts
83
+
84
+
85
+ def load_many_artifacts(art_strings: list[str], fmt=ArtifactFormat.TOML) -> list[Artifact]:
86
+ """
87
+ A helper function to load many dumped artifacts from a list of strings. Each string should have been dumped
88
+ using the `dumps` method of an artifact.
89
+
90
+ :param art_strings: A list of strings or a single string containing multiple dumped artifacts.
91
+ :param fmt: The format of the dumped artifacts.
92
+ """
93
+ return _load_arts_from_list(art_strings, fmt=fmt)