sdata 1.5.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.
- sdata/__init__.py +140 -0
- sdata/base.py +684 -0
- sdata/contrib/__init__.py +0 -0
- sdata/contrib/piexif/__init__.py +12 -0
- sdata/contrib/piexif/_common.py +94 -0
- sdata/contrib/piexif/_dump.py +346 -0
- sdata/contrib/piexif/_exceptions.py +2 -0
- sdata/contrib/piexif/_exif.py +638 -0
- sdata/contrib/piexif/_insert.py +60 -0
- sdata/contrib/piexif/_load.py +270 -0
- sdata/contrib/piexif/_remove.py +56 -0
- sdata/contrib/piexif/_transplant.py +45 -0
- sdata/contrib/piexif/_webp.py +240 -0
- sdata/contrib/piexif/helper.py +66 -0
- sdata/contrib/ranger/__init__.py +8 -0
- sdata/contrib/ranger/collections/__init__.py +0 -0
- sdata/contrib/ranger/collections/rangebucketmap.py +346 -0
- sdata/contrib/ranger/collections/rangemap.py +396 -0
- sdata/contrib/ranger/collections/rangeset.py +483 -0
- sdata/contrib/ranger/range/__init__.py +0 -0
- sdata/contrib/ranger/range/cut.py +285 -0
- sdata/contrib/ranger/range/range.py +644 -0
- sdata/contrib/sobol_seq.py +394 -0
- sdata/contrib/sortedcontainers/LICENSE +13 -0
- sdata/contrib/sortedcontainers/__init__.py +74 -0
- sdata/contrib/sortedcontainers/sorteddict.py +789 -0
- sdata/contrib/sortedcontainers/sortedlist.py +2646 -0
- sdata/contrib/sortedcontainers/sortedset.py +733 -0
- sdata/contrib/sqlitedict.py +598 -0
- sdata/contrib/timeflake/__init__.py +40 -0
- sdata/contrib/timeflake/flake.py +69 -0
- sdata/contrib/timeflake/utils.py +47 -0
- sdata/deprecated/__init__.py +10 -0
- sdata/deprecated/data.py +1503 -0
- sdata/did/__init__.py +129 -0
- sdata/did/_http.py +67 -0
- sdata/did/base58btc.py +49 -0
- sdata/did/did_github.py +152 -0
- sdata/did/did_peer4.py +155 -0
- sdata/did/did_web.py +222 -0
- sdata/did/diddoc.py +75 -0
- sdata/did/ed25519.py +225 -0
- sdata/did/eddsa.py +107 -0
- sdata/did/errors.py +40 -0
- sdata/did/issue_vc.py +193 -0
- sdata/did/jose.py +126 -0
- sdata/did/keys.py +104 -0
- sdata/did/utils_didkey.py +161 -0
- sdata/did/verify_vp.py +158 -0
- sdata/doe.py +101 -0
- sdata/dtypes.py +510 -0
- sdata/experiments/__init__.py +331 -0
- sdata/experiments/blob0.py +131 -0
- sdata/experiments/file.py +42 -0
- sdata/experiments/filegroup.py +144 -0
- sdata/experiments/image_old.py +313 -0
- sdata/experiments/ks2.py +15 -0
- sdata/experiments/lapshear.py +8 -0
- sdata/experiments/material.py +7 -0
- sdata/experiments/parameters_deprecated.py +394 -0
- sdata/experiments/workbook.py +226 -0
- sdata/format.py +125 -0
- sdata/imagemeta.py +533 -0
- sdata/interactive.py +118 -0
- sdata/iolib/__init__.py +51 -0
- sdata/iolib/hashtree.py +264 -0
- sdata/iolib/json1sqlitestore.py +554 -0
- sdata/iolib/jsonsqlitestore.py +704 -0
- sdata/iolib/pud.py +242 -0
- sdata/iolib/reader.py +262 -0
- sdata/iolib/writer.py +419 -0
- sdata/metadata.py +882 -0
- sdata/parameter.py +414 -0
- sdata/schema.py +273 -0
- sdata/sclass/__init__.py +111 -0
- sdata/sclass/bfo_classes.py +362 -0
- sdata/sclass/blob.py +362 -0
- sdata/sclass/content.py +87 -0
- sdata/sclass/dataframe.py +1251 -0
- sdata/sclass/dataframegroup.py +187 -0
- sdata/sclass/dependency_graph.py +85 -0
- sdata/sclass/filereference.py +124 -0
- sdata/sclass/image.py +218 -0
- sdata/sclass/lazy_namespace.py +155 -0
- sdata/sclass/process.py +383 -0
- sdata/sclass/process_graph.py +221 -0
- sdata/sclass/zipfile.py +135 -0
- sdata/semantic.py +346 -0
- sdata/suuid.py +136 -0
- sdata/timestamp.py +336 -0
- sdata/tools.py +14 -0
- sdata/units.py +602 -0
- sdata/vocab.py +149 -0
- sdata-1.5.0.dist-info/METADATA +264 -0
- sdata-1.5.0.dist-info/RECORD +98 -0
- sdata-1.5.0.dist-info/WHEEL +5 -0
- sdata-1.5.0.dist-info/licenses/LICENSE-MIT +21 -0
- sdata-1.5.0.dist-info/top_level.txt +1 -0
sdata/metadata.py
ADDED
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
# -*-coding: utf-8-*-
|
|
2
|
+
import logging
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
4
|
+
from sdata import __version__
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import numpy as np
|
|
7
|
+
from sdata.timestamp import TimeStamp
|
|
8
|
+
import sdata.dtypes as dtypes
|
|
9
|
+
import datetime
|
|
10
|
+
from dateutil.parser import parse as parse_date
|
|
11
|
+
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import hashlib
|
|
15
|
+
import re
|
|
16
|
+
import copy
|
|
17
|
+
from sdata.contrib.sortedcontainers.sorteddict import SortedDict
|
|
18
|
+
|
|
19
|
+
def extract_name_unit(value):
|
|
20
|
+
"""extract name and unit from a combined string
|
|
21
|
+
|
|
22
|
+
.. code-block:: python
|
|
23
|
+
|
|
24
|
+
value: 'Target Strain Rate (1/s) '
|
|
25
|
+
name : 'Target Strain Rate'
|
|
26
|
+
unit : '1/s'
|
|
27
|
+
|
|
28
|
+
value: 'Gauge Length [mm] monkey '
|
|
29
|
+
name : 'Gauge Length'
|
|
30
|
+
unit : 'mm'
|
|
31
|
+
|
|
32
|
+
value: 'Gauge Length <mm> whatever '
|
|
33
|
+
name : 'Gauge Length'
|
|
34
|
+
unit : 'mm'
|
|
35
|
+
|
|
36
|
+
:param value: string, e.g. 'Length <mm> whatever'
|
|
37
|
+
:return: name, unit
|
|
38
|
+
"""
|
|
39
|
+
pattern1 = r'([\w\s\.]+) \(([\w.-\/]+)\)'
|
|
40
|
+
match1 = re.search(pattern1, value)
|
|
41
|
+
pattern2 = r'([\w\s\.]+) \[([\w.-\/]+)\]'
|
|
42
|
+
match2 = re.search(pattern2, value)
|
|
43
|
+
pattern3 = r'([\w\s\.]+) \<([\w.-\/]+)\>'
|
|
44
|
+
match3 = re.search(pattern3, value)
|
|
45
|
+
if match1:
|
|
46
|
+
name = match1.group(1)
|
|
47
|
+
unit = match1.group(2)
|
|
48
|
+
elif match2:
|
|
49
|
+
name = match2.group(1)
|
|
50
|
+
unit = match2.group(2)
|
|
51
|
+
elif match3:
|
|
52
|
+
name = match3.group(1)
|
|
53
|
+
unit = match3.group(2)
|
|
54
|
+
else:
|
|
55
|
+
name = value
|
|
56
|
+
unit = ""
|
|
57
|
+
return name, unit
|
|
58
|
+
|
|
59
|
+
def _to_utc(dt: datetime.datetime) -> datetime.datetime:
|
|
60
|
+
"""Sichere UTC-Normalisierung (aware)."""
|
|
61
|
+
if dt.tzinfo is None:
|
|
62
|
+
return dt.replace(tzinfo=datetime.timezone.utc)
|
|
63
|
+
return dt.astimezone(datetime.timezone.utc)
|
|
64
|
+
|
|
65
|
+
class Attribute(object):
|
|
66
|
+
"""Attribute class"""
|
|
67
|
+
|
|
68
|
+
# Kompat-Aliasse: identische Inhalte für die 6 Alt-dtypes; die Registry in
|
|
69
|
+
# :mod:`sdata.dtypes` ist die Single Source of Truth (+ bytes/json/uri).
|
|
70
|
+
DTYPES = dtypes.DTYPES
|
|
71
|
+
DTYPES_INV = dtypes.DTYPES_INV
|
|
72
|
+
|
|
73
|
+
def __init__(self, name, value, *, strict=False, **kwargs):
|
|
74
|
+
"""Attribute(name, value, dtype=str, unit="-", description="", label="", required=False,
|
|
75
|
+
ontology:"BFO:quality", strict=False)
|
|
76
|
+
:param name
|
|
77
|
+
:param value
|
|
78
|
+
:param dtype ['float', 'int', 'str', 'timestamp', 'bool', 'list', 'bytes', 'json', 'uri']
|
|
79
|
+
:param description
|
|
80
|
+
:param unit
|
|
81
|
+
:param label
|
|
82
|
+
:param required
|
|
83
|
+
:param ontology
|
|
84
|
+
:param strict: bei True wirft fehlgeschlagene Coercion ``dtypes.DtypeError``
|
|
85
|
+
statt still zu degradieren (Default False = bisheriges Verhalten)
|
|
86
|
+
"""
|
|
87
|
+
self._strict: bool = bool(strict)
|
|
88
|
+
self._name: str = None
|
|
89
|
+
self._value: Any = None
|
|
90
|
+
self._unit:str = "-"
|
|
91
|
+
self._description:str = ""
|
|
92
|
+
self._label:str = ""
|
|
93
|
+
self._ontology:str = ""
|
|
94
|
+
self._symbol:str = ""
|
|
95
|
+
self._dtype:str = "str"
|
|
96
|
+
dtype = kwargs.get("dtype", None) or self.guess_dtype(value)
|
|
97
|
+
self._set_dtype(dtype)
|
|
98
|
+
self.name = name
|
|
99
|
+
self._set_description(kwargs.get("description", ""))
|
|
100
|
+
self._set_label(kwargs.get("label", ""))
|
|
101
|
+
self._set_unit(kwargs.get("unit", "-"))
|
|
102
|
+
self._set_required(kwargs.get("required", False))
|
|
103
|
+
self._set_ontology(kwargs.get("ontology", ""))
|
|
104
|
+
self._set_symbol(kwargs.get("symbol", ""))
|
|
105
|
+
# set dtype first!
|
|
106
|
+
self._set_value(value)
|
|
107
|
+
|
|
108
|
+
def _get_name(self):
|
|
109
|
+
return self._name
|
|
110
|
+
|
|
111
|
+
def _set_name(self, value):
|
|
112
|
+
if isinstance(value, str):
|
|
113
|
+
try:
|
|
114
|
+
value = value.strip()[:256]
|
|
115
|
+
if len(value) > 0:
|
|
116
|
+
self._name = value
|
|
117
|
+
else:
|
|
118
|
+
raise ValueError("empty Attribute.name")
|
|
119
|
+
except ValueError as exp:
|
|
120
|
+
logger.warning("error Attribute.name: %s" % exp)
|
|
121
|
+
else:
|
|
122
|
+
self._name = str(value).strip()[:256]
|
|
123
|
+
|
|
124
|
+
name = property(fget=_get_name, fset=_set_name, doc="Attribute name")
|
|
125
|
+
|
|
126
|
+
def _get_value(self):
|
|
127
|
+
return self._value
|
|
128
|
+
|
|
129
|
+
def _set_value(self, value):
|
|
130
|
+
spec = dtypes.get(self._dtype) or dtypes.get("str")
|
|
131
|
+
try:
|
|
132
|
+
self._value = spec.coerce(value, strict=self._strict)
|
|
133
|
+
except dtypes.DtypeError as exp:
|
|
134
|
+
if self._strict:
|
|
135
|
+
raise
|
|
136
|
+
# lenient: loggen und vorhandenen Wert unverändert lassen (Altverhalten)
|
|
137
|
+
logger.error("error Attribute.value: {}".format(exp))
|
|
138
|
+
|
|
139
|
+
value = property(fget=_get_value, fset=_set_value, doc="Attribute value")
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def guess_dtype(value):
|
|
143
|
+
"""returns dtype class
|
|
144
|
+
|
|
145
|
+
:param value:
|
|
146
|
+
:return: __class__
|
|
147
|
+
"""
|
|
148
|
+
if isinstance(value, (int)):
|
|
149
|
+
return value.__class__
|
|
150
|
+
elif isinstance(value, (float)):
|
|
151
|
+
return value.__class__
|
|
152
|
+
elif isinstance(value, (str)):
|
|
153
|
+
return value.__class__
|
|
154
|
+
elif isinstance(value, (list, tuple)):
|
|
155
|
+
return "list[str]"
|
|
156
|
+
else:
|
|
157
|
+
return str
|
|
158
|
+
|
|
159
|
+
def _get_dtype(self):
|
|
160
|
+
return self._dtype
|
|
161
|
+
|
|
162
|
+
def _set_dtype(self, value):
|
|
163
|
+
"""set dtype str
|
|
164
|
+
|
|
165
|
+
Akzeptiert dtype-String ('float', 'float64', ...) ODER Klasse (float, int).
|
|
166
|
+
Ist bereits ein Wert gesetzt, wird er in den neuen dtype umgecastet.
|
|
167
|
+
|
|
168
|
+
:param value: dtype-String oder -Klasse
|
|
169
|
+
:return:
|
|
170
|
+
"""
|
|
171
|
+
name = dtypes.resolve(value)
|
|
172
|
+
if name is None:
|
|
173
|
+
return None
|
|
174
|
+
self._dtype = name
|
|
175
|
+
# Re-Cast eines bereits gesetzten Werts (während __init__ ist _value noch None)
|
|
176
|
+
if getattr(self, "_value", None) is not None:
|
|
177
|
+
self._set_value(self._value)
|
|
178
|
+
|
|
179
|
+
dtype = property(fget=_get_dtype, fset=_set_dtype, doc="Attribute type str")
|
|
180
|
+
|
|
181
|
+
def _get_description(self):
|
|
182
|
+
return self._description
|
|
183
|
+
|
|
184
|
+
def _set_description(self, value):
|
|
185
|
+
if value is None:
|
|
186
|
+
value = ""
|
|
187
|
+
self._description = str(value)
|
|
188
|
+
|
|
189
|
+
description = property(fget=_get_description, fset=_set_description, doc="Attribute description")
|
|
190
|
+
|
|
191
|
+
def _get_label(self):
|
|
192
|
+
return self._label
|
|
193
|
+
|
|
194
|
+
def _set_label(self, value):
|
|
195
|
+
if value is None:
|
|
196
|
+
value = ""
|
|
197
|
+
self._label = str(value)
|
|
198
|
+
|
|
199
|
+
label = property(fget=_get_label, fset=_set_label, doc="Attribute label")
|
|
200
|
+
|
|
201
|
+
def _get_unit(self):
|
|
202
|
+
return self._unit
|
|
203
|
+
|
|
204
|
+
def _set_unit(self, value):
|
|
205
|
+
self._unit = value
|
|
206
|
+
|
|
207
|
+
unit = property(fget=_get_unit, fset=_set_unit, doc="Attribute unit")
|
|
208
|
+
|
|
209
|
+
def _get_required(self):
|
|
210
|
+
return self._required
|
|
211
|
+
|
|
212
|
+
def _set_required(self, value):
|
|
213
|
+
if value in [True, 1, "true", "True"]:
|
|
214
|
+
self._required = True
|
|
215
|
+
else:
|
|
216
|
+
self._required = False
|
|
217
|
+
|
|
218
|
+
required = property(fget=_get_required, fset=_set_required, doc="Attribute required")
|
|
219
|
+
|
|
220
|
+
def _get_ontology(self):
|
|
221
|
+
return self._ontology
|
|
222
|
+
|
|
223
|
+
def _set_ontology(self, value):
|
|
224
|
+
if value is None:
|
|
225
|
+
value = ""
|
|
226
|
+
self._ontology = str(value)
|
|
227
|
+
|
|
228
|
+
ontology = property(fget=_get_ontology, fset=_set_ontology, doc="Attribute ontology")
|
|
229
|
+
|
|
230
|
+
def _get_symbol(self):
|
|
231
|
+
return self._symbol
|
|
232
|
+
|
|
233
|
+
def _set_symbol(self, value):
|
|
234
|
+
if value is None:
|
|
235
|
+
value = ""
|
|
236
|
+
self._symbol = str(value)
|
|
237
|
+
|
|
238
|
+
symbol = property(fget=_get_symbol, fset=_set_symbol,
|
|
239
|
+
doc="Formelzeichen der Größe, z. B. ``R_p0,2`` (RFC 0016)")
|
|
240
|
+
|
|
241
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
242
|
+
"""Return a dict of the attribute's items (name/value/unit/dtype/...)."""
|
|
243
|
+
return {'name': self.name,
|
|
244
|
+
'value': self.value,
|
|
245
|
+
'unit': self.unit,
|
|
246
|
+
'dtype': self.dtype,
|
|
247
|
+
'description': self.description,
|
|
248
|
+
'label': self.label,
|
|
249
|
+
'required': self.required,
|
|
250
|
+
'ontology': self.ontology,
|
|
251
|
+
'symbol': self.symbol,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
def to_list(self):
|
|
255
|
+
return [self.name, self.value, self.unit, self.dtype, self.description, self.label, self.required, self.ontology,
|
|
256
|
+
self.symbol]
|
|
257
|
+
|
|
258
|
+
def to_csv(self, prefix="", sep=",", quote=None):
|
|
259
|
+
"""export Attribute to csv
|
|
260
|
+
|
|
261
|
+
:param prefix:
|
|
262
|
+
:param sep:
|
|
263
|
+
:param quote:
|
|
264
|
+
:return:
|
|
265
|
+
"""
|
|
266
|
+
xs = []
|
|
267
|
+
for x in self.to_list():
|
|
268
|
+
if x is None:
|
|
269
|
+
xs.append("")
|
|
270
|
+
else:
|
|
271
|
+
xs.append(str(x))
|
|
272
|
+
return "{}{}".format(prefix, sep.join(xs))
|
|
273
|
+
|
|
274
|
+
def __str__(self):
|
|
275
|
+
return "(Attr'%s':%s(%s))" % (self.name, self.value, self.dtype)
|
|
276
|
+
|
|
277
|
+
__repr__ = __str__
|
|
278
|
+
|
|
279
|
+
def _repr_html_(self):
|
|
280
|
+
from sdata import interactive
|
|
281
|
+
return interactive.attribute_html(self)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class Metadata(object):
|
|
285
|
+
"""Metadata container class
|
|
286
|
+
|
|
287
|
+
each Metadata entry has has a
|
|
288
|
+
* name (256)
|
|
289
|
+
* value
|
|
290
|
+
* unit
|
|
291
|
+
* description
|
|
292
|
+
* type (int, str, float, bool, timestamp)
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
ATTRIBUTEKEYS = ["name", "value", "unit", "dtype", "description", "label", "required", "ontology", "symbol"]
|
|
296
|
+
#: reservierte Attribut-Schlüssel, der den Objektnamen autoritativ hält
|
|
297
|
+
RESERVED_NAME_KEY = "_sdata_name"
|
|
298
|
+
|
|
299
|
+
def __init__(self, **kwargs):
|
|
300
|
+
"""Metadata class
|
|
301
|
+
|
|
302
|
+
:param kwargs:
|
|
303
|
+
"""
|
|
304
|
+
self._attributes = SortedDict()
|
|
305
|
+
self._name = kwargs.get("name") or "N.N."
|
|
306
|
+
|
|
307
|
+
def _get_name(self):
|
|
308
|
+
# Single Source of Truth: das reservierte _sdata_name-Attribut ist
|
|
309
|
+
# autoritativ, sobald gesetzt; sonst der einfache _name-Fallback.
|
|
310
|
+
attr = self._attributes.get(self.RESERVED_NAME_KEY)
|
|
311
|
+
if attr is not None and attr.value:
|
|
312
|
+
return attr.value
|
|
313
|
+
return self._name
|
|
314
|
+
|
|
315
|
+
def _set_name(self, value):
|
|
316
|
+
self._name = str(value)
|
|
317
|
+
attr = self._attributes.get(self.RESERVED_NAME_KEY)
|
|
318
|
+
if attr is not None:
|
|
319
|
+
attr.value = str(value)
|
|
320
|
+
|
|
321
|
+
name = property(fget=_get_name, fset=_set_name, doc="Name of the Metadata")
|
|
322
|
+
|
|
323
|
+
def _get_attributes(self):
|
|
324
|
+
return self._attributes
|
|
325
|
+
|
|
326
|
+
def _set_attributes(self, value):
|
|
327
|
+
self._attributes = value
|
|
328
|
+
|
|
329
|
+
attributes = property(fget=_get_attributes, fset=_set_attributes, doc="returns Attributes")
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def user_attributes(self):
|
|
333
|
+
attrs = [(a.name, a) for a in self.attributes.values() if not a.name.startswith("_sdata")]
|
|
334
|
+
return SortedDict(attrs)
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def sdata_attributes(self):
|
|
338
|
+
attrs = [(a.name, a) for a in self.attributes.values() if a.name.startswith("_sdata")]
|
|
339
|
+
return SortedDict(attrs)
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def required_attributes(self):
|
|
343
|
+
required_attributes = [(attr.name, attr) for attr in self.attributes.values() if attr.required is True]
|
|
344
|
+
return SortedDict(required_attributes)
|
|
345
|
+
|
|
346
|
+
def add_attribute(self, attr: Attribute, **kwargs: Any) -> None:
|
|
347
|
+
"""set Attribute"""
|
|
348
|
+
prefix: str = kwargs.get("prefix", "")
|
|
349
|
+
if not isinstance(attr, Attribute):
|
|
350
|
+
raise TypeError(f"attr must be an instance of Attribute, got {type(attr).__name__}")
|
|
351
|
+
self._attributes[prefix + attr.name] = attr
|
|
352
|
+
|
|
353
|
+
def set_attr(self, name="N.N.", value=None, **kwargs):
|
|
354
|
+
"""set Attribute"""
|
|
355
|
+
prefix = kwargs.get("prefix", "")
|
|
356
|
+
if isinstance(name, Attribute) or "Attribute" in name.__class__.__name__:
|
|
357
|
+
attr = name # name is the Attribute!
|
|
358
|
+
else:
|
|
359
|
+
attr = self.get_attr(prefix + name) or Attribute(name, value, **kwargs)
|
|
360
|
+
for key in ["unit", "dtype", "description", "label", "required", "ontology", "symbol"]:
|
|
361
|
+
if key in kwargs:
|
|
362
|
+
setattr(attr, key, kwargs.get(key))
|
|
363
|
+
if value is not None:
|
|
364
|
+
attr.value = value
|
|
365
|
+
self._attributes[prefix + attr.name] = attr
|
|
366
|
+
|
|
367
|
+
def get_attr(self, name: str) -> Optional["Attribute"]:
|
|
368
|
+
"""get Attribute by name"""
|
|
369
|
+
return self._attributes.get(name, None)
|
|
370
|
+
|
|
371
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
372
|
+
"""serialize attributes to dict"""
|
|
373
|
+
d = {}
|
|
374
|
+
for attr in self.attributes.values():
|
|
375
|
+
d[attr.name] = attr.to_dict()
|
|
376
|
+
return d
|
|
377
|
+
|
|
378
|
+
@staticmethod
|
|
379
|
+
def guess_dtype_from_value(value):
|
|
380
|
+
"""guess dtype from value,
|
|
381
|
+
e.g.
|
|
382
|
+
'1.23' -> 'float'
|
|
383
|
+
'otto1.23' -> 'str'
|
|
384
|
+
1 -> 'int'
|
|
385
|
+
False -> 'bool'
|
|
386
|
+
|
|
387
|
+
:param value:
|
|
388
|
+
:return: dtype(value), dtype ['int', 'float', 'bool', 'str', 'list']
|
|
389
|
+
"""
|
|
390
|
+
if value.__class__.__name__ in ["int", "float", "bool"]:
|
|
391
|
+
return value, value.__class__.__name__
|
|
392
|
+
elif value in ["False", "True", "true", "false"]:
|
|
393
|
+
return bool(value), 'bool'
|
|
394
|
+
try:
|
|
395
|
+
value = int(value)
|
|
396
|
+
return value, value.__class__.__name__
|
|
397
|
+
except:
|
|
398
|
+
pass
|
|
399
|
+
try:
|
|
400
|
+
value = float(value)
|
|
401
|
+
return value, value.__class__.__name__
|
|
402
|
+
except:
|
|
403
|
+
pass
|
|
404
|
+
return str(value), "str"
|
|
405
|
+
|
|
406
|
+
def update_from_dict(self, d, guess_dtype=True):
|
|
407
|
+
"""set attributes from dict
|
|
408
|
+
|
|
409
|
+
:param d: dict
|
|
410
|
+
:return:
|
|
411
|
+
"""
|
|
412
|
+
for k, v in d.items():
|
|
413
|
+
if guess_dtype is False:
|
|
414
|
+
value = v
|
|
415
|
+
dtype = None
|
|
416
|
+
else:
|
|
417
|
+
value, dtype = self.guess_dtype_from_value(v)
|
|
418
|
+
if dtype in ["float", "int", "bool"]:
|
|
419
|
+
v = {"name":k, "value":value}
|
|
420
|
+
# v = {"name":k, "value":value, "dtype":dtype, "unit":"", "description":"", "label":"", "required":False}
|
|
421
|
+
elif isinstance(v, (str,)):
|
|
422
|
+
v = {"name":k, "value":v, "dtype":"str", "unit":"", "description":"", "label":"", "required":False, "ontology":""}
|
|
423
|
+
elif hasattr(v, "keys"):
|
|
424
|
+
dtype = v.get("dtype", self.guess_dtype_from_value(v.get("value"))[1])
|
|
425
|
+
value = v.get("value")
|
|
426
|
+
v = {"name":k, "value":value, "dtype":dtype,
|
|
427
|
+
"unit":v.get("unit", ""), "description":v.get("description", ""),
|
|
428
|
+
"label":v.get("label", ""), "required":v.get("required", False),
|
|
429
|
+
"ontology":v.get("ontology", ""), "symbol":v.get("symbol", ""),}
|
|
430
|
+
else:
|
|
431
|
+
v, dtype = self.guess_dtype_from_value(v)
|
|
432
|
+
# v = {"name":k, "value":v, "dtype":dtype, "unit":"", "description":"", "label":"", "required":False}
|
|
433
|
+
v = {"name":k, "value":v, "dtype":dtype}
|
|
434
|
+
self.set_attr(**v)
|
|
435
|
+
|
|
436
|
+
@classmethod
|
|
437
|
+
def from_dict(cls, d):
|
|
438
|
+
"""setup metadata from dict"""
|
|
439
|
+
metadata = cls()
|
|
440
|
+
metadata.update_from_dict(d)
|
|
441
|
+
return metadata
|
|
442
|
+
|
|
443
|
+
def _to_dict(self, attributes):
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
:param attributes:
|
|
447
|
+
:return:
|
|
448
|
+
"""
|
|
449
|
+
d = {}
|
|
450
|
+
for attr in attributes.values():
|
|
451
|
+
d[attr.name] = attr.to_dict()
|
|
452
|
+
return d
|
|
453
|
+
|
|
454
|
+
def get_sdict(self):
|
|
455
|
+
"""get sdata attribute as dict"""
|
|
456
|
+
d = {}
|
|
457
|
+
for attr in self.sdata_attributes.values():
|
|
458
|
+
d[attr.name] = attr.value
|
|
459
|
+
return d
|
|
460
|
+
|
|
461
|
+
def get_udict(self):
|
|
462
|
+
"""get user attribute as dict"""
|
|
463
|
+
d = {}
|
|
464
|
+
for attr in self.user_attributes.values():
|
|
465
|
+
d[attr.name] = attr.value
|
|
466
|
+
return d
|
|
467
|
+
|
|
468
|
+
def get_dict(self):
|
|
469
|
+
"""get user attribute as dict"""
|
|
470
|
+
d = {}
|
|
471
|
+
for attr in self.attributes.values():
|
|
472
|
+
d[attr.name] = attr.value
|
|
473
|
+
return d
|
|
474
|
+
|
|
475
|
+
def _to_dataframe(self, attributes):
|
|
476
|
+
"""create dataframe from attributes"""
|
|
477
|
+
d = self._to_dict(attributes)
|
|
478
|
+
if len(d) == 0:
|
|
479
|
+
df = pd.DataFrame(columns=self.ATTRIBUTEKEYS)
|
|
480
|
+
else:
|
|
481
|
+
df = pd.DataFrame.from_dict(d, orient="index")
|
|
482
|
+
df.index.name = "key"
|
|
483
|
+
return df[self.ATTRIBUTEKEYS]
|
|
484
|
+
|
|
485
|
+
def to_dataframe(self):
|
|
486
|
+
"""create dataframe"""
|
|
487
|
+
return self._to_dataframe(self.attributes)
|
|
488
|
+
|
|
489
|
+
@property
|
|
490
|
+
def df(self):
|
|
491
|
+
"""create dataframe"""
|
|
492
|
+
return self._to_dataframe(self.attributes)
|
|
493
|
+
|
|
494
|
+
@property
|
|
495
|
+
def udf(self):
|
|
496
|
+
"""create dataframe for user attributes"""
|
|
497
|
+
return self._to_dataframe(self.user_attributes)
|
|
498
|
+
|
|
499
|
+
@property
|
|
500
|
+
def dft(self):
|
|
501
|
+
"""create transposed dataframe for sdata attributes"""
|
|
502
|
+
mt = self.df[["value"]].transpose().copy()
|
|
503
|
+
mt.index = [self.get("_sdata_sname").value]
|
|
504
|
+
return mt
|
|
505
|
+
|
|
506
|
+
def get_dft(self, index_name=None):
|
|
507
|
+
"""create transposed dataframe for sdata attributes"""
|
|
508
|
+
if index_name is None:
|
|
509
|
+
index_name = "_sdata_sname"
|
|
510
|
+
mt = self.df[["value"]].transpose().copy()
|
|
511
|
+
mt.index = [self.get(index_name).value]
|
|
512
|
+
return mt
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@property
|
|
516
|
+
def sdf(self):
|
|
517
|
+
"""create dataframe for sdata attributes"""
|
|
518
|
+
return self._to_dataframe(self.sdata_attributes)
|
|
519
|
+
|
|
520
|
+
@property
|
|
521
|
+
def sdft(self):
|
|
522
|
+
"""create transposed dataframe for sdata attributes"""
|
|
523
|
+
mt = self.sdf[["value"]].transpose().copy()
|
|
524
|
+
mt.index = [self.get("_sdata_uuid").value]
|
|
525
|
+
return mt
|
|
526
|
+
|
|
527
|
+
@classmethod
|
|
528
|
+
def from_dataframe(cls, df):
|
|
529
|
+
"""create metadata from dataframe"""
|
|
530
|
+
df["unit"] = df.unit.fillna("")
|
|
531
|
+
df["label"] =df.label.fillna("")
|
|
532
|
+
df["description"] =df.description.fillna("")
|
|
533
|
+
df["symbol"] = df["symbol"].fillna("") if "symbol" in df.columns else ""
|
|
534
|
+
d = df.to_dict(orient='index')
|
|
535
|
+
metadata = cls.from_dict(d)
|
|
536
|
+
return metadata
|
|
537
|
+
|
|
538
|
+
def update_from_usermetadata(self, metadata):
|
|
539
|
+
"""update user metadata from metadata"""
|
|
540
|
+
for attribute in metadata.user_attributes.values():
|
|
541
|
+
self.add(attribute)
|
|
542
|
+
|
|
543
|
+
def to_csv(self, filepath=None, sep=",", header=False):
|
|
544
|
+
"""serialize to csv"""
|
|
545
|
+
try:
|
|
546
|
+
df = self.to_dataframe()
|
|
547
|
+
# df.to_csv(filepath, index=None, sep=sep)
|
|
548
|
+
return df.to_csv(filepath, index=None, sep=sep, header=header)
|
|
549
|
+
except OSError as exp:
|
|
550
|
+
logger.error("metadata.to_csv error: %s" % (exp))
|
|
551
|
+
|
|
552
|
+
def to_csv_header(self, prefix="#", sep=",", filepath=None):
|
|
553
|
+
"""serialize to csv"""
|
|
554
|
+
try:
|
|
555
|
+
lines = []
|
|
556
|
+
for attr in self.attributes.values():
|
|
557
|
+
lines.append(attr.to_csv(prefix=prefix, sep=sep)+"\n")
|
|
558
|
+
|
|
559
|
+
alines = "".join(lines)
|
|
560
|
+
if filepath:
|
|
561
|
+
logger.info("export '{}'".format(filepath))
|
|
562
|
+
with open(filepath, "w") as fh:
|
|
563
|
+
fh.write(alines)
|
|
564
|
+
return alines
|
|
565
|
+
except OSError as exp:
|
|
566
|
+
logger.error("metadata.to_csv error: %s" % (exp))
|
|
567
|
+
|
|
568
|
+
@classmethod
|
|
569
|
+
def from_csv(cls, filepath):
|
|
570
|
+
"""create metadata from dataframe"""
|
|
571
|
+
df = pd.read_csv(filepath, header=None)
|
|
572
|
+
# Dateien vor RFC 0016 haben 8 Spalten (ohne symbol)
|
|
573
|
+
df.columns = cls.ATTRIBUTEKEYS[:len(df.columns)]
|
|
574
|
+
# Index nach Spalte "name" (Spalte behalten). Kein df.name.values, da das
|
|
575
|
+
# in neueren pandas-Versionen ein StringArray ist, den set_index als
|
|
576
|
+
# unhashbaren Key behandelt (TypeError).
|
|
577
|
+
df.set_index("name", drop=False, inplace=True)
|
|
578
|
+
metadata = cls.from_dataframe(df)
|
|
579
|
+
return metadata
|
|
580
|
+
|
|
581
|
+
def to_json(self, filepath=None):
|
|
582
|
+
"""create a json
|
|
583
|
+
|
|
584
|
+
:param filepath: default None
|
|
585
|
+
:return: json str
|
|
586
|
+
"""
|
|
587
|
+
d = self.to_dict()
|
|
588
|
+
if filepath:
|
|
589
|
+
with open(filepath, "w") as fh:
|
|
590
|
+
json.dump(d, fh, default=dtypes.json_default)
|
|
591
|
+
return json.dumps(d, default=dtypes.json_default)
|
|
592
|
+
|
|
593
|
+
@classmethod
|
|
594
|
+
def from_json(cls, jsonstr=None, filepath=None):
|
|
595
|
+
"""create metadata from json file
|
|
596
|
+
|
|
597
|
+
:param jsonstr: json str
|
|
598
|
+
:param filepath: filepath to json file
|
|
599
|
+
:return: Metadata
|
|
600
|
+
:raises ValueError: if neither jsonstr nor an existing filepath is given
|
|
601
|
+
"""
|
|
602
|
+
|
|
603
|
+
if filepath is not None and os.path.exists(filepath):
|
|
604
|
+
with open(filepath, "r") as fh:
|
|
605
|
+
j = json.load(fh)
|
|
606
|
+
metadata = cls.from_dict(j)
|
|
607
|
+
elif jsonstr is not None:
|
|
608
|
+
j = json.loads(jsonstr)
|
|
609
|
+
metadata = cls.from_dict(j)
|
|
610
|
+
else:
|
|
611
|
+
if filepath is not None:
|
|
612
|
+
raise ValueError(
|
|
613
|
+
f"Metadata.from_json: file not found and no jsonstr given: {filepath!r}")
|
|
614
|
+
raise ValueError("Metadata.from_json requires jsonstr or filepath")
|
|
615
|
+
return metadata
|
|
616
|
+
|
|
617
|
+
def to_list(self):
|
|
618
|
+
"""create a nested list of Attribute values
|
|
619
|
+
|
|
620
|
+
:return: list
|
|
621
|
+
"""
|
|
622
|
+
return self.df.values.tolist()
|
|
623
|
+
|
|
624
|
+
@classmethod
|
|
625
|
+
def from_list(cls, mlist):
|
|
626
|
+
"""create metadata from a list of Attribute values
|
|
627
|
+
|
|
628
|
+
[['force_x', 1.2, 'kN', 'float', 'force in x-direction'],
|
|
629
|
+
['force_y', 3.1, 'N', 'float', 'force in y-direction', 'label', True]]
|
|
630
|
+
"""
|
|
631
|
+
metadata = cls()
|
|
632
|
+
for alist in mlist:
|
|
633
|
+
if len(alist) < 2:
|
|
634
|
+
logger.error("Metadata.from_list skip {}".format(alist))
|
|
635
|
+
else:
|
|
636
|
+
alist.extend(["", "", "", "", ""])
|
|
637
|
+
#["name", "value", "unit", "dtype", "description", "label", "required"]
|
|
638
|
+
metadata.add(alist[0], alist[1], unit=alist[2], dtype=alist[3], description=alist[4],
|
|
639
|
+
label=alist[5], required=alist[6])
|
|
640
|
+
return metadata
|
|
641
|
+
|
|
642
|
+
# --- JSON-LD / Linked Data / Sidecar (siehe sdata.semantic) -------------
|
|
643
|
+
def to_jsonld(self, context_mode="inline"):
|
|
644
|
+
"""Serialisiere als selbstbeschreibendes JSON-LD-Dokument (dict)."""
|
|
645
|
+
from sdata import semantic
|
|
646
|
+
return semantic.to_jsonld(self, context_mode=context_mode)
|
|
647
|
+
|
|
648
|
+
@classmethod
|
|
649
|
+
def from_jsonld(cls, doc):
|
|
650
|
+
"""Rekonstruiere Metadata aus einem JSON-LD-Dokument (dict oder str)."""
|
|
651
|
+
from sdata import semantic
|
|
652
|
+
return semantic.from_jsonld(doc)
|
|
653
|
+
|
|
654
|
+
def get_prefixed(self, prefix):
|
|
655
|
+
"""Neue Metadata nur mit Attributen, deren Schlüssel mit ``prefix`` beginnt."""
|
|
656
|
+
sub = Metadata()
|
|
657
|
+
for key, attr in self._attributes.items():
|
|
658
|
+
if key.startswith(prefix):
|
|
659
|
+
sub._attributes[key] = attr
|
|
660
|
+
return sub
|
|
661
|
+
|
|
662
|
+
@property
|
|
663
|
+
def a(self):
|
|
664
|
+
"""Attribut-Autocomplete-Accessor für die interaktive Nutzung (``m.a.force_x``)."""
|
|
665
|
+
from sdata import interactive
|
|
666
|
+
return interactive.AttrAccessor(self)
|
|
667
|
+
|
|
668
|
+
def _repr_html_(self):
|
|
669
|
+
from sdata import interactive
|
|
670
|
+
return interactive.metadata_html(self)
|
|
671
|
+
|
|
672
|
+
def validate(self, schema):
|
|
673
|
+
"""Validiere diese Metadaten gegen ein :class:`sdata.schema.MetadataSchema`."""
|
|
674
|
+
return schema.validate(self)
|
|
675
|
+
|
|
676
|
+
def apply_schema(self, schema):
|
|
677
|
+
"""Vervollständige diese Metadaten in-place gemäß einem MetadataSchema."""
|
|
678
|
+
return schema.apply(self)
|
|
679
|
+
|
|
680
|
+
def to_rdf(self, fmt="turtle"):
|
|
681
|
+
"""Serialisiere als RDF (rdflib falls vorhanden, sonst JSON-LD)."""
|
|
682
|
+
from sdata import semantic
|
|
683
|
+
return semantic.to_rdf(self, fmt=fmt)
|
|
684
|
+
|
|
685
|
+
def to_verifiable_credential(self, issuer_did, issuer_priv_jwk, kid=None,
|
|
686
|
+
extra_claims=None):
|
|
687
|
+
"""Signiere die Metadaten als W3C Verifiable Credential (Compact-JWS)."""
|
|
688
|
+
from sdata import semantic
|
|
689
|
+
return semantic.to_verifiable_credential(
|
|
690
|
+
self, issuer_did, issuer_priv_jwk, kid=kid, extra_claims=extra_claims)
|
|
691
|
+
|
|
692
|
+
@classmethod
|
|
693
|
+
def from_verifiable_credential(cls, vc_jws, pub_jwk):
|
|
694
|
+
"""Verifiziere ein VC und rekonstruiere die Metadata aus dem credentialSubject."""
|
|
695
|
+
from sdata import semantic
|
|
696
|
+
return cls.from_jsonld(semantic.verify_credential(vc_jws, pub_jwk))
|
|
697
|
+
|
|
698
|
+
def to_turtle(self):
|
|
699
|
+
"""Convenience: RDF im Turtle-Format."""
|
|
700
|
+
from sdata import semantic
|
|
701
|
+
return semantic.to_turtle(self)
|
|
702
|
+
|
|
703
|
+
def write_sidecar(self, path=None, indent=2):
|
|
704
|
+
"""Schreibe ``<sname>.meta.jsonld`` neben einen Datenblob; gibt den Pfad zurück."""
|
|
705
|
+
from sdata import semantic
|
|
706
|
+
return semantic.write_sidecar(self, path=path, indent=indent)
|
|
707
|
+
|
|
708
|
+
@classmethod
|
|
709
|
+
def read_sidecar(cls, path):
|
|
710
|
+
"""Lade eine ``.meta.jsonld``-Sidecar-Datei zu Metadata."""
|
|
711
|
+
from sdata import semantic
|
|
712
|
+
return semantic.read_sidecar(path)
|
|
713
|
+
|
|
714
|
+
def __repr__(self):
|
|
715
|
+
return "(Metadata'%s':%d)" % (self.name, len(self.attributes))
|
|
716
|
+
|
|
717
|
+
def __str__(self):
|
|
718
|
+
return "(Metadata'%s':%d %s)" % (self.name, len(self.attributes), [x for x in self.attributes])
|
|
719
|
+
|
|
720
|
+
def add(self, name, value=None, **kwargs):
|
|
721
|
+
"""add Attribute
|
|
722
|
+
|
|
723
|
+
:param name:
|
|
724
|
+
:param value:
|
|
725
|
+
:param kwargs:
|
|
726
|
+
:return:
|
|
727
|
+
"""
|
|
728
|
+
self.set_attr(name, value, **kwargs)
|
|
729
|
+
|
|
730
|
+
def relabel(self, name, newname):
|
|
731
|
+
"""relabel Attribute
|
|
732
|
+
|
|
733
|
+
:param name: old attribute name
|
|
734
|
+
:param newname: new attribute name
|
|
735
|
+
:return: None
|
|
736
|
+
"""
|
|
737
|
+
attr = self._attributes.get(name)
|
|
738
|
+
if attr is None:
|
|
739
|
+
logger.warning("{0}: no Attribute {1} to relabel.".format(self.__class__, name))
|
|
740
|
+
return
|
|
741
|
+
# explizit umschlüsseln (kein Präfix-Raten -> robust auch bei prefixed keys)
|
|
742
|
+
self._attributes.pop(name)
|
|
743
|
+
attr.name = newname
|
|
744
|
+
self._attributes[newname] = attr
|
|
745
|
+
|
|
746
|
+
def get(self, name: str, default: Any = None) -> Any:
|
|
747
|
+
#default = default or Attribute(name=name, value=None)
|
|
748
|
+
if self._attributes.get(name) is not None:
|
|
749
|
+
return self._attributes.get(name)
|
|
750
|
+
else:
|
|
751
|
+
return default
|
|
752
|
+
|
|
753
|
+
def keys(self) -> List[str]:
|
|
754
|
+
"""
|
|
755
|
+
|
|
756
|
+
:return: list of Attribute names
|
|
757
|
+
"""
|
|
758
|
+
return list(self._attributes.keys())
|
|
759
|
+
|
|
760
|
+
def values(self):
|
|
761
|
+
"""
|
|
762
|
+
|
|
763
|
+
:return: list of Attribute values
|
|
764
|
+
"""
|
|
765
|
+
return list(self._attributes.values())
|
|
766
|
+
|
|
767
|
+
def items(self):
|
|
768
|
+
"""
|
|
769
|
+
|
|
770
|
+
:return: list of Attribute items (keys, values)
|
|
771
|
+
"""
|
|
772
|
+
return list(self._attributes.items())
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def copy(self):
|
|
776
|
+
"""returns a deep copy"""
|
|
777
|
+
return copy.deepcopy(self)
|
|
778
|
+
|
|
779
|
+
@property
|
|
780
|
+
def size(self):
|
|
781
|
+
"""return number uf Attribute"""
|
|
782
|
+
return len(self.attributes)
|
|
783
|
+
|
|
784
|
+
def __getitem__(self, name):
|
|
785
|
+
return self.get(name)
|
|
786
|
+
|
|
787
|
+
def __setitem__(self, name, value):
|
|
788
|
+
"""update value in Attribute or create new Attribute"""
|
|
789
|
+
attr = self.get(name)
|
|
790
|
+
|
|
791
|
+
if attr is None:
|
|
792
|
+
attr = Attribute(name=name, value=value, dtype=type(value).__name__)
|
|
793
|
+
self._attributes[attr.name] = attr
|
|
794
|
+
else:
|
|
795
|
+
dtype = attr.guess_dtype(value)
|
|
796
|
+
attr.dtype = dtype
|
|
797
|
+
attr.value = dtype(value)
|
|
798
|
+
|
|
799
|
+
def __contains__(self, item):
|
|
800
|
+
return item in self.attributes.keys()
|
|
801
|
+
|
|
802
|
+
def pop(self, item):
|
|
803
|
+
try:
|
|
804
|
+
return self._attributes.pop(item)
|
|
805
|
+
except KeyError as exp:
|
|
806
|
+
return None
|
|
807
|
+
|
|
808
|
+
def __iter__(self):
|
|
809
|
+
for x in self.attributes.items():
|
|
810
|
+
yield x
|
|
811
|
+
|
|
812
|
+
@property
|
|
813
|
+
def sha3_256(self):
|
|
814
|
+
"""Return a new SHA3 hash object with a hashbit length of 32 bytes.
|
|
815
|
+
|
|
816
|
+
:return: hashlib.sha3_256.hexdigest()
|
|
817
|
+
"""
|
|
818
|
+
s = hashlib.sha3_256()
|
|
819
|
+
metadatastr = self.to_json().encode(errors="replace")
|
|
820
|
+
s.update(metadatastr)
|
|
821
|
+
return s.hexdigest()
|
|
822
|
+
|
|
823
|
+
def update_hash(self, hashobject):
|
|
824
|
+
"""A hash represents the object used to calculate a checksum of a
|
|
825
|
+
string of information.
|
|
826
|
+
|
|
827
|
+
.. code-block:: python
|
|
828
|
+
|
|
829
|
+
hashobject = hashlib.sha3_256()
|
|
830
|
+
metadata = Metadata()
|
|
831
|
+
metadata.update_hash(hashobject)
|
|
832
|
+
hash.hexdigest()
|
|
833
|
+
|
|
834
|
+
:param hashobject: hash object
|
|
835
|
+
:return: hash_function().hexdigest()
|
|
836
|
+
"""
|
|
837
|
+
if not (hasattr(hashobject, "update") and hasattr(hashobject, "hexdigest")):
|
|
838
|
+
logger.error("Metadata.hash: given hashfunction is invalid")
|
|
839
|
+
raise Exception("Metadata.hash: given hashfunction is invalid")
|
|
840
|
+
|
|
841
|
+
metadatastr = self.to_json().encode(errors="replace")
|
|
842
|
+
hashobject.update(metadatastr)
|
|
843
|
+
return hashobject
|
|
844
|
+
|
|
845
|
+
def set_unit_from_name(self, add_description=True, fix_name=True):
|
|
846
|
+
"""try to extract unit from attribute name
|
|
847
|
+
|
|
848
|
+
:return:
|
|
849
|
+
"""
|
|
850
|
+
for attr in self.user_attributes.values():
|
|
851
|
+
new_name, unit = extract_name_unit(attr.name)
|
|
852
|
+
attr.unit = unit
|
|
853
|
+
if add_description is True and len(attr.description)==0 and len(unit)>0:
|
|
854
|
+
attr.description = attr.name
|
|
855
|
+
if fix_name is True:
|
|
856
|
+
self.relabel(attr.name, new_name)
|
|
857
|
+
|
|
858
|
+
def guess_value_dtype(self):
|
|
859
|
+
"""try to cast the Attribute values, e.g. str -> float
|
|
860
|
+
|
|
861
|
+
:return:
|
|
862
|
+
"""
|
|
863
|
+
for attr in list(self.user_attributes.values()):
|
|
864
|
+
for dtype in [float, str]:
|
|
865
|
+
try:
|
|
866
|
+
# attr.value = dtype(attr.value)
|
|
867
|
+
# attr.dtype = dtype.__name__
|
|
868
|
+
self.add(name=attr.name, value=dtype(attr.value), dtype=dtype.__name__)
|
|
869
|
+
# print(["ok", attr.name, attr.value, self.get(attr.name).value])
|
|
870
|
+
break
|
|
871
|
+
except (ValueError, TypeError) as exp:
|
|
872
|
+
pass
|
|
873
|
+
# print([attr.name, attr.value, dtype, exp])
|
|
874
|
+
|
|
875
|
+
def is_complete(self):
|
|
876
|
+
"""check all required attributes"""
|
|
877
|
+
required_attributes = self.required_attributes.values()
|
|
878
|
+
for attr in required_attributes:
|
|
879
|
+
if attr.value is None or attr.value=="":
|
|
880
|
+
return False
|
|
881
|
+
|
|
882
|
+
return True
|