sdtoolkit 0.1.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.
sdtoolkit/__init__.py ADDED
@@ -0,0 +1,31 @@
1
+ """sdtoolkit: dependency-free standard deviation, variance and RSD."""
2
+
3
+ from .core import (
4
+ StatisticsError,
5
+ Summary,
6
+ coefficient_of_variation,
7
+ describe,
8
+ mean,
9
+ population_stdev,
10
+ population_variance,
11
+ relative_std_dev,
12
+ sample_stdev,
13
+ sample_variance,
14
+ sum_of_squares,
15
+ )
16
+
17
+ __version__ = "0.1.0"
18
+
19
+ __all__ = [
20
+ "StatisticsError",
21
+ "Summary",
22
+ "coefficient_of_variation",
23
+ "describe",
24
+ "mean",
25
+ "population_stdev",
26
+ "population_variance",
27
+ "relative_std_dev",
28
+ "sample_stdev",
29
+ "sample_variance",
30
+ "sum_of_squares",
31
+ ]
sdtoolkit/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())
sdtoolkit/cli.py ADDED
@@ -0,0 +1,93 @@
1
+ """Command-line interface: ``python -m sdtoolkit 2 4 4 4 5 5 7 9``."""
2
+
3
+ import argparse
4
+ import csv
5
+ import sys
6
+ from typing import List, Optional
7
+
8
+ from .core import StatisticsError, describe
9
+
10
+
11
+ def _parse_number(text: str, where: str) -> float:
12
+ try:
13
+ return float(text)
14
+ except ValueError:
15
+ raise StatisticsError(f"{where}: {text!r} is not a number") from None
16
+
17
+
18
+ def _read_column(path: str, column: str) -> List[float]:
19
+ with open(path, newline="", encoding="utf-8") as f:
20
+ # Lines starting with '#' are comments.
21
+ reader = csv.DictReader(line for line in f if not line.startswith("#"))
22
+ if reader.fieldnames is None or column not in reader.fieldnames:
23
+ raise StatisticsError(
24
+ f"column {column!r} not found; available: {reader.fieldnames}"
25
+ )
26
+ values = []
27
+ for i, row in enumerate(reader, start=1):
28
+ cell = (row[column] or "").strip()
29
+ if not cell:
30
+ raise StatisticsError(
31
+ f"{path}: data row {i}: empty cell in {column!r} "
32
+ "(missing values are not skipped)"
33
+ )
34
+ values.append(_parse_number(cell, f"{path}: data row {i}"))
35
+ return values
36
+
37
+
38
+ def _fmt(x: float) -> str:
39
+ return f"{x:.10g}"
40
+
41
+
42
+ def format_summary(s) -> str:
43
+ kind = "population" if s.population else "sample"
44
+ denom = "N" if s.population else "n - 1"
45
+ rows = [
46
+ ("n", str(s.n)),
47
+ ("mean", _fmt(s.mean)),
48
+ ("min", _fmt(s.minimum)),
49
+ ("max", _fmt(s.maximum)),
50
+ ("range", _fmt(s.range)),
51
+ ("sum of squares", _fmt(s.sum_of_squares)),
52
+ (f"{kind} variance", _fmt(s.variance)),
53
+ (f"{kind} std dev", _fmt(s.stdev)),
54
+ ("RSD (%)", "undefined (mean is 0)" if s.rsd_percent is None else _fmt(s.rsd_percent)),
55
+ ]
56
+ width = max(len(k) for k, _ in rows)
57
+ lines = [f"{k:<{width}} {v}" for k, v in rows]
58
+ lines.append(f"(divisor: {denom})")
59
+ return "\n".join(lines)
60
+
61
+
62
+ def main(argv: Optional[List[str]] = None) -> int:
63
+ parser = argparse.ArgumentParser(
64
+ prog="sdtoolkit",
65
+ description="Mean, variance, standard deviation and RSD of a dataset.",
66
+ )
67
+ parser.add_argument("values", nargs="*", help="numbers to summarise")
68
+ parser.add_argument(
69
+ "--population", action="store_true",
70
+ help="treat data as the whole population (divide by N, not n - 1)",
71
+ )
72
+ parser.add_argument("--csv", metavar="FILE", help="read values from a CSV file")
73
+ parser.add_argument("--column", metavar="NAME", help="CSV column to use (with --csv)")
74
+ args = parser.parse_args(argv)
75
+
76
+ if args.csv and args.values:
77
+ parser.error("give either values or --csv, not both")
78
+ if bool(args.csv) != bool(args.column):
79
+ parser.error("--csv and --column must be used together")
80
+ if not args.csv and not args.values:
81
+ parser.error("no values given")
82
+
83
+ try:
84
+ if args.csv:
85
+ data = _read_column(args.csv, args.column)
86
+ else:
87
+ data = [_parse_number(v, f"argument {i + 1}") for i, v in enumerate(args.values)]
88
+ summary = describe(data, population=args.population)
89
+ except (StatisticsError, OSError) as e:
90
+ print(f"error: {e}", file=sys.stderr)
91
+ return 1
92
+ print(format_summary(summary))
93
+ return 0
sdtoolkit/core.py ADDED
@@ -0,0 +1,199 @@
1
+ """Core dispersion statistics.
2
+
3
+ Every function takes an iterable of numbers. Inputs are validated and
4
+ converted to ``float`` first:
5
+
6
+ * ``int``, ``float`` and other real numbers are accepted.
7
+ * ``decimal.Decimal`` and ``fractions.Fraction`` are accepted and converted
8
+ to ``float``. Results are floats; exact decimal/rational precision is not
9
+ preserved.
10
+ * ``bool``, ``None``, strings, NaN and infinities are rejected with a
11
+ :class:`StatisticsError`. Missing values are never skipped silently: clean
12
+ your data before calling these functions.
13
+
14
+ Variance is computed with a two-pass algorithm: the mean first, then the
15
+ squared deviations from it, summed with :func:`math.fsum`. A small correction
16
+ term (the sum of deviations, which is zero in exact arithmetic) absorbs the
17
+ rounding error left in the mean. The shortcut ``sum(x**2) - n*mean**2`` is
18
+ never used because it loses most of its significant digits when the data
19
+ sits far from zero.
20
+ """
21
+
22
+ import math
23
+ import numbers
24
+ from dataclasses import dataclass, asdict
25
+ from decimal import Decimal
26
+ from typing import Iterable, List, Optional
27
+
28
+ __all__ = [
29
+ "StatisticsError",
30
+ "Summary",
31
+ "mean",
32
+ "sum_of_squares",
33
+ "population_variance",
34
+ "sample_variance",
35
+ "population_stdev",
36
+ "sample_stdev",
37
+ "relative_std_dev",
38
+ "coefficient_of_variation",
39
+ "describe",
40
+ ]
41
+
42
+
43
+ class StatisticsError(ValueError):
44
+ """Raised for empty, too-short or invalid input."""
45
+
46
+
47
+ def _to_floats(data: Iterable) -> List[float]:
48
+ if isinstance(data, (str, bytes)):
49
+ raise StatisticsError("expected an iterable of numbers, got a string")
50
+ values = []
51
+ for i, x in enumerate(data):
52
+ if isinstance(x, bool):
53
+ raise StatisticsError(f"item {i}: booleans are not numbers ({x!r})")
54
+ if x is None:
55
+ raise StatisticsError(
56
+ f"item {i}: None (missing value); remove or fill it before computing"
57
+ )
58
+ if isinstance(x, str):
59
+ raise StatisticsError(
60
+ f"item {i}: strings are not accepted ({x!r}); convert with float() first"
61
+ )
62
+ if not isinstance(x, (numbers.Real, Decimal)):
63
+ raise StatisticsError(f"item {i}: {x!r} is not a real number")
64
+ try:
65
+ f = float(x)
66
+ except OverflowError:
67
+ raise StatisticsError(f"item {i}: {x!r} is too large for a float") from None
68
+ if math.isnan(f):
69
+ raise StatisticsError(f"item {i}: NaN is not allowed")
70
+ if math.isinf(f):
71
+ raise StatisticsError(f"item {i}: infinity is not allowed")
72
+ values.append(f)
73
+ if not values:
74
+ raise StatisticsError("no data points: at least one value is required")
75
+ return values
76
+
77
+
78
+ def _mean_and_ss(values: List[float]):
79
+ n = len(values)
80
+ m = math.fsum(values) / n
81
+ deviations = [x - m for x in values]
82
+ # Corrected two-pass: sum(d) is 0 in exact arithmetic; subtracting
83
+ # sum(d)**2 / n removes the error caused by rounding in m.
84
+ ss = math.fsum(d * d for d in deviations) - math.fsum(deviations) ** 2 / n
85
+ return m, max(ss, 0.0)
86
+
87
+
88
+ def _need_two(n: int) -> None:
89
+ if n < 2:
90
+ raise StatisticsError(
91
+ f"sample statistics need at least 2 data points, got {n}"
92
+ )
93
+
94
+
95
+ def mean(data: Iterable) -> float:
96
+ """Arithmetic mean."""
97
+ values = _to_floats(data)
98
+ return math.fsum(values) / len(values)
99
+
100
+
101
+ def sum_of_squares(data: Iterable) -> float:
102
+ """Sum of squared deviations from the mean, SS = sum((x - mean)**2)."""
103
+ return _mean_and_ss(_to_floats(data))[1]
104
+
105
+
106
+ def population_variance(data: Iterable) -> float:
107
+ """Population variance, SS / N."""
108
+ values = _to_floats(data)
109
+ return _mean_and_ss(values)[1] / len(values)
110
+
111
+
112
+ def sample_variance(data: Iterable) -> float:
113
+ """Sample variance with Bessel's correction, SS / (n - 1). Needs n >= 2."""
114
+ values = _to_floats(data)
115
+ _need_two(len(values))
116
+ return _mean_and_ss(values)[1] / (len(values) - 1)
117
+
118
+
119
+ def population_stdev(data: Iterable) -> float:
120
+ """Population standard deviation, sqrt(SS / N)."""
121
+ return math.sqrt(population_variance(data))
122
+
123
+
124
+ def sample_stdev(data: Iterable) -> float:
125
+ """Sample standard deviation, sqrt(SS / (n - 1)). Needs n >= 2."""
126
+ return math.sqrt(sample_variance(data))
127
+
128
+
129
+ def coefficient_of_variation(data: Iterable, population: bool = False) -> float:
130
+ """Standard deviation divided by |mean|, as a ratio (0.05 means 5 %).
131
+
132
+ Uses the sample standard deviation unless ``population=True``.
133
+ Raises StatisticsError when the mean is zero.
134
+ """
135
+ values = _to_floats(data)
136
+ n = len(values)
137
+ if not population:
138
+ _need_two(n)
139
+ m, ss = _mean_and_ss(values)
140
+ if m == 0:
141
+ raise StatisticsError("mean is zero; relative dispersion is undefined")
142
+ sd = math.sqrt(ss / (n if population else n - 1))
143
+ return sd / abs(m)
144
+
145
+
146
+ def relative_std_dev(data: Iterable, population: bool = False) -> float:
147
+ """Relative standard deviation in percent: 100 * SD / |mean|.
148
+
149
+ Uses the sample standard deviation unless ``population=True``.
150
+ Raises StatisticsError when the mean is zero.
151
+ """
152
+ return 100.0 * coefficient_of_variation(data, population=population)
153
+
154
+
155
+ @dataclass(frozen=True)
156
+ class Summary:
157
+ """Result of :func:`describe`. ``rsd_percent`` is None when the mean is 0."""
158
+
159
+ n: int
160
+ mean: float
161
+ minimum: float
162
+ maximum: float
163
+ range: float
164
+ sum_of_squares: float
165
+ variance: float
166
+ stdev: float
167
+ rsd_percent: Optional[float]
168
+ population: bool
169
+
170
+ def as_dict(self) -> dict:
171
+ return asdict(self)
172
+
173
+
174
+ def describe(data: Iterable, population: bool = False) -> Summary:
175
+ """Summary of location and spread.
176
+
177
+ ``population=False`` (default) uses n - 1 in the denominator and needs
178
+ at least two values; ``population=True`` uses N.
179
+ """
180
+ values = _to_floats(data)
181
+ n = len(values)
182
+ if not population:
183
+ _need_two(n)
184
+ m, ss = _mean_and_ss(values)
185
+ var = ss / (n if population else n - 1)
186
+ sd = math.sqrt(var)
187
+ lo, hi = min(values), max(values)
188
+ return Summary(
189
+ n=n,
190
+ mean=m,
191
+ minimum=lo,
192
+ maximum=hi,
193
+ range=hi - lo,
194
+ sum_of_squares=ss,
195
+ variance=var,
196
+ stdev=sd,
197
+ rsd_percent=None if m == 0 else 100.0 * (sd / abs(m)),
198
+ population=population,
199
+ )
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.4
2
+ Name: sdtoolkit
3
+ Version: 0.1.0
4
+ Summary: Dependency-free standard deviation, variance and relative standard deviation for Python
5
+ Author: Ramesh-xyz
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://standarddeviationcalculator.net/
8
+ Project-URL: Documentation, https://ramesh-xyz.github.io/statistics-calculator-toolkit/python.html
9
+ Project-URL: Source, https://github.com/Ramesh-xyz/statistics-calculator-toolkit
10
+ Project-URL: Issues, https://github.com/Ramesh-xyz/statistics-calculator-toolkit/issues
11
+ Project-URL: Changelog, https://github.com/Ramesh-xyz/statistics-calculator-toolkit/blob/main/CHANGELOG.md
12
+ Keywords: standard deviation,variance,coefficient of variation,statistics
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # sdtoolkit for Python
22
+
23
+ Part of [statistics-calculator-toolkit](https://github.com/Ramesh-xyz/statistics-calculator-toolkit), which ships the same API for
24
+ [several languages](https://ramesh-xyz.github.io/statistics-calculator-toolkit/).
25
+
26
+ `sdtoolkit` is a small, dependency-free Python package for standard
27
+ deviation, variance, sum of squares, relative standard deviation (RSD) and
28
+ coefficient of variation, with a command-line interface. It uses only the
29
+ standard library and supports Python 3.9 and later.
30
+
31
+ ## Problems it solves
32
+
33
+ - **Picking the wrong formula.** Population and sample versions have
34
+ separate, explicitly named functions, and the CLI prints which divisor it
35
+ used.
36
+ - **Silent precision loss.** The shortcut `Σx² − (Σx)²/n` can return zero or
37
+ even a negative variance for data far from zero. sdtoolkit uses a two-pass
38
+ algorithm with `math.fsum`.
39
+ - **Garbage in, number out.** `None`, `NaN`, infinities, booleans and strings
40
+ raise a clear error instead of producing a plausible-looking result. Missing
41
+ values are never skipped silently.
42
+ - **Undefined RSD.** A zero mean raises an error instead of returning
43
+ infinity or dividing by zero.
44
+
45
+ ## Features
46
+
47
+ | function | returns |
48
+ |---|---|
49
+ | `mean(data)` | arithmetic mean |
50
+ | `sum_of_squares(data)` | SS = Σ(x − x̄)² |
51
+ | `population_variance(data)` | SS / N |
52
+ | `sample_variance(data)` | SS / (n − 1) |
53
+ | `population_stdev(data)` | √(SS / N) |
54
+ | `sample_stdev(data)` | √(SS / (n − 1)) |
55
+ | `relative_std_dev(data, population=False)` | 100 × SD / \|mean\|, in percent |
56
+ | `coefficient_of_variation(data, population=False)` | SD / \|mean\|, as a ratio |
57
+ | `describe(data, population=False)` | `Summary` dataclass with all of the above plus min, max, range |
58
+
59
+ All errors are raised as `sdtoolkit.StatisticsError`, a subclass of
60
+ `ValueError`.
61
+
62
+ ## Install
63
+
64
+ ```
65
+ pip install sdtoolkit
66
+ ```
67
+
68
+ Or from a clone, without installing:
69
+
70
+ ```
71
+ PYTHONPATH=src python -m sdtoolkit 1 2 3
72
+ ```
73
+
74
+ ## Usage
75
+
76
+ ### Python
77
+
78
+ ```python
79
+ >>> import sdtoolkit as sd
80
+ >>> data = [2, 4, 4, 4, 5, 5, 7, 9]
81
+ >>> sd.mean(data)
82
+ 5.0
83
+ >>> sd.sum_of_squares(data)
84
+ 32.0
85
+ >>> sd.population_variance(data), sd.population_stdev(data)
86
+ (4.0, 2.0)
87
+ >>> sd.sample_variance(data), sd.sample_stdev(data)
88
+ (4.571428571428571, 2.138089935299395)
89
+ >>> sd.relative_std_dev(data)
90
+ 42.7617987059879
91
+ >>> sd.coefficient_of_variation(data, population=True)
92
+ 0.4
93
+ >>> sd.describe(data)
94
+ Summary(n=8, mean=5.0, minimum=2.0, maximum=9.0, range=7.0, sum_of_squares=32.0, variance=4.571428571428571, stdev=2.138089935299395, rsd_percent=42.7617987059879, population=False)
95
+ >>> sd.describe(data, population=True).as_dict()["stdev"]
96
+ 2.0
97
+ ```
98
+
99
+ ### Command line
100
+
101
+ ```console
102
+ $ python -m sdtoolkit 2 4 4 4 5 5 7 9
103
+ n 8
104
+ mean 5
105
+ min 2
106
+ max 9
107
+ range 7
108
+ sum of squares 32
109
+ sample variance 4.571428571
110
+ sample std dev 2.138089935
111
+ RSD (%) 42.76179871
112
+ (divisor: n - 1)
113
+ ```
114
+
115
+ ```console
116
+ $ python -m sdtoolkit --population 2 4 4 4 5 5 7 9
117
+ n 8
118
+ mean 5
119
+ min 2
120
+ max 9
121
+ range 7
122
+ sum of squares 32
123
+ population variance 4
124
+ population std dev 2
125
+ RSD (%) 40
126
+ (divisor: N)
127
+ ```
128
+
129
+ Read one column of a CSV file (lines starting with `#` are treated as
130
+ comments):
131
+
132
+ ```console
133
+ $ python -m sdtoolkit --csv examples/sample-datasets.csv --column lab_replicate_mg_l
134
+ n 10
135
+ mean 12.405
136
+ min 12.36
137
+ max 12.45
138
+ range 0.09
139
+ sum of squares 0.00825
140
+ sample variance 0.0009166666667
141
+ sample std dev 0.03027650354
142
+ RSD (%) 0.244066937
143
+ (divisor: n - 1)
144
+ ```
145
+
146
+ Errors go to stderr with exit status 1:
147
+
148
+ ```console
149
+ $ python -m sdtoolkit 1 2 x
150
+ error: argument 3: 'x' is not a number
151
+ $ python -m sdtoolkit 5
152
+ error: sample statistics need at least 2 data points, got 1
153
+ ```
154
+
155
+ After `pip install .` the same CLI is also available as `sdtoolkit`.
156
+
157
+ ## Population vs sample
158
+
159
+ Use the population functions when your data are the entire group you are
160
+ describing. Use the sample functions when your data are drawn from a larger
161
+ group you want to draw conclusions about. The sample formulas divide by
162
+ n − 1 (Bessel's correction), which makes the sample variance an unbiased
163
+ estimate of the population variance, and they need at least two values.
164
+
165
+ `describe`, `relative_std_dev`, `coefficient_of_variation` and the CLI
166
+ default to the sample formulas. Details:
167
+ [Sample vs population](https://ramesh-xyz.github.io/statistics-calculator-toolkit/sample-vs-population.html).
168
+
169
+ ## Formulas
170
+
171
+ ```
172
+ x̄ = Σxᵢ / n
173
+ SS = Σ (xᵢ − x̄)²
174
+ σ² = SS / N σ = √σ²
175
+ s² = SS / (n − 1) s = √s²
176
+ CV = s / |x̄|
177
+ RSD = 100 × s / |x̄| %
178
+ ```
179
+
180
+ A step-by-step walk-through of these formulas is on the
181
+ [standard deviation formula reference](https://standarddeviationcalculator.net/standard-deviation-formula/).
182
+
183
+ Further reading:
184
+
185
+ - [Standard deviation](https://ramesh-xyz.github.io/statistics-calculator-toolkit/standard-deviation.html)
186
+ - [Variance](https://ramesh-xyz.github.io/statistics-calculator-toolkit/variance.html)
187
+ - [Sample vs population](https://ramesh-xyz.github.io/statistics-calculator-toolkit/sample-vs-population.html)
188
+ - [Relative standard deviation](https://ramesh-xyz.github.io/statistics-calculator-toolkit/relative-standard-deviation.html)
189
+ - [Outliers](https://ramesh-xyz.github.io/statistics-calculator-toolkit/outliers.html)
190
+
191
+ ## Example datasets
192
+
193
+ [`examples/sample-datasets.csv`](https://github.com/Ramesh-xyz/statistics-calculator-toolkit/blob/main/examples/sample-datasets.csv) has three
194
+ small columns: lab replicate readings, exam scores and daily temperatures.
195
+ They are **illustrative values made up for teaching, not real research
196
+ data.** [`examples/worked-examples.md`](https://github.com/Ramesh-xyz/statistics-calculator-toolkit/blob/main/examples/worked-examples.md) works
197
+ through each one by hand and checks the result with the code.
198
+
199
+ ## Accuracy and edge cases
200
+
201
+ - **Algorithm.** Two passes: the mean, then the squared deviations from it,
202
+ each summed with `math.fsum`. A correction term removes the rounding error
203
+ left in the mean. The test suite compares results with Python's
204
+ `statistics.pvariance`, `variance`, `pstdev` and `stdev` (which use exact
205
+ rational arithmetic) to a relative tolerance of 1e-12, including data
206
+ offset by 10⁹ and 10¹².
207
+ - **Accepted input.** Any iterable of `int`, `float`, `decimal.Decimal` or
208
+ `fractions.Fraction`. Decimals and fractions are converted to `float`, so
209
+ their extra precision is not kept and results are always `float`.
210
+ - **Rejected input.** `bool`, `None`, strings, NaN, ±infinity, complex
211
+ numbers, and integers too large for a float raise `StatisticsError` naming
212
+ the offending item.
213
+ - **Missing values are not skipped.** `None` raises, and an empty CSV cell
214
+ raises with its row number. Remove or impute missing data yourself so the
215
+ choice is visible in your code.
216
+ - **Empty input** raises. **Sample statistics with one value** raise.
217
+ Population variance of one value is 0.
218
+ - **Zero mean.** `relative_std_dev` and `coefficient_of_variation` raise;
219
+ `describe` sets `rsd_percent` to `None`. RSD is only meaningful for
220
+ ratio-scale data; see
221
+ [Relative standard deviation](https://ramesh-xyz.github.io/statistics-calculator-toolkit/relative-standard-deviation.html).
222
+
223
+ ```python
224
+ >>> sd.mean([1, None, 3])
225
+ Traceback (most recent call last):
226
+ ...
227
+ sdtoolkit.core.StatisticsError: item 1: None (missing value); remove or fill it before computing
228
+ >>> sd.sample_stdev([1e9 + 4, 1e9 + 7, 1e9 + 13, 1e9 + 16]) ** 2
229
+ 30.0
230
+ ```
231
+
232
+ ## Tests
233
+
234
+ ```
235
+ PYTHONPATH=src python -m unittest discover -s tests
236
+ ```
237
+
238
+ The suite uses only `unittest`. GitHub Actions runs it on Python 3.9 to 3.13
239
+ ([workflow](https://github.com/Ramesh-xyz/statistics-calculator-toolkit/blob/main/.github/workflows/test-python.yml)).
240
+
241
+ ## Contributing
242
+
243
+ See [CONTRIBUTING.md](https://github.com/Ramesh-xyz/statistics-calculator-toolkit/blob/main/CONTRIBUTING.md).
244
+
245
+ ## License
246
+
247
+ MIT. See [LICENSE](https://github.com/Ramesh-xyz/statistics-calculator-toolkit/blob/main/LICENSE).
@@ -0,0 +1,10 @@
1
+ sdtoolkit/__init__.py,sha256=tOeKKMqYckvf58uHn-PrgDe2iOavGwOycaGEGmtgfW8,586
2
+ sdtoolkit/__main__.py,sha256=E6Gls0DNz8GQK2K-kOUIx8cYhgANW_CH54VKrfCfs14,52
3
+ sdtoolkit/cli.py,sha256=3oh9HJllzk3YSrHCjI-dWt7Onq_HT1Xv1Ju5-buWMbk,3336
4
+ sdtoolkit/core.py,sha256=sihuX4gvQlg83hrExRovTfwTWmFKpx7Lwv13XUn9dyo,6330
5
+ sdtoolkit-0.1.0.dist-info/licenses/LICENSE,sha256=FEPoA8cBEIhb-W6J-y_YzTMn8TjdKX9Euh1KvSwb-mY,1067
6
+ sdtoolkit-0.1.0.dist-info/METADATA,sha256=XxCM8hU211f12cIAbC8umt-GtFJ4nLGxcC6DvD9QzzI,9101
7
+ sdtoolkit-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
8
+ sdtoolkit-0.1.0.dist-info/entry_points.txt,sha256=z1B6dnaSLOFcFAvsBKvA8p7igpn2kPsCe5eAyY4vhqM,49
9
+ sdtoolkit-0.1.0.dist-info/top_level.txt,sha256=BJAOxl62Hfo46HWNk_oRIoK2IBjYagzpoTsGv6ykmrk,10
10
+ sdtoolkit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sdtoolkit = sdtoolkit.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ramesh-xyz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ sdtoolkit