wizata-dsapi 1.3.19__py3-none-any.whl → 1.3.20__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.
- wizata_dsapi/mlmodel.py +120 -230
- wizata_dsapi/version.py +1 -1
- {wizata_dsapi-1.3.19.dist-info → wizata_dsapi-1.3.20.dist-info}/METADATA +1 -1
- {wizata_dsapi-1.3.19.dist-info → wizata_dsapi-1.3.20.dist-info}/RECORD +7 -7
- {wizata_dsapi-1.3.19.dist-info → wizata_dsapi-1.3.20.dist-info}/WHEEL +0 -0
- {wizata_dsapi-1.3.19.dist-info → wizata_dsapi-1.3.20.dist-info}/licenses/LICENSE.txt +0 -0
- {wizata_dsapi-1.3.19.dist-info → wizata_dsapi-1.3.20.dist-info}/top_level.txt +0 -0
wizata_dsapi/mlmodel.py
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import uuid
|
|
3
|
-
|
|
4
1
|
from typing import List, Iterator
|
|
5
|
-
|
|
6
2
|
from .api_dto import ApiDto
|
|
7
|
-
from enum import Enum
|
|
8
3
|
|
|
9
4
|
|
|
10
5
|
def get_bool(obj, name: str):
|
|
@@ -14,6 +9,110 @@ def get_bool(obj, name: str):
|
|
|
14
9
|
return bool(obj[name])
|
|
15
10
|
|
|
16
11
|
|
|
12
|
+
class ModelInfo:
|
|
13
|
+
"""
|
|
14
|
+
define a pointer to a machine learning model.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self,
|
|
18
|
+
key: str,
|
|
19
|
+
twin_hardware_id: str = None,
|
|
20
|
+
property_name: str = None,
|
|
21
|
+
alias: str = None,
|
|
22
|
+
file_format: str = 'pkl',
|
|
23
|
+
source: str = 'wizata'
|
|
24
|
+
):
|
|
25
|
+
self.key = key
|
|
26
|
+
self.twin_hardware_id = twin_hardware_id
|
|
27
|
+
self.property_name = property_name
|
|
28
|
+
self.alias = alias
|
|
29
|
+
self.file_format = file_format
|
|
30
|
+
self.source = source
|
|
31
|
+
self.trained_model = None
|
|
32
|
+
self.scaler = None
|
|
33
|
+
|
|
34
|
+
def identifier(self, include_alias: bool = False) -> str:
|
|
35
|
+
"""
|
|
36
|
+
returns the complete string identifier for this model.
|
|
37
|
+
:param include_alias: include the alias pointer or leave it to target the default version.
|
|
38
|
+
:return: complete identifier of a model.
|
|
39
|
+
"""
|
|
40
|
+
if self.key is None:
|
|
41
|
+
raise KeyError('please specific a model key')
|
|
42
|
+
identifier = self.key
|
|
43
|
+
|
|
44
|
+
if self.twin_hardware_id is not None:
|
|
45
|
+
identifier += f".{self.twin_hardware_id}"
|
|
46
|
+
|
|
47
|
+
if self.property_name is not None:
|
|
48
|
+
identifier += f".{self.property_name}"
|
|
49
|
+
|
|
50
|
+
if include_alias and self.alias is not None:
|
|
51
|
+
identifier += f"@{self.alias}"
|
|
52
|
+
|
|
53
|
+
return identifier
|
|
54
|
+
|
|
55
|
+
def to_json(self):
|
|
56
|
+
"""
|
|
57
|
+
convert this entity in a dict that can be json serializable
|
|
58
|
+
:return: dict
|
|
59
|
+
"""
|
|
60
|
+
obj = {
|
|
61
|
+
"key": self.key,
|
|
62
|
+
"file_format": self.file_format,
|
|
63
|
+
"source": self.source
|
|
64
|
+
}
|
|
65
|
+
if self.twin_hardware_id is not None:
|
|
66
|
+
obj["twin_hardware_id"] = str(self.twin_hardware_id)
|
|
67
|
+
if self.property_name is not None:
|
|
68
|
+
obj["property_name"] = self.property_name
|
|
69
|
+
if self.alias is not None:
|
|
70
|
+
obj["alias"] = self.alias
|
|
71
|
+
if self.file_format is not None:
|
|
72
|
+
obj["file_format"] = self.file_format
|
|
73
|
+
if self.source is not None:
|
|
74
|
+
obj["source"] = self.source
|
|
75
|
+
return obj
|
|
76
|
+
|
|
77
|
+
def from_json(self, obj):
|
|
78
|
+
"""
|
|
79
|
+
load this entity from a dict
|
|
80
|
+
"""
|
|
81
|
+
if "key" in obj.keys():
|
|
82
|
+
self.key = obj["key"]
|
|
83
|
+
if "twin_hardware_id" in obj.keys():
|
|
84
|
+
self.twin_hardware_id = obj["twin_hardware_id"]
|
|
85
|
+
if "property_name" in obj.keys():
|
|
86
|
+
self.property_name = obj["property_name"]
|
|
87
|
+
if "alias" in obj.keys():
|
|
88
|
+
self.alias = obj["alias"]
|
|
89
|
+
if "file_format" in obj.keys():
|
|
90
|
+
self.key = obj["file_format"]
|
|
91
|
+
if "source" in obj.keys():
|
|
92
|
+
self.source = obj["source"]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ModelList:
|
|
96
|
+
"""
|
|
97
|
+
used to conveniently manipulate a list of models.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self):
|
|
101
|
+
self.models: List[ModelInfo] = []
|
|
102
|
+
|
|
103
|
+
def __iter__(self) -> Iterator[ModelInfo]:
|
|
104
|
+
return iter(self.models)
|
|
105
|
+
|
|
106
|
+
def __len__(self) -> int:
|
|
107
|
+
return len(self.models)
|
|
108
|
+
|
|
109
|
+
def __getitem__(self, index: int) -> ModelInfo:
|
|
110
|
+
return self.models[index]
|
|
111
|
+
|
|
112
|
+
def append(self, model: ModelInfo):
|
|
113
|
+
self.models.append(model)
|
|
114
|
+
|
|
115
|
+
|
|
17
116
|
class MLModelConfig(ApiDto):
|
|
18
117
|
"""
|
|
19
118
|
a model config defines execution properties within a pipeline.
|
|
@@ -87,6 +186,22 @@ class MLModelConfig(ApiDto):
|
|
|
87
186
|
self.model_type = model_type
|
|
88
187
|
self.model_alias = model_alias
|
|
89
188
|
|
|
189
|
+
def create_model_info(self, hardware_id: str = None) -> ModelInfo:
|
|
190
|
+
"""
|
|
191
|
+
create model info corresponding to the configuration.
|
|
192
|
+
:param hardware_id: provide a hardware id for this model if by_twin.
|
|
193
|
+
:return:
|
|
194
|
+
"""
|
|
195
|
+
model_info = ModelInfo(
|
|
196
|
+
key=self.model_key,
|
|
197
|
+
twin_hardware_id=hardware_id,
|
|
198
|
+
property_name=self.property_name,
|
|
199
|
+
source=self.source,
|
|
200
|
+
alias=self.model_alias,
|
|
201
|
+
file_format=self.model_format
|
|
202
|
+
)
|
|
203
|
+
return model_info
|
|
204
|
+
|
|
90
205
|
def from_json(self, obj):
|
|
91
206
|
|
|
92
207
|
# Managed deprecated fields
|
|
@@ -230,228 +345,3 @@ class MLModelConfig(ApiDto):
|
|
|
230
345
|
else:
|
|
231
346
|
raise TypeError(f'unsupported target_feat type {self.target_feat.__class__.__name__}')
|
|
232
347
|
|
|
233
|
-
|
|
234
|
-
class ModelInfo:
|
|
235
|
-
"""
|
|
236
|
-
define a pointer to a machine learning model.
|
|
237
|
-
"""
|
|
238
|
-
|
|
239
|
-
def __init__(self,
|
|
240
|
-
key: str,
|
|
241
|
-
twin_hardware_id: str = None,
|
|
242
|
-
property_name: str = None,
|
|
243
|
-
alias: str = None,
|
|
244
|
-
file_format: str = 'pkl',
|
|
245
|
-
source: str = 'wizata'
|
|
246
|
-
):
|
|
247
|
-
self.key = key
|
|
248
|
-
self.twin_hardware_id = twin_hardware_id
|
|
249
|
-
self.property_name = property_name
|
|
250
|
-
self.alias = alias
|
|
251
|
-
self.file_format = file_format
|
|
252
|
-
self.source = source
|
|
253
|
-
self.trained_model = None
|
|
254
|
-
self.scaler = None
|
|
255
|
-
|
|
256
|
-
def identifier(self, include_alias: bool = False) -> str:
|
|
257
|
-
"""
|
|
258
|
-
returns the complete string identifier for this model.
|
|
259
|
-
:param include_alias: include the alias pointer or leave it to target the default version.
|
|
260
|
-
:return: complete identifier of a model.
|
|
261
|
-
"""
|
|
262
|
-
if self.key is None:
|
|
263
|
-
raise KeyError('please specific a model key')
|
|
264
|
-
identifier = self.key
|
|
265
|
-
|
|
266
|
-
if self.twin_hardware_id is not None:
|
|
267
|
-
identifier += f".{self.twin_hardware_id}"
|
|
268
|
-
|
|
269
|
-
if self.property_name is not None:
|
|
270
|
-
identifier += f".{self.property_name}"
|
|
271
|
-
|
|
272
|
-
if include_alias and self.alias is not None:
|
|
273
|
-
identifier += f"@{self.alias}"
|
|
274
|
-
|
|
275
|
-
return identifier
|
|
276
|
-
|
|
277
|
-
def to_json(self):
|
|
278
|
-
"""
|
|
279
|
-
convert this entity in a dict that can be json serializable
|
|
280
|
-
:return: dict
|
|
281
|
-
"""
|
|
282
|
-
obj = {
|
|
283
|
-
"key": self.key,
|
|
284
|
-
"file_format": self.file_format,
|
|
285
|
-
"source": self.source
|
|
286
|
-
}
|
|
287
|
-
if self.twin_hardware_id is not None:
|
|
288
|
-
obj["twin_hardware_id"] = str(self.twin_hardware_id)
|
|
289
|
-
if self.property_name is not None:
|
|
290
|
-
obj["property_name"] = self.property_name
|
|
291
|
-
if self.alias is not None:
|
|
292
|
-
obj["alias"] = self.alias
|
|
293
|
-
if self.file_format is not None:
|
|
294
|
-
obj["file_format"] = self.file_format
|
|
295
|
-
if self.source is not None:
|
|
296
|
-
obj["source"] = self.source
|
|
297
|
-
return obj
|
|
298
|
-
|
|
299
|
-
def from_json(self, obj):
|
|
300
|
-
"""
|
|
301
|
-
load this entity from a dict
|
|
302
|
-
"""
|
|
303
|
-
if "key" in obj.keys():
|
|
304
|
-
self.key = obj["key"]
|
|
305
|
-
if "twin_hardware_id" in obj.keys():
|
|
306
|
-
self.twin_hardware_id = obj["twin_hardware_id"]
|
|
307
|
-
if "property_name" in obj.keys():
|
|
308
|
-
self.property_name = obj["property_name"]
|
|
309
|
-
if "alias" in obj.keys():
|
|
310
|
-
self.alias = obj["alias"]
|
|
311
|
-
if "file_format" in obj.keys():
|
|
312
|
-
self.key = obj["file_format"]
|
|
313
|
-
if "source" in obj.keys():
|
|
314
|
-
self.source = obj["source"]
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
class ModelList:
|
|
318
|
-
"""
|
|
319
|
-
used to conveniently manipulate a list of models.
|
|
320
|
-
"""
|
|
321
|
-
|
|
322
|
-
def __init__(self):
|
|
323
|
-
self.models: List[ModelInfo] = []
|
|
324
|
-
|
|
325
|
-
def __iter__(self) -> Iterator[ModelInfo]:
|
|
326
|
-
return iter(self.models)
|
|
327
|
-
|
|
328
|
-
def __len__(self) -> int:
|
|
329
|
-
return len(self.models)
|
|
330
|
-
|
|
331
|
-
def __getitem__(self, index: int) -> ModelInfo:
|
|
332
|
-
return self.models[index]
|
|
333
|
-
|
|
334
|
-
def append(self, model: ModelInfo):
|
|
335
|
-
self.models.append(model)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
class MLModel(ApiDto):
|
|
339
|
-
"""
|
|
340
|
-
used to define a stored machine learning model within Wizata.
|
|
341
|
-
- deprecated -
|
|
342
|
-
"""
|
|
343
|
-
|
|
344
|
-
@classmethod
|
|
345
|
-
def route(cls):
|
|
346
|
-
return "mlmodels"
|
|
347
|
-
|
|
348
|
-
@classmethod
|
|
349
|
-
def from_dict(cls, data):
|
|
350
|
-
obj = MLModel()
|
|
351
|
-
obj.from_json(data)
|
|
352
|
-
return obj
|
|
353
|
-
|
|
354
|
-
@classmethod
|
|
355
|
-
def get_type(cls):
|
|
356
|
-
return "pickle"
|
|
357
|
-
|
|
358
|
-
def __init__(self,
|
|
359
|
-
model_id: str = None,
|
|
360
|
-
generated_by_id=None,
|
|
361
|
-
exact_names=True,
|
|
362
|
-
exact_numbers=True,
|
|
363
|
-
key=None):
|
|
364
|
-
self.model_id = model_id
|
|
365
|
-
self.key = key
|
|
366
|
-
|
|
367
|
-
self.generatedById = generated_by_id
|
|
368
|
-
|
|
369
|
-
self.status = 'draft'
|
|
370
|
-
self.input_columns = []
|
|
371
|
-
self.output_columns = []
|
|
372
|
-
|
|
373
|
-
self.needExactColumnNumbers = exact_numbers
|
|
374
|
-
self.needExactColumnNames = exact_names
|
|
375
|
-
self.has_anomalies = False
|
|
376
|
-
self.label_counts = 0
|
|
377
|
-
self.has_target_feat = False
|
|
378
|
-
|
|
379
|
-
self.trained_model = None
|
|
380
|
-
self.scaler = None
|
|
381
|
-
|
|
382
|
-
self.experimentId = None
|
|
383
|
-
|
|
384
|
-
def api_id(self) -> str:
|
|
385
|
-
return str(self.model_id).upper()
|
|
386
|
-
|
|
387
|
-
def endpoint(self) -> str:
|
|
388
|
-
return "MLModels"
|
|
389
|
-
|
|
390
|
-
def to_json(self, target: str = None):
|
|
391
|
-
obj = {"id": str(self.model_id),
|
|
392
|
-
"status": str(self.status),
|
|
393
|
-
"needExactColumnNames": str(self.needExactColumnNames),
|
|
394
|
-
"needExactColumnNumbers": str(self.needExactColumnNumbers),
|
|
395
|
-
"hasAnomalies": str(self.has_anomalies),
|
|
396
|
-
"hasTargetFeat": str(self.has_target_feat),
|
|
397
|
-
"labelCount": str(self.label_counts)
|
|
398
|
-
}
|
|
399
|
-
if self.key is not None:
|
|
400
|
-
obj["key"] = str(self.key)
|
|
401
|
-
if self.generatedById is not None:
|
|
402
|
-
obj["generatedById"] = self.generatedById
|
|
403
|
-
if self.input_columns is not None:
|
|
404
|
-
obj["inputColumns"] = json.dumps(list(self.input_columns))
|
|
405
|
-
if self.output_columns is not None:
|
|
406
|
-
obj["outputColumns"] = json.dumps(list(self.output_columns))
|
|
407
|
-
if self.experimentId is not None:
|
|
408
|
-
obj["experimentId"] = str(self.experimentId)
|
|
409
|
-
return obj
|
|
410
|
-
|
|
411
|
-
def from_json(self, obj):
|
|
412
|
-
if "id" in obj.keys():
|
|
413
|
-
self.model_id = obj["id"]
|
|
414
|
-
if "key" in obj.keys() and obj["key"] is not None:
|
|
415
|
-
self.key = obj["key"]
|
|
416
|
-
if "generatedById" in obj.keys() and obj["generatedById"] is not None:
|
|
417
|
-
self.generatedById = int(obj["generatedById"])
|
|
418
|
-
if "experimentId" in obj.keys() and obj["experimentId"] is not None:
|
|
419
|
-
self.experimentId = uuid.UUID(obj["experimentId"])
|
|
420
|
-
if "status" in obj.keys():
|
|
421
|
-
self.status = str(obj["status"]).lower()
|
|
422
|
-
if "inputColumns" in obj.keys():
|
|
423
|
-
self.input_columns = json.loads(obj["inputColumns"])
|
|
424
|
-
if "outputColumns" in obj.keys():
|
|
425
|
-
self.output_columns = json.loads(obj["outputColumns"])
|
|
426
|
-
if "labelCount" in obj.keys():
|
|
427
|
-
self.label_counts = int(obj["labelCount"])
|
|
428
|
-
if "hasAnomalies" in obj.keys():
|
|
429
|
-
if isinstance(obj["hasAnomalies"], str) and obj["hasAnomalies"].lower() == "false":
|
|
430
|
-
self.has_anomalies = False
|
|
431
|
-
else:
|
|
432
|
-
self.has_anomalies = bool(obj["hasAnomalies"])
|
|
433
|
-
if "hasTargetFeat" in obj.keys():
|
|
434
|
-
if isinstance(obj["hasTargetFeat"], str) and obj["hasTargetFeat"].lower() == "false":
|
|
435
|
-
self.has_target_feat = False
|
|
436
|
-
else:
|
|
437
|
-
self.has_target_feat = bool(obj["hasTargetFeat"])
|
|
438
|
-
if "needExactColumnNumbers" in obj.keys():
|
|
439
|
-
if isinstance(obj["needExactColumnNumbers"], str) and obj["needExactColumnNumbers"].lower() == "false":
|
|
440
|
-
self.needExactColumnNumbers = False
|
|
441
|
-
else:
|
|
442
|
-
self.needExactColumnNumbers = bool(obj["needExactColumnNumbers"])
|
|
443
|
-
if "needExactColumnNames" in obj.keys():
|
|
444
|
-
if isinstance(obj["needExactColumnNames"], str) and obj["needExactColumnNames"].lower() == "false":
|
|
445
|
-
self.needExactColumnNames = False
|
|
446
|
-
else:
|
|
447
|
-
self.needExactColumnNames = bool(obj["needExactColumnNames"])
|
|
448
|
-
|
|
449
|
-
def get_sample_payload(self):
|
|
450
|
-
pl_columns = {"timestamp": "[timestamp]"}
|
|
451
|
-
for hardwareId in self.input_columns:
|
|
452
|
-
pl_columns[hardwareId] = "[" + hardwareId + "]"
|
|
453
|
-
pl_json = {
|
|
454
|
-
"id": str(self.model_id),
|
|
455
|
-
"dataset": pl_columns
|
|
456
|
-
}
|
|
457
|
-
return pl_json
|
wizata_dsapi/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.3.
|
|
1
|
+
__version__ = "1.3.20"
|
|
@@ -16,7 +16,7 @@ wizata_dsapi/experiment.py,sha256=QYQ1CJ-MTWsXq08xYbm5sAp95dRxbPOmGDgaAOoBMDQ,46
|
|
|
16
16
|
wizata_dsapi/group_system.py,sha256=6rUKe0_J3YWACysyBlzuw_TEpKNXgLOMxhpWsNxOzwY,1708
|
|
17
17
|
wizata_dsapi/ilogger.py,sha256=iYnID-Z-qrYhie26C43404aIuU4_tHSKXbDeQIdo82Q,807
|
|
18
18
|
wizata_dsapi/insight.py,sha256=ABFZ04DqYxxzqAEfU1tzlTZqqrigM-zN-8Lbetko3g0,6468
|
|
19
|
-
wizata_dsapi/mlmodel.py,sha256=
|
|
19
|
+
wizata_dsapi/mlmodel.py,sha256=vtrMjhvnjZS-PFM9hWAzXA3c862B-qR2qEJS4UtJgo0,14289
|
|
20
20
|
wizata_dsapi/model_toolkit.py,sha256=a76ckSuetSKDjEkOl7o49g9oaItdw9iOaPt0JcstmhU,1551
|
|
21
21
|
wizata_dsapi/paged_query_result.py,sha256=0Iyt2Kd4tvrfthhT-tk9EmSERsbJTaPNON2euHcBn6k,1150
|
|
22
22
|
wizata_dsapi/pipeline.py,sha256=WDJeOxPZJiYW1qwTNZUm3jom2epIxqrSoiUwcrTF9EE,31300
|
|
@@ -31,7 +31,7 @@ wizata_dsapi/template.py,sha256=wtCRKKk3PchH4RrNgNYlEF_9C6bzZwKIeLyEvgv6Fdo,1370
|
|
|
31
31
|
wizata_dsapi/trigger.py,sha256=w3BZYP-L3SUwvaT0oCTanh_Ewn57peZvlt7vxzHv9J8,5129
|
|
32
32
|
wizata_dsapi/twin.py,sha256=S0DUzQf1smZXZTdXpXZPtkZYCfKIhw53EecCnsl9i4Q,11017
|
|
33
33
|
wizata_dsapi/twinregistration.py,sha256=Mi6-YuwroiEXc0c1hgrOaphh4hNVoHupxOnXedVtJtE,13377
|
|
34
|
-
wizata_dsapi/version.py,sha256=
|
|
34
|
+
wizata_dsapi/version.py,sha256=xkU7aW7cKvizjP0w8nXhUrp9VWxt6sXDqExnl-nGWGw,23
|
|
35
35
|
wizata_dsapi/wizard_function.py,sha256=RbM7W7Gf-6Rhp_1dU9DBYkHaciknGAGvuAndhAS_vyo,942
|
|
36
36
|
wizata_dsapi/wizard_request.py,sha256=v6BaqKLKvTWmUSo0_gda9FabAQz5x_-GOH1Av50GzFo,3762
|
|
37
37
|
wizata_dsapi/wizata_dsapi_client.py,sha256=kos_8j47CBXiy6y6b_H2yDUe3aWh76diEmWjCisYKkY,78662
|
|
@@ -42,8 +42,8 @@ wizata_dsapi/plots/__init__.py,sha256=qgnSFqrjOPur-807M8uh5awIfjM1ZHXUXcAqHc-r2l
|
|
|
42
42
|
wizata_dsapi/plots/common.py,sha256=jdPsJqLHBwSKc6dX83BSGPqSRxzIVNHSYO5yI_8sjGk,6568
|
|
43
43
|
wizata_dsapi/scripts/__init__.py,sha256=hAxiETSQf0qOHde1si1tEAJU48seqEgHrchCzS2-LvQ,80
|
|
44
44
|
wizata_dsapi/scripts/common.py,sha256=efwq-Rd0lvYljIs3gSFz9izogBD7asOU2cTK-IvHTkM,4244
|
|
45
|
-
wizata_dsapi-1.3.
|
|
46
|
-
wizata_dsapi-1.3.
|
|
47
|
-
wizata_dsapi-1.3.
|
|
48
|
-
wizata_dsapi-1.3.
|
|
49
|
-
wizata_dsapi-1.3.
|
|
45
|
+
wizata_dsapi-1.3.20.dist-info/licenses/LICENSE.txt,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
46
|
+
wizata_dsapi-1.3.20.dist-info/METADATA,sha256=AlxEUFMhHXabL0a22dsV8VSxOStL2KwigOTGdIIcKr0,5651
|
|
47
|
+
wizata_dsapi-1.3.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
48
|
+
wizata_dsapi-1.3.20.dist-info/top_level.txt,sha256=-OeTJbEnh5DuWyTOHtvw0Dw3LRg3G27TNS6W4ZtfwPs,13
|
|
49
|
+
wizata_dsapi-1.3.20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|