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.
Files changed (98) hide show
  1. sdata/__init__.py +140 -0
  2. sdata/base.py +684 -0
  3. sdata/contrib/__init__.py +0 -0
  4. sdata/contrib/piexif/__init__.py +12 -0
  5. sdata/contrib/piexif/_common.py +94 -0
  6. sdata/contrib/piexif/_dump.py +346 -0
  7. sdata/contrib/piexif/_exceptions.py +2 -0
  8. sdata/contrib/piexif/_exif.py +638 -0
  9. sdata/contrib/piexif/_insert.py +60 -0
  10. sdata/contrib/piexif/_load.py +270 -0
  11. sdata/contrib/piexif/_remove.py +56 -0
  12. sdata/contrib/piexif/_transplant.py +45 -0
  13. sdata/contrib/piexif/_webp.py +240 -0
  14. sdata/contrib/piexif/helper.py +66 -0
  15. sdata/contrib/ranger/__init__.py +8 -0
  16. sdata/contrib/ranger/collections/__init__.py +0 -0
  17. sdata/contrib/ranger/collections/rangebucketmap.py +346 -0
  18. sdata/contrib/ranger/collections/rangemap.py +396 -0
  19. sdata/contrib/ranger/collections/rangeset.py +483 -0
  20. sdata/contrib/ranger/range/__init__.py +0 -0
  21. sdata/contrib/ranger/range/cut.py +285 -0
  22. sdata/contrib/ranger/range/range.py +644 -0
  23. sdata/contrib/sobol_seq.py +394 -0
  24. sdata/contrib/sortedcontainers/LICENSE +13 -0
  25. sdata/contrib/sortedcontainers/__init__.py +74 -0
  26. sdata/contrib/sortedcontainers/sorteddict.py +789 -0
  27. sdata/contrib/sortedcontainers/sortedlist.py +2646 -0
  28. sdata/contrib/sortedcontainers/sortedset.py +733 -0
  29. sdata/contrib/sqlitedict.py +598 -0
  30. sdata/contrib/timeflake/__init__.py +40 -0
  31. sdata/contrib/timeflake/flake.py +69 -0
  32. sdata/contrib/timeflake/utils.py +47 -0
  33. sdata/deprecated/__init__.py +10 -0
  34. sdata/deprecated/data.py +1503 -0
  35. sdata/did/__init__.py +129 -0
  36. sdata/did/_http.py +67 -0
  37. sdata/did/base58btc.py +49 -0
  38. sdata/did/did_github.py +152 -0
  39. sdata/did/did_peer4.py +155 -0
  40. sdata/did/did_web.py +222 -0
  41. sdata/did/diddoc.py +75 -0
  42. sdata/did/ed25519.py +225 -0
  43. sdata/did/eddsa.py +107 -0
  44. sdata/did/errors.py +40 -0
  45. sdata/did/issue_vc.py +193 -0
  46. sdata/did/jose.py +126 -0
  47. sdata/did/keys.py +104 -0
  48. sdata/did/utils_didkey.py +161 -0
  49. sdata/did/verify_vp.py +158 -0
  50. sdata/doe.py +101 -0
  51. sdata/dtypes.py +510 -0
  52. sdata/experiments/__init__.py +331 -0
  53. sdata/experiments/blob0.py +131 -0
  54. sdata/experiments/file.py +42 -0
  55. sdata/experiments/filegroup.py +144 -0
  56. sdata/experiments/image_old.py +313 -0
  57. sdata/experiments/ks2.py +15 -0
  58. sdata/experiments/lapshear.py +8 -0
  59. sdata/experiments/material.py +7 -0
  60. sdata/experiments/parameters_deprecated.py +394 -0
  61. sdata/experiments/workbook.py +226 -0
  62. sdata/format.py +125 -0
  63. sdata/imagemeta.py +533 -0
  64. sdata/interactive.py +118 -0
  65. sdata/iolib/__init__.py +51 -0
  66. sdata/iolib/hashtree.py +264 -0
  67. sdata/iolib/json1sqlitestore.py +554 -0
  68. sdata/iolib/jsonsqlitestore.py +704 -0
  69. sdata/iolib/pud.py +242 -0
  70. sdata/iolib/reader.py +262 -0
  71. sdata/iolib/writer.py +419 -0
  72. sdata/metadata.py +882 -0
  73. sdata/parameter.py +414 -0
  74. sdata/schema.py +273 -0
  75. sdata/sclass/__init__.py +111 -0
  76. sdata/sclass/bfo_classes.py +362 -0
  77. sdata/sclass/blob.py +362 -0
  78. sdata/sclass/content.py +87 -0
  79. sdata/sclass/dataframe.py +1251 -0
  80. sdata/sclass/dataframegroup.py +187 -0
  81. sdata/sclass/dependency_graph.py +85 -0
  82. sdata/sclass/filereference.py +124 -0
  83. sdata/sclass/image.py +218 -0
  84. sdata/sclass/lazy_namespace.py +155 -0
  85. sdata/sclass/process.py +383 -0
  86. sdata/sclass/process_graph.py +221 -0
  87. sdata/sclass/zipfile.py +135 -0
  88. sdata/semantic.py +346 -0
  89. sdata/suuid.py +136 -0
  90. sdata/timestamp.py +336 -0
  91. sdata/tools.py +14 -0
  92. sdata/units.py +602 -0
  93. sdata/vocab.py +149 -0
  94. sdata-1.5.0.dist-info/METADATA +264 -0
  95. sdata-1.5.0.dist-info/RECORD +98 -0
  96. sdata-1.5.0.dist-info/WHEEL +5 -0
  97. sdata-1.5.0.dist-info/licenses/LICENSE-MIT +21 -0
  98. 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