samplekit 0.1.0__tar.gz → 0.1.2__tar.gz

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.
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: samplekit
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Lightweight Python framework for documenting scientific samples with bidirectional Markdown I/O
5
5
  Author-email: zelyph <github@zelyph.fr>
6
6
  License: MIT
7
- Project-URL: Homepage, https://github.com/zelyph/SampleKit
8
- Project-URL: Repository, https://github.com/zelyph/SampleKit
7
+ Project-URL: Homepage, https://github.com/zelyph/SampleKit-dev
8
+ Project-URL: Repository, https://github.com/zelyph/SampleKit-dev
9
9
  Keywords: science,measurement,markdown,yaml,sample,property
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Intended Audience :: Science/Research
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "samplekit"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "Lightweight Python framework for documenting scientific samples with bidirectional Markdown I/O"
5
5
  authors = [{ name = "zelyph", email = "github@zelyph.fr" }]
6
6
  readme = "README.md"
@@ -28,5 +28,5 @@ build-backend = "setuptools.build_meta"
28
28
  include = ["samplekit*"]
29
29
 
30
30
  [project.urls]
31
- Homepage = "https://github.com/zelyph/SampleKit"
32
- Repository = "https://github.com/zelyph/SampleKit"
31
+ Homepage = "https://github.com/zelyph/SampleKit-dev"
32
+ Repository = "https://github.com/zelyph/SampleKit-dev"
@@ -7,7 +7,7 @@ from .sample_list import SampleList
7
7
  from . import report
8
8
  from . import converters
9
9
 
10
- __version__ = "0.1.0"
10
+ __version__ = "0.1.2"
11
11
 
12
12
  __all__ = [
13
13
  "Property",
@@ -128,7 +128,7 @@ def properties_table(
128
128
  sym = prop.symbol or name
129
129
  sym_cell = sym
130
130
  val_cell = format_property(prop, style, unit=False)
131
- unit_cell = f"${prop.unit_math.translate(_LATEX_SPECIAL)}$" if math and prop.unit else prop.unit
131
+ unit_cell = f"${prop.unit_math}$" if math and prop.unit else prop.unit
132
132
  rows.append([sym_cell, val_cell, unit_cell])
133
133
 
134
134
  return markdown_table(rows, headers, align or ["c"] * len(headers))
@@ -97,6 +97,8 @@ class Sample:
97
97
  n = name if name is not None else (fp.stem if fp else "Unnamed")
98
98
  object.__setattr__(self, 'name', n)
99
99
  object.__setattr__(self, '_hydrating', False)
100
+ if type(self) is Sample:
101
+ self._auto_hydrate()
100
102
 
101
103
  def _auto_hydrate(self):
102
104
  """Load data from filepath if the file exists."""
@@ -354,10 +354,10 @@ class Table:
354
354
  if not prop.unit and col.unit:
355
355
  prop.unit = col.unit
356
356
  prop.unit_math = col.unit_math
357
+ if prop.precision_unc == prop.precision and col.precision_unc:
358
+ prop.precision_unc = col.precision_unc
357
359
  if not prop.precision and col.precision:
358
360
  prop.precision = col.precision
359
- if prop.precision_unc == prop.precision and col.precision_unc != col.precision:
360
- prop.precision_unc = col.precision_unc
361
361
  if prop.symbol is None and col.symbol:
362
362
  prop.symbol = col.symbol
363
363
  if prop.symbol_math is None and col.symbol_math:
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: samplekit
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Lightweight Python framework for documenting scientific samples with bidirectional Markdown I/O
5
5
  Author-email: zelyph <github@zelyph.fr>
6
6
  License: MIT
7
- Project-URL: Homepage, https://github.com/zelyph/SampleKit
8
- Project-URL: Repository, https://github.com/zelyph/SampleKit
7
+ Project-URL: Homepage, https://github.com/zelyph/SampleKit-dev
8
+ Project-URL: Repository, https://github.com/zelyph/SampleKit-dev
9
9
  Keywords: science,measurement,markdown,yaml,sample,property
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Intended Audience :: Science/Research
@@ -12,5 +12,4 @@ samplekit.egg-info/PKG-INFO
12
12
  samplekit.egg-info/SOURCES.txt
13
13
  samplekit.egg-info/dependency_links.txt
14
14
  samplekit.egg-info/requires.txt
15
- samplekit.egg-info/top_level.txt
16
- tests/test_smoke.py
15
+ samplekit.egg-info/top_level.txt
@@ -1,324 +0,0 @@
1
- """Smoke tests for samplekit v2."""
2
-
3
- import tempfile
4
- import subprocess
5
- import sys
6
- from pathlib import Path
7
-
8
- from samplekit import Property, Table, Column, Sample, SampleList, report
9
- import samplekit
10
- from samplekit.report import format_property
11
-
12
-
13
- def test_imports():
14
- print("=== 1. Import test ===")
15
- print(f" version: {samplekit.__version__}")
16
- assert samplekit.__version__ == "2.0.0a1"
17
- print(" all imports OK")
18
-
19
-
20
- def test_property():
21
- print("\n=== 2. Property test ===")
22
- # Static
23
- p = Property(value=25.0, uncertainty=0.5, unit="°C", symbol_math="T")
24
- print(f" static: {p.text}")
25
- assert p.value == 25.0
26
- assert p.uncertainty == 0.5
27
-
28
- # Measured (auto mean ± std)
29
- p2 = Property(value=[101.1, 101.3, 101.5], unit="kPa", symbol_math="P")
30
- print(f" measured: {p2.text}")
31
- assert abs(p2.value - 101.3) < 0.01
32
- assert p2.uncertainty is not None
33
-
34
- # String
35
- p3 = Property(value="Alumina")
36
- print(f" string: {p3.text}")
37
- assert p3.value == "Alumina"
38
-
39
-
40
- def test_computed_invalidation():
41
- print("\n=== 3. Computed property with dependency invalidation ===")
42
-
43
- class TestSample(Sample):
44
- def __init__(self, name=None, filepath=None):
45
- super().__init__(name, filepath)
46
- self.a = Property(value=10.0)
47
- self.b = Property(value=3.0)
48
- self.ratio = Property(
49
- compute=self._calc_ratio,
50
- depends_on=[self.a, self.b],
51
- precision=".4f",
52
- )
53
-
54
- def _calc_ratio(self):
55
- return self.a.value / self.b.value
56
-
57
- s = TestSample("test")
58
- print(f" ratio = {s.ratio.value:.4f}")
59
- assert abs(s.ratio.value - 3.3333) < 0.001
60
-
61
- s.a.value = 20.0
62
- print(f" after a=20: ratio = {s.ratio.value:.4f}")
63
- assert abs(s.ratio.value - 6.6667) < 0.001
64
- print(" invalidation OK")
65
-
66
-
67
- def test_table():
68
- print("\n=== 4. Table test ===")
69
- t = Table(
70
- columns={
71
- "T": Column(unit="°C"),
72
- "f": Column(unit="GHz", precision=".6f"),
73
- "Q": Column(precision=".0f"),
74
- },
75
- )
76
- t.add(T=20, f=(8.878084, 9e-7), Q=24840)
77
- t.add(T=30, f=8.873760, Q=24104)
78
- print(f" rows: {len(t)}, indices: {t.index_values}")
79
- assert len(t) == 2
80
- assert t(20).f.value == 8.878084
81
- assert t(20).f.uncertainty == 9e-7
82
- assert t(20).Q.value == 24840
83
-
84
- # Column access
85
- assert t.f.values == [8.878084, 8.873760]
86
- assert t.f[20].value == 8.878084
87
-
88
- # Cell formatting (via report.format_property)
89
- assert "$" in format_property(t(20).f, style="math", unit=False)
90
-
91
- print(" Table OK")
92
-
93
-
94
- class MySample(Sample):
95
- def __init__(self, name=None, filepath=None):
96
- super().__init__(name, filepath)
97
- self.material = Property(unit="-")
98
- self.height = Property(unit="mm", symbol_math="h", precision=".3f")
99
- self.diameter = Property(unit="mm", symbol_math="d", precision=".3f")
100
- self.rho = Property(
101
- unit="g/cm³", precision=".4f",
102
- compute=self._calc_rho,
103
- depends_on=[self.height, self.diameter],
104
- )
105
- self.measurements = Table(
106
- columns={
107
- "T": Column(unit="°C"),
108
- "f": Column(unit="GHz", precision=".6f", precision_unc=".2e"),
109
- "Q": Column(precision=".0f"),
110
- },
111
- )
112
-
113
- def _calc_rho(self):
114
- return 3.95
115
-
116
- def template(self, style="math"):
117
- s = []
118
- s.append(report.heading("Properties"))
119
- s.append(report.properties_table(
120
- self, ["material", "height", "diameter", "rho"], style=style,
121
- ))
122
- if self.measurements:
123
- s.append(report.heading("Measurements"))
124
- s.append(report.table_to_markdown(self.measurements, style=style))
125
- return "\n\n".join(s)
126
-
127
-
128
- def test_roundtrip():
129
- print("\n=== 5. Write / Read round-trip ===")
130
- s = MySample("roundtrip_test")
131
- s.material.value = "Alumina"
132
- s.height.value = [6.012, 6.009, 6.015]
133
- s.height.uncertainty = 0.017
134
- s.diameter.value = 25.127
135
- s.measurements.add(T=20, f=(8.878084, 9e-7), Q=24840)
136
- s.measurements.add(T=30, f=8.873760, Q=24104)
137
-
138
- with tempfile.TemporaryDirectory() as tmpdir:
139
- fp = Path(tmpdir) / "test.md"
140
- s.save(fp)
141
- content = fp.read_text()
142
- print(f" Written OK, file size: {len(content)} bytes")
143
-
144
- assert "name: roundtrip_test" in content
145
- assert "Alumina" in content
146
- assert "8.878084" in content
147
- assert "!!float" not in content
148
- print(" YAML format OK (no !!float)")
149
-
150
- loaded = MySample.load(fp)
151
- print(f" Loaded name: {loaded.name}")
152
- print(f" Loaded height: {loaded.height.value} +/- {loaded.height.uncertainty}")
153
- print(f" Loaded material: {loaded.material.value}")
154
- print(f" Loaded rho (computed, seeded): {loaded.rho.value}")
155
- print(f" Loaded measurements: {len(loaded.measurements)} rows")
156
- print(f" Loaded f@20: {loaded.measurements(20).f.value}")
157
-
158
- assert loaded.name == "roundtrip_test"
159
- assert loaded.material.value == "Alumina"
160
- assert abs(loaded.height.value - 6.012) < 0.001
161
- assert loaded.height.uncertainty == 0.017
162
- assert loaded.measurements(20).f.value == 8.878084
163
- assert loaded.measurements(20).f.uncertainty == 9e-7
164
- assert loaded.measurements(30).Q.value == 24104
165
- print(" Round-trip assertions PASSED")
166
-
167
-
168
- def test_report():
169
- print("\n=== 6. Report rendering ===")
170
- s = MySample("report_test")
171
- s.material.value = "Alumina"
172
- s.height.value = 6.012
173
- s.height.uncertainty = 0.017
174
- s.diameter.value = 25.127
175
- s.measurements.add(T=20, f=8.878084, Q=24840)
176
-
177
- md = s.template(style="math")
178
- assert "| $h$" in md
179
- assert "\\pm" in md
180
- assert "Measurements" in md
181
- print(" Markdown template OK")
182
-
183
- txt = s.template(style="text")
184
- assert "±" in txt
185
- print(" Text template OK")
186
-
187
-
188
- def test_samplelist():
189
- print("\n=== 7. SampleList ===")
190
- with tempfile.TemporaryDirectory() as tmpdir:
191
- for i in range(3):
192
- si = MySample(f"sample_{i}")
193
- si.material.value = f"Material_{i}"
194
- si.height.value = 5.0 + i * 0.5
195
- si.diameter.value = 25.0
196
- si.save(Path(tmpdir) / f"sample_{i}.md")
197
-
198
- sl = SampleList(tmpdir, sample_class=MySample)
199
- print(f" Loaded {len(sl)} samples from directory")
200
- assert len(sl) == 3
201
-
202
- filtered = sl.filter(lambda s: s.height.value > 5.2)
203
- print(f" Filtered to {len(filtered)} samples (height > 5.2)")
204
- assert len(filtered) == 2
205
-
206
- sorted_sl = sl.sort("height", reverse=True)
207
- print(f" Sorted: {[s.name for s in sorted_sl]}")
208
- assert sorted_sl[0].name == "sample_2"
209
-
210
- s0 = sl["sample_0"]
211
- assert s0.material.value == "Material_0"
212
- print(" Access by name OK")
213
- print(" SampleList assertions PASSED")
214
-
215
-
216
- def test_table_column_compute():
217
- """Table with column-wise compute."""
218
- print("\n=== 8. Table column-wise compute ===")
219
-
220
- def make_f(Ts):
221
- return [8.878 - 0.001 * (T - 20) for T in Ts]
222
-
223
- def make_q(Ts):
224
- return [25000 - 10 * (T - 20) for T in Ts]
225
-
226
- t = Table(
227
- columns={
228
- "T": Column(unit="°C"),
229
- "f": Column(unit="GHz", precision=".6f"),
230
- "Q": Column(precision=".0f"),
231
- },
232
- compute={"f": make_f, "Q": make_q},
233
- index=[20, 30, 40],
234
- )
235
- assert len(t) == 3
236
- assert t.index_values == [20, 30, 40]
237
- assert t(20).f.value == 8.878
238
- assert t(30).f.value == 8.868
239
- assert t(40).Q.value == 24800
240
- # Column access
241
- assert t.f.values == [8.878, 8.868, 8.858]
242
- print(f" column-wise compute OK: {t.f.values}")
243
-
244
-
245
- def test_table_compute_with_unc():
246
- """Table with column-wise compute + uncertainty."""
247
- print("\n=== 9. Table compute with uncertainty ===")
248
-
249
- TC = Column(precision=".2f")
250
-
251
- def make_f(Ts):
252
- return [8.878 - 0.001 * (T - 20) for T in Ts]
253
-
254
- def make_f_unc(Ts):
255
- return [1e-6 * T for T in Ts]
256
-
257
- def make_tau(Ts):
258
- return [-5.0 + 0.1 * T for T in Ts]
259
-
260
- t = Table(
261
- columns={
262
- "T": Column(unit="°C"),
263
- "f": Column(unit="GHz", precision=".6f"),
264
- "tau_lin": TC, "tau_poly": TC,
265
- },
266
- compute={"f": make_f, "tau_lin": make_tau, "tau_poly": make_tau},
267
- compute_unc={"f": make_f_unc},
268
- index=[20, 30],
269
- )
270
- assert len(t) == 2
271
- assert t(20).f.value == 8.878
272
- assert abs(t(20).f.uncertainty - 20e-6) < 1e-12
273
- assert t(30).f.value == 8.868
274
- assert abs(t(30).f.uncertainty - 30e-6) < 1e-12
275
- # Shared Column: both tau columns have same metadata
276
- assert t(20).tau_lin.value == -3.0
277
- assert t(20).tau_poly.value == -3.0
278
- print(f" compute+unc OK: f={t.f.values}, tau_lin={t.tau_lin.values}")
279
-
280
-
281
- def test_table_data_dict():
282
- """Table with data=dict (static values)."""
283
- print("\n=== 10. Table data=dict (static) ===")
284
- t = Table(
285
- columns={
286
- "T": Column(unit="°C"),
287
- "f": Column(unit="GHz", precision=".6f"),
288
- "Q": Column(precision=".0f"),
289
- },
290
- data={
291
- 20: {"f": (8.878, 9e-7), "Q": 24840},
292
- 30: {"f": 8.874, "Q": 24104},
293
- },
294
- )
295
- assert len(t) == 2
296
- assert t(20).f.value == 8.878
297
- assert t(20).f.uncertainty == 9e-7
298
- assert t(30).Q.value == 24104
299
- print(f" static rows OK: f@20={t(20).f.value}, Q@30={t(30).Q.value}")
300
-
301
-
302
- def test_example():
303
- print("\n=== 10. Example ===")
304
- result = subprocess.run(
305
- [sys.executable, "examples/basic_sample.py"],
306
- capture_output=True, text=True,
307
- )
308
- assert result.returncode == 0, f"Example failed:\n{result.stderr}"
309
- print(" basic_sample.py runs OK")
310
-
311
-
312
- if __name__ == "__main__":
313
- test_imports()
314
- test_property()
315
- test_computed_invalidation()
316
- test_table()
317
- test_table_column_compute()
318
- test_table_compute_with_unc()
319
- test_table_data_dict()
320
- test_roundtrip()
321
- test_report()
322
- test_samplelist()
323
- test_example()
324
- print("\n\nALL TESTS PASSED ✓")
File without changes
File without changes
File without changes