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/iolib/pud.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
logger = logging.getLogger("sdata")
|
|
5
|
+
import os
|
|
6
|
+
import uuid
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from sdata.iolib import PID
|
|
10
|
+
from sdata import Data
|
|
11
|
+
|
|
12
|
+
class Pud(Data):
|
|
13
|
+
"""run object, e.g. single tension test simulation"""
|
|
14
|
+
|
|
15
|
+
ATTRIBUTES = ["material_norm_name",
|
|
16
|
+
"material_number_norm",
|
|
17
|
+
"material_name",
|
|
18
|
+
"test",
|
|
19
|
+
"sample_ident_number",
|
|
20
|
+
"sample_geometry",
|
|
21
|
+
"sample_direction",
|
|
22
|
+
"nominal_pre_deformation <%>",
|
|
23
|
+
"actual_pre_deformation <%>",
|
|
24
|
+
"direction_of_pre_deformation",
|
|
25
|
+
"heat_treatment",
|
|
26
|
+
"actual_sample_width_<mm>",
|
|
27
|
+
"actual_sample_thickness_<mm>",
|
|
28
|
+
"actual_gauge_length_<mm>",
|
|
29
|
+
"nominal_testing_temperature_<K>",
|
|
30
|
+
"nominal_testing_speed_<m/s>",
|
|
31
|
+
"order",
|
|
32
|
+
"date_of_test_<dd.mm.yyyy>",
|
|
33
|
+
"tester",
|
|
34
|
+
"place_of_test",
|
|
35
|
+
"remark",
|
|
36
|
+
"data",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
def __init__(self, **kwargs):
|
|
40
|
+
""""""
|
|
41
|
+
Data.__init__(self, **kwargs)
|
|
42
|
+
# for attr in self.ATTRIBUTES:
|
|
43
|
+
# self.set_attr(attr, None)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_file(cls, filepath):
|
|
48
|
+
"""read pud file
|
|
49
|
+
|
|
50
|
+
.. code-block:: none
|
|
51
|
+
|
|
52
|
+
WERKSTOFF_NORM_NAME = HC340LA
|
|
53
|
+
WERKSTOFFNUMMER_NORM =
|
|
54
|
+
MATERIALNAME = HC340LA
|
|
55
|
+
PRUEFUNG = FLIESSKURVE
|
|
56
|
+
PROBENIDENTNUMMER = id0815
|
|
57
|
+
PROBENGEOMETRIE =
|
|
58
|
+
ENTNAHMERICHTUNG = Quer (90deg)
|
|
59
|
+
VORVERFORMUNG_SOLL <%> = 0
|
|
60
|
+
VORVERFORMUNG_IST <%> =
|
|
61
|
+
VORVERFORMUNGSRICHTUNG = Unverformt
|
|
62
|
+
WAERMEBEHANDLUNG = O
|
|
63
|
+
PROBENBREITE_IST <mm> = 20.014
|
|
64
|
+
PROBENDICKE_IST <mm> = 0.751
|
|
65
|
+
MESSLAENGE_IST <mm> = 80.0
|
|
66
|
+
MESSLAENGE_IST_FD <mm> = 80.0
|
|
67
|
+
PRUEFTEMPERATUR_SOLL <K> = 293
|
|
68
|
+
PRUEFGESCHWINDIGKEIT_SOLL <mm/s> = 0.32
|
|
69
|
+
PRUEFGESCHWINDIGKEIT_IST <mm/s> = 0.32
|
|
70
|
+
DEHNRATE_SOLL <1/s> = 0.004
|
|
71
|
+
DEHNRATE_IST <1/s> = 0.004
|
|
72
|
+
AUFTRAG =
|
|
73
|
+
PRUEFDATUM <tt.mm.jjjj> = 19.03.2017
|
|
74
|
+
PRUEFER = Otto
|
|
75
|
+
PRUEFSTELLE = SALZGITTER AG
|
|
76
|
+
BEMERKUNG = ASL 2009-056
|
|
77
|
+
DATEN =
|
|
78
|
+
ZEIT <s>; KRAFT <N>; WEG <mm>; BREITENAENDERUNG <mm>; WEG_FD <mm>
|
|
79
|
+
1.2372;192.181;-0.0235;0.0012;-0.0235
|
|
80
|
+
1.2772;198.325;-0.0231;0.0012;-0.0231
|
|
81
|
+
1.2972;201.397;-0.0227;0.0012;-0.0227
|
|
82
|
+
1.3172;205.152;-0.0224;0.0013;-0.0224
|
|
83
|
+
1.3572;211.638;-0.022;0.0013;-0.022
|
|
84
|
+
1.3972;218.123;-0.0213;0.0013;-0.0213
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
if not os.path.exists(filepath):
|
|
88
|
+
raise Exception("File not exists '{}'".format(filepath))
|
|
89
|
+
|
|
90
|
+
filename = os.path.split(filepath)[-1]
|
|
91
|
+
tt = cls(name=filename.upper().replace(".TXT", ""))
|
|
92
|
+
|
|
93
|
+
df = None
|
|
94
|
+
# https://docs.python.org/2/library/codecs.html#standard-encodings
|
|
95
|
+
for encoding in ['ISO-8859-1', "ascii", "utf-8", "iso8859_1", "latin1", 'ISO-8859-1', 'cp1252', "iso8859_2", ]:
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
logging.debug("try {}".format(encoding))
|
|
99
|
+
df = pd.read_csv(filepath, encoding=encoding, sep="=", header=None)
|
|
100
|
+
if df is not None:
|
|
101
|
+
break
|
|
102
|
+
except: # pragma: no cover - Encoding-Fallback
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
startindex = df[df[0].str.startswith("DATEN")].iloc[0].name + 1
|
|
106
|
+
attributes = df.loc[:startindex - 2]
|
|
107
|
+
attributes.columns = ["key", "value"]
|
|
108
|
+
|
|
109
|
+
table = {
|
|
110
|
+
0xe4 : u'ae',
|
|
111
|
+
ord(u'ö'): u'oe',
|
|
112
|
+
ord(u'ü'): u'ue',
|
|
113
|
+
ord(u'ß'): u'ss',
|
|
114
|
+
ord(u'Ä'): u'AE',
|
|
115
|
+
ord(u'Ö'): u'OE',
|
|
116
|
+
ord(u'Ü'): u'UE',
|
|
117
|
+
ord(u'ß'): u'SS',
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for row, attr in attributes.iterrows():
|
|
121
|
+
s = attr.key # .decode('utf8')
|
|
122
|
+
attr.key = s.translate(table)
|
|
123
|
+
try:
|
|
124
|
+
tt.metadata.set_attr(attr.key.strip(), attr.value.strip())
|
|
125
|
+
except AttributeError as exp:
|
|
126
|
+
tt.metadata.set_attr(attr.key.strip(), attr.value)
|
|
127
|
+
except UnicodeEncodeError as exp: # pragma: no cover - seltener Encoding-Fehler
|
|
128
|
+
print("UnicodeEncodeError {}".format(attr.key))
|
|
129
|
+
raise
|
|
130
|
+
|
|
131
|
+
table = df.loc[startindex:].copy()
|
|
132
|
+
table = table[0].str.split(";", expand=True)
|
|
133
|
+
table.columns = [x.strip() for x in table.iloc[0].values]
|
|
134
|
+
table = table.drop(index=startindex)
|
|
135
|
+
for col in table.columns:
|
|
136
|
+
table[col] = table[col].astype(float)
|
|
137
|
+
|
|
138
|
+
if 'KRAFT <kN>' in table.columns: # pragma: no cover - kN-Einheit selten
|
|
139
|
+
table['KRAFT <N>'] = table['KRAFT <kN>'] * 1e3
|
|
140
|
+
|
|
141
|
+
# print(table)
|
|
142
|
+
# print(attributes)
|
|
143
|
+
# mapper={"Kraft <N>":"force",
|
|
144
|
+
# 'Weg <mm>':"displacement",
|
|
145
|
+
# "Zeit <s>":"time",
|
|
146
|
+
# "Breitenaenderung <mm>":"displacement_y",
|
|
147
|
+
# "KRAFT <N>":"force",
|
|
148
|
+
# 'WEG <mm>':"displacement",
|
|
149
|
+
# "ZEIT <s>":"time",
|
|
150
|
+
# "BREITENAENDERUNG <mm>":"displacement_y"}
|
|
151
|
+
#
|
|
152
|
+
# table = table.rename(columns=mapper)
|
|
153
|
+
|
|
154
|
+
attr = tt.metadata.get("PROBENIDENTNUMMER")
|
|
155
|
+
tt.uuid = uuid.uuid5(uuid.NAMESPACE_DNS, str(attr.name.upper()))
|
|
156
|
+
|
|
157
|
+
tt.table = table
|
|
158
|
+
|
|
159
|
+
l0 = tt.metadata.get('MESSLAENGE_IST <mm>', np.nan)
|
|
160
|
+
width = tt.metadata.get('PROBENBREITE_IST <mm>', np.nan)
|
|
161
|
+
thickness = tt.metadata.get('PROBENDICKE_IST <mm>', np.nan)
|
|
162
|
+
actual_testing_temperature = tt.metadata.get('PRUEFTEMPERATUR_SOLL <K>', np.nan)
|
|
163
|
+
sample_direction = tt.metadata.get('ENTNAHMERICHTUNG', np.nan)
|
|
164
|
+
nominal_pre_deformation = tt.metadata.get('VORVERFORMUNG_SOLL <%>', np.nan)
|
|
165
|
+
actual_pre_deformation = tt.metadata.get('VORVERFORMUNG_IST <%>', np.nan)
|
|
166
|
+
nominal_strain_rate = tt.metadata.get('DEHNRATE_SOLL <1/s>', np.nan)
|
|
167
|
+
actual_strain_rate = tt.metadata.get('DEHNRATE_IST <1/s>', np.nan)
|
|
168
|
+
place_of_test = tt.metadata.get('PRUEFSTELLE', np.nan)
|
|
169
|
+
|
|
170
|
+
mapper = {"Kraft <N>" : "force",
|
|
171
|
+
'Weg <mm>' : "displacement",
|
|
172
|
+
"Zeit <s>" : "time",
|
|
173
|
+
"Breitenaenderung <mm>": "displacement_y",
|
|
174
|
+
"KRAFT <N>" : "force",
|
|
175
|
+
'WEG <mm>' : "displacement",
|
|
176
|
+
"ZEIT <s>" : "time",
|
|
177
|
+
"BREITENAENDERUNG <mm>": "displacement_y"}
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
l0 = float(l0)
|
|
181
|
+
except Exception as exp:
|
|
182
|
+
l0 = np.nan
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
width = float(width)
|
|
186
|
+
except Exception as exp:
|
|
187
|
+
width = np.nan
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
thickness = float(thickness)
|
|
191
|
+
except Exception as exp:
|
|
192
|
+
thickness = np.nan
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
actual_testing_temperature = float(actual_testing_temperature)
|
|
196
|
+
except Exception as exp:
|
|
197
|
+
actual_testing_temperature = np.nan
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
sample_direction = tt.metadata.get_orientation(sample_direction)
|
|
201
|
+
except Exception as exp:
|
|
202
|
+
sample_direction = np.nan
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
nominal_pre_deformation = float(nominal_pre_deformation) / 100.
|
|
206
|
+
except Exception as exp:
|
|
207
|
+
nominal_pre_deformation = np.nan
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
actual_pre_deformation = float(actual_pre_deformation) / 100.
|
|
211
|
+
except Exception as exp:
|
|
212
|
+
actual_pre_deformation = np.nan
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
nominal_strain_rate = float(nominal_strain_rate)
|
|
216
|
+
except Exception as exp:
|
|
217
|
+
nominal_strain_rate = np.nan
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
actual_strain_rate = float(actual_strain_rate)
|
|
221
|
+
except Exception as exp:
|
|
222
|
+
actual_strain_rate = np.nan
|
|
223
|
+
|
|
224
|
+
# try:
|
|
225
|
+
# place_of_test = str(place_of_test)
|
|
226
|
+
# except Exception as exp:
|
|
227
|
+
# place_of_test = "?"hash
|
|
228
|
+
|
|
229
|
+
area = width * thickness
|
|
230
|
+
tt.metadata.set_attr("l0", l0)
|
|
231
|
+
tt.metadata.set_attr("width", width)
|
|
232
|
+
tt.metadata.set_attr("thickness", thickness)
|
|
233
|
+
tt.metadata.set_attr("area", area)
|
|
234
|
+
tt.metadata.set_attr("actual_testing_temperature", actual_testing_temperature)
|
|
235
|
+
tt.metadata.set_attr("sample_direction", sample_direction)
|
|
236
|
+
tt.metadata.set_attr("nominal_pre_deformation", nominal_pre_deformation)
|
|
237
|
+
tt.metadata.set_attr("actual_pre_deformation", actual_pre_deformation)
|
|
238
|
+
tt.metadata.set_attr("nominal_strain_rate", nominal_strain_rate)
|
|
239
|
+
tt.metadata.set_attr("actual_strain_rate", actual_strain_rate)
|
|
240
|
+
tt.metadata.set_attr("place_of_test", place_of_test)
|
|
241
|
+
|
|
242
|
+
return tt
|
sdata/iolib/reader.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""Einheitliche Reader-/Source-Abstraktion für :class:`sdata.sclass.dataframe.DataFrame`.
|
|
2
|
+
|
|
3
|
+
RFC 0009 — das symmetrische Gegenstück zu :mod:`sdata.iolib.writer` (RFC 0007).
|
|
4
|
+
Ein gemeinsamer ``read()``-Vertrag, unter dem Quellen (Parquet-Datei/Objektspeicher,
|
|
5
|
+
JSON1-SQLite-Store, relationales SQL) austauschbar sind; die ``require_*``-Prüfung
|
|
6
|
+
aus dem Writer dient hier als **Eingangs**-Validierung einer Pipeline.
|
|
7
|
+
|
|
8
|
+
Konkrete Reader:
|
|
9
|
+
|
|
10
|
+
* :class:`ParquetReader` — eine fsspec-URI, ein Objekt (``_sdata`` reist im Format mit).
|
|
11
|
+
* :class:`StoreReader` — Selektor-Zugriff (``suuid``/``sname``/Record-ID) und Iteration
|
|
12
|
+
über einen :class:`~sdata.iolib.json1sqlitestore.JSON1SQLiteStore`.
|
|
13
|
+
* :class:`SqlReader` — verlustfreier Snapshot aus der ``sdata_dataframe_meta``-Tabelle
|
|
14
|
+
des :class:`~sdata.iolib.writer.SqlWriter`; ``fresh=True`` liest zusätzlich den
|
|
15
|
+
**aktuellen** Stand der Datentabelle.
|
|
16
|
+
|
|
17
|
+
``read()`` gibt das :class:`DataFrame` selbst zurück — kein ``ReadReceipt``-Pendant:
|
|
18
|
+
das Objekt trägt Identität (``suuid``/``sname``) und Provenienz bereits in seinen
|
|
19
|
+
Metadaten (RFC 0009 §8).
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import logging
|
|
25
|
+
from abc import ABC, abstractmethod
|
|
26
|
+
from typing import Any, Iterator, Protocol, Tuple, runtime_checkable, TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
import pandas as pd
|
|
29
|
+
|
|
30
|
+
from sdata.iolib.writer import check_contract, _safe_identifier, _SQL_META_TABLE
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
35
|
+
from sdata.sclass.dataframe import DataFrame
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"DataFrameReader",
|
|
39
|
+
"BaseDataFrameReader",
|
|
40
|
+
"ParquetReader",
|
|
41
|
+
"StoreReader",
|
|
42
|
+
"SqlReader",
|
|
43
|
+
"read_group",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
#: Jüngste Metazeile zu einem Selektor (suuid **oder** sname); Literal-SQL mit
|
|
47
|
+
#: qmark-Parametern — keine Identifier-Interpolation (wie im Writer, RFC 0007 C1).
|
|
48
|
+
_SQL_META_SELECT = (
|
|
49
|
+
"SELECT target_table, sdata FROM sdata_dataframe_meta "
|
|
50
|
+
"WHERE suuid = ? OR sname = ? ORDER BY rowid DESC LIMIT 1"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def read_group(reader: "DataFrameReader", keys, *, name: str = "group"):
|
|
55
|
+
"""Mehrere Mitglieder aus **einer** Quelle zu einer
|
|
56
|
+
:class:`~sdata.sclass.dataframegroup.DataFrameGroup` zusammenführen (RFC 0011).
|
|
57
|
+
|
|
58
|
+
:param reader: eine beliebige :class:`DataFrameReader`-Quelle.
|
|
59
|
+
:param keys: Iterable von Selektoren (suuid/sname/…), je einer pro Mitglied.
|
|
60
|
+
:param name: Name der erzeugten Gruppe.
|
|
61
|
+
:return: eine ``DataFrameGroup`` mit den gelesenen Mitgliedern (Schlüssel = Selektor).
|
|
62
|
+
"""
|
|
63
|
+
from sdata.sclass.dataframegroup import DataFrameGroup
|
|
64
|
+
group = DataFrameGroup(name=name)
|
|
65
|
+
with reader:
|
|
66
|
+
for key in keys:
|
|
67
|
+
group.add(reader.read(key), key=str(key), overwrite=True)
|
|
68
|
+
return group
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@runtime_checkable
|
|
72
|
+
class DataFrameReader(Protocol):
|
|
73
|
+
"""Struktureller Vertrag jeder Quelle."""
|
|
74
|
+
|
|
75
|
+
def read(self, selector: Any = None) -> "DataFrame":
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
def close(self) -> None:
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class BaseDataFrameReader(ABC):
|
|
83
|
+
"""Template-Method: vom Backend lesen, dann den Eingangsvertrag prüfen."""
|
|
84
|
+
|
|
85
|
+
def __init__(self,
|
|
86
|
+
require_metadata: Tuple[str, ...] = (),
|
|
87
|
+
require_columns: Tuple[str, ...] = (),
|
|
88
|
+
require_units: Tuple[str, ...] = ()):
|
|
89
|
+
"""Store the require_* contract (Pflichtschlüssel/-spalten/-einheiten)."""
|
|
90
|
+
self.require_metadata = tuple(require_metadata)
|
|
91
|
+
self.require_columns = tuple(require_columns)
|
|
92
|
+
self.require_units = tuple(require_units)
|
|
93
|
+
self._count = 0
|
|
94
|
+
|
|
95
|
+
def read(self, selector: Any = None) -> "DataFrame":
|
|
96
|
+
sdf = self._read_impl(selector)
|
|
97
|
+
check_contract(sdf, self.require_metadata, self.require_columns,
|
|
98
|
+
self.require_units, role="reader")
|
|
99
|
+
self._count += 1
|
|
100
|
+
logger.info("%s read %r", type(self).__name__, sdf.sname)
|
|
101
|
+
return sdf
|
|
102
|
+
|
|
103
|
+
@abstractmethod
|
|
104
|
+
def _read_impl(self, selector: Any) -> "DataFrame":
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
def close(self) -> None:
|
|
108
|
+
"""Release resources (default: no-op)."""
|
|
109
|
+
|
|
110
|
+
def __enter__(self) -> "BaseDataFrameReader":
|
|
111
|
+
"""Enter the context manager (returns self)."""
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def __exit__(self, *exc: Any) -> None:
|
|
115
|
+
"""Exit the context manager, closing the reader."""
|
|
116
|
+
self.close()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ParquetReader(BaseDataFrameReader):
|
|
120
|
+
"""Liest **eine** Parquet-URI (fsspec: lokal, ``s3://``, …) als :class:`DataFrame`.
|
|
121
|
+
|
|
122
|
+
Die Metadaten reisen im Format mit (``_sdata``-Schema-Key, RFC 0007 §6.1);
|
|
123
|
+
:meth:`DataFrame.from_parquet_bytes` restauriert ``metadata``/``column_metadata``/
|
|
124
|
+
``description``.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def __init__(self, uri: str, *, directory: bool = False, **contract: Any):
|
|
128
|
+
"""Bind the source fsspec ``uri`` and the require_* contract.
|
|
129
|
+
|
|
130
|
+
:param uri: Quell-URI (Einzeldatei) bzw. Quell-Verzeichnis (``directory=True``).
|
|
131
|
+
:param directory: Verzeichnis-Modus — ``read(sname)`` liest ``<uri>/<sname>.spq``;
|
|
132
|
+
:meth:`keys` listet die Mitglieder (Gegenseite zu ``ParquetWriter(directory=True)``).
|
|
133
|
+
"""
|
|
134
|
+
super().__init__(**contract)
|
|
135
|
+
self.uri = uri
|
|
136
|
+
self.directory = directory
|
|
137
|
+
|
|
138
|
+
def _read_impl(self, selector: Any) -> "DataFrame":
|
|
139
|
+
from sdata.sclass.blob import fsspec
|
|
140
|
+
from sdata.sclass.dataframe import DataFrame
|
|
141
|
+
|
|
142
|
+
if fsspec is None: # pragma: no cover - optionale Abhängigkeit
|
|
143
|
+
raise ImportError("ParquetReader requires: pip install sdata[blob]")
|
|
144
|
+
if self.directory:
|
|
145
|
+
if selector is None:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
"directory ParquetReader needs a key (sname); use keys()/read_group")
|
|
148
|
+
src = "{}/{}.spq".format(self.uri.rstrip("/"), selector)
|
|
149
|
+
else:
|
|
150
|
+
if selector is not None:
|
|
151
|
+
raise ValueError(
|
|
152
|
+
"ParquetReader reads exactly one URI; selector is not supported")
|
|
153
|
+
src = self.uri
|
|
154
|
+
with fsspec.open(src, "rb") as fh:
|
|
155
|
+
return DataFrame.from_parquet_bytes(fh.read())
|
|
156
|
+
|
|
157
|
+
def keys(self) -> list:
|
|
158
|
+
"""Mitglied-``sname``s eines Verzeichnisses (globt ``<uri>/*.spq``).
|
|
159
|
+
|
|
160
|
+
:return: sortierte Liste der ``sname``s; mit ``read_group(reader, reader.keys())``
|
|
161
|
+
liest man das ganze Verzeichnis in eine :class:`DataFrameGroup`.
|
|
162
|
+
:raises ValueError: außerhalb des Verzeichnis-Modus.
|
|
163
|
+
"""
|
|
164
|
+
if not self.directory:
|
|
165
|
+
raise ValueError("keys() requires directory mode")
|
|
166
|
+
from sdata.sclass.blob import fsspec
|
|
167
|
+
|
|
168
|
+
if fsspec is None: # pragma: no cover - optionale Abhängigkeit
|
|
169
|
+
raise ImportError("ParquetReader requires: pip install sdata[blob]")
|
|
170
|
+
base = self.uri.rstrip("/")
|
|
171
|
+
fs, _, _ = fsspec.get_fs_token_paths(base)
|
|
172
|
+
paths = fs.glob("{}/*.spq".format(base))
|
|
173
|
+
return sorted(p.rsplit("/", 1)[-1][:-4] for p in paths)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class StoreReader(BaseDataFrameReader):
|
|
177
|
+
"""Selektor-Zugriff und Iteration über einen :class:`JSON1SQLiteStore`.
|
|
178
|
+
|
|
179
|
+
Invertiert den Flatten-Adapter des :class:`~sdata.iolib.writer.StoreWriter`
|
|
180
|
+
(RFC 0007 §6.2): die ``_sdata_*``-Spalten machen den Record auffindbar, das
|
|
181
|
+
verlustfreie ``to_dict`` unter ``"sdata"`` stellt das Objekt wieder her.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def __init__(self, target: Any, **contract: Any):
|
|
185
|
+
"""Bind a store: an existing ``JSON1SQLiteStore`` (not owned) or a DB path (owned)."""
|
|
186
|
+
super().__init__(**contract)
|
|
187
|
+
from sdata.iolib.json1sqlitestore import JSON1SQLiteStore
|
|
188
|
+
|
|
189
|
+
if hasattr(target, "get_id_by_key"):
|
|
190
|
+
self._store = target
|
|
191
|
+
self._owns = False
|
|
192
|
+
else:
|
|
193
|
+
self._store = JSON1SQLiteStore(target)
|
|
194
|
+
self._owns = True
|
|
195
|
+
|
|
196
|
+
def _read_impl(self, selector: Any) -> "DataFrame":
|
|
197
|
+
from sdata.sclass.dataframe import DataFrame
|
|
198
|
+
|
|
199
|
+
row = self._resolve(selector)
|
|
200
|
+
if row is None or "sdata" not in row:
|
|
201
|
+
raise KeyError(f"no DataFrame record for selector {selector!r}")
|
|
202
|
+
return DataFrame.from_dict(row["sdata"])
|
|
203
|
+
|
|
204
|
+
def _resolve(self, selector: Any):
|
|
205
|
+
"""Dokumentiert deterministisch: ``int`` → Record-ID, sonst suuid, dann sname."""
|
|
206
|
+
if isinstance(selector, int):
|
|
207
|
+
return self._store.get(selector)
|
|
208
|
+
sel = str(selector) # SUUID-Objekte -> str (RFC 0007 F4)
|
|
209
|
+
rid = (self._store.get_id_by_key("_sdata_suuid", sel)
|
|
210
|
+
or self._store.get_id_by_key("_sdata_sname", sel))
|
|
211
|
+
return self._store.get(rid) if rid is not None else None
|
|
212
|
+
|
|
213
|
+
def iter(self) -> Iterator["DataFrame"]:
|
|
214
|
+
"""Alle DataFrame-Records des Stores; Records ohne ``"sdata"``-Payload
|
|
215
|
+
(Fremdobjekte) werden übersprungen."""
|
|
216
|
+
from sdata.sclass.dataframe import DataFrame
|
|
217
|
+
|
|
218
|
+
for row in self._store.fetch_all():
|
|
219
|
+
if isinstance(row, dict) and "sdata" in row:
|
|
220
|
+
yield DataFrame.from_dict(row["sdata"])
|
|
221
|
+
|
|
222
|
+
def close(self) -> None:
|
|
223
|
+
"""If this reader owns the store, close its connection."""
|
|
224
|
+
if self._owns:
|
|
225
|
+
self._store.conn.close()
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class SqlReader(BaseDataFrameReader):
|
|
229
|
+
"""Rekonstruiert ein :class:`DataFrame` aus der ``sdata_dataframe_meta``-Tabelle.
|
|
230
|
+
|
|
231
|
+
* ``fresh=False`` (Default): Daten **und** Metadaten aus dem ``sdata``-JSON der
|
|
232
|
+
Metazeile — der verlustfreie **Snapshot zum Schreibzeitpunkt** (inkl. Einheiten/
|
|
233
|
+
Ontologie, die ``to_sql`` selbst verliert).
|
|
234
|
+
* ``fresh=True``: Metadaten aus dem Snapshot, Datenzeilen per ``read_sql`` aus
|
|
235
|
+
``target_table`` — der **aktuelle** Tabellenstand (bei ``if_exists="append"``
|
|
236
|
+
kumulativ über mehrere Schreibvorgänge; Provenienz pro Zeile: ``suuid``-Spalte,
|
|
237
|
+
RFC 0007 §6.2). Der Tabellenname läuft durch dieselbe Allowlist wie im Writer.
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
def __init__(self, conn: Any, *, fresh: bool = False, **contract: Any):
|
|
241
|
+
"""Bind a DBAPI/PEP-249 connection (oder ``engine.raw_connection()``)."""
|
|
242
|
+
super().__init__(**contract)
|
|
243
|
+
self._conn = conn
|
|
244
|
+
self.fresh = fresh
|
|
245
|
+
self.meta_table = _SQL_META_TABLE
|
|
246
|
+
|
|
247
|
+
def _read_impl(self, selector: Any) -> "DataFrame":
|
|
248
|
+
from sdata.sclass.dataframe import DataFrame
|
|
249
|
+
|
|
250
|
+
sel = str(selector)
|
|
251
|
+
row = self._conn.execute(_SQL_META_SELECT, (sel, sel)).fetchone()
|
|
252
|
+
if row is None:
|
|
253
|
+
raise KeyError(f"no entry in {self.meta_table} for selector {selector!r}")
|
|
254
|
+
target_table, sdata_json = row[0], row[1]
|
|
255
|
+
sdf = DataFrame.from_dict(json.loads(sdata_json))
|
|
256
|
+
if self.fresh:
|
|
257
|
+
table = _safe_identifier(target_table)
|
|
258
|
+
# Identifier ist Allowlist-validiert (RFC 0009 §5.3); Parameter-Binding
|
|
259
|
+
# für Identifier existiert in SQL nicht.
|
|
260
|
+
query = "SELECT * FROM " + table # nosec B608
|
|
261
|
+
sdf.df = pd.read_sql_query(query, self._conn)
|
|
262
|
+
return sdf
|