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.
- {samplekit-0.1.0/samplekit.egg-info → samplekit-0.1.2}/PKG-INFO +3 -3
- {samplekit-0.1.0 → samplekit-0.1.2}/pyproject.toml +3 -3
- {samplekit-0.1.0 → samplekit-0.1.2}/samplekit/__init__.py +1 -1
- {samplekit-0.1.0 → samplekit-0.1.2}/samplekit/report.py +1 -1
- {samplekit-0.1.0 → samplekit-0.1.2}/samplekit/sample.py +2 -0
- {samplekit-0.1.0 → samplekit-0.1.2}/samplekit/table.py +2 -2
- {samplekit-0.1.0 → samplekit-0.1.2/samplekit.egg-info}/PKG-INFO +3 -3
- {samplekit-0.1.0 → samplekit-0.1.2}/samplekit.egg-info/SOURCES.txt +1 -2
- samplekit-0.1.0/tests/test_smoke.py +0 -324
- {samplekit-0.1.0 → samplekit-0.1.2}/LICENSE +0 -0
- {samplekit-0.1.0 → samplekit-0.1.2}/README.md +0 -0
- {samplekit-0.1.0 → samplekit-0.1.2}/samplekit/converters.py +0 -0
- {samplekit-0.1.0 → samplekit-0.1.2}/samplekit/property.py +0 -0
- {samplekit-0.1.0 → samplekit-0.1.2}/samplekit/sample_list.py +0 -0
- {samplekit-0.1.0 → samplekit-0.1.2}/samplekit.egg-info/dependency_links.txt +0 -0
- {samplekit-0.1.0 → samplekit-0.1.2}/samplekit.egg-info/requires.txt +0 -0
- {samplekit-0.1.0 → samplekit-0.1.2}/samplekit.egg-info/top_level.txt +0 -0
- {samplekit-0.1.0 → samplekit-0.1.2}/setup.cfg +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: samplekit
|
|
3
|
-
Version: 0.1.
|
|
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.
|
|
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"
|
|
@@ -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
|
|
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.
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|