bytesz 0.2.0__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.
- bytesz-0.2.0/.gitattributes +1 -0
- bytesz-0.2.0/.gitignore +12 -0
- bytesz-0.2.0/CHANGELOG.md +57 -0
- bytesz-0.2.0/CONTRIBUTING.md +22 -0
- bytesz-0.2.0/GUIDE.md +141 -0
- bytesz-0.2.0/LICENSE +21 -0
- bytesz-0.2.0/PKG-INFO +147 -0
- bytesz-0.2.0/README.md +116 -0
- bytesz-0.2.0/pyproject.toml +61 -0
- bytesz-0.2.0/requirements-dev.txt +3 -0
- bytesz-0.2.0/scripts/check.ps1 +8 -0
- bytesz-0.2.0/scripts/e2e_wheel.py +89 -0
- bytesz-0.2.0/scripts/release.ps1 +28 -0
- bytesz-0.2.0/src/bytesz/__init__.py +44 -0
- bytesz-0.2.0/src/bytesz/__main__.py +10 -0
- bytesz-0.2.0/src/bytesz/_bits.py +81 -0
- bytesz-0.2.0/src/bytesz/_cli.py +163 -0
- bytesz-0.2.0/src/bytesz/_cli_support.py +74 -0
- bytesz-0.2.0/src/bytesz/_convert.py +50 -0
- bytesz-0.2.0/src/bytesz/_errors.py +42 -0
- bytesz-0.2.0/src/bytesz/_format.py +159 -0
- bytesz-0.2.0/src/bytesz/_hints.py +49 -0
- bytesz-0.2.0/src/bytesz/_parse.py +182 -0
- bytesz-0.2.0/src/bytesz/_types.py +70 -0
- bytesz-0.2.0/src/bytesz/_units.py +71 -0
- bytesz-0.2.0/src/bytesz/py.typed +0 -0
- bytesz-0.2.0/tests/test_bits_convert.py +87 -0
- bytesz-0.2.0/tests/test_cli.py +167 -0
- bytesz-0.2.0/tests/test_contract.py +42 -0
- bytesz-0.2.0/tests/test_docs.py +42 -0
- bytesz-0.2.0/tests/test_format.py +67 -0
- bytesz-0.2.0/tests/test_format_options.py +134 -0
- bytesz-0.2.0/tests/test_numfmt_oracle.py +115 -0
- bytesz-0.2.0/tests/test_parse.py +82 -0
- bytesz-0.2.0/tests/test_parse_options.py +159 -0
- bytesz-0.2.0/tests/test_promise.py +100 -0
- bytesz-0.2.0/tests/test_roundtrip.py +20 -0
- bytesz-0.2.0/tests/test_sources.py +66 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text=auto eol=lf
|
bytesz-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.2.0]
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- The 2022 SI prefixes ronna and quetta (`RB`, `QB`) and the IEC 80000-13:2025
|
|
14
|
+
prefixes robi and quebi (`RiB`, `QiB`).
|
|
15
|
+
- `format` options (also on `format_bits`): `system` may be omitted and then follows
|
|
16
|
+
`unit` (default IEC); `rounding` (`half-up`, `half-even`, `half-down`, `up`, `down`,
|
|
17
|
+
`ceiling`, `floor`), `unit` to force one unit, `signed`, `trailing_zeros`,
|
|
18
|
+
`decimal_point` and `separator`.
|
|
19
|
+
- `parse` options: `rounding` for values that are not whole bytes, `signed`,
|
|
20
|
+
`decimal_point`, and an opt-in `legacy` mode that reads `KB`, `MB`, `GB` (and bare
|
|
21
|
+
`K`, `M`, `G`) as powers of 1024. Bare IEC prefixes (`1.5Mi`) are accepted, and bare
|
|
22
|
+
SI prefixes (`1.5M`) when `system="si"` is pinned, matching `numfmt` output.
|
|
23
|
+
- `parse_bits` and `format_bits` for bit counts with the `bit` symbol.
|
|
24
|
+
- `convert(count, unit)` for the exact `Decimal` value in one unit.
|
|
25
|
+
- Typed errors `UnknownUnitError`, `SystemMismatchError` and `FractionalBytesError`,
|
|
26
|
+
all subclasses of `InvalidSizeError`; every error carries `.text` and `.unit`.
|
|
27
|
+
- `System` and `Rounding` type aliases, `IEC_SUFFIXES` and `SI_SUFFIXES`.
|
|
28
|
+
- A `bytesz` command with `format` and `parse` subcommands, stdin input and a
|
|
29
|
+
numfmt style `--field` option. Also runnable as `python -m bytesz`.
|
|
30
|
+
- GUIDE.md.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Rounding now carries into the next unit: `format(1048575)` is `"1 MiB"` instead of
|
|
35
|
+
`"1024 KiB"`.
|
|
36
|
+
- Counts of 1024**9 bytes and above now use `RiB` and `QiB` (and `RB`, `QB` for SI)
|
|
37
|
+
instead of large numbers of `YiB`.
|
|
38
|
+
- A lower case `b` in `parse` now explains that it means bits, and a bit unit such
|
|
39
|
+
as `Kibit` raises `UnknownUnitError` pointing at `parse_bits`.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Parsing stays exact for long inputs and the largest units; the default decimal
|
|
44
|
+
context used to round products above 28 digits.
|
|
45
|
+
- Arithmetic runs in a private decimal context, so a caller's global settings
|
|
46
|
+
(traps, precision, exponent limits) no longer change or break the results.
|
|
47
|
+
|
|
48
|
+
## [0.1.0]
|
|
49
|
+
|
|
50
|
+
- `parse` turns a human size into an exact integer number of bytes, choosing the
|
|
51
|
+
system from the unit: IEC suffixes (KiB, MiB) use 1024 and SI suffixes (kB, MB)
|
|
52
|
+
use 1000.
|
|
53
|
+
- `format` turns a byte count into a human size for either system with a chosen
|
|
54
|
+
precision.
|
|
55
|
+
- Strict by design: ambiguous spellings such as `KB` raise `AmbiguousUnitError`,
|
|
56
|
+
and inputs that do not land on a whole byte raise `InvalidSizeError`.
|
|
57
|
+
- `system` can pin `parse` to one system so the other is rejected.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for taking the time. This project keeps a small, predictable shape.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pip install -r requirements-dev.txt
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Before you open a pull request
|
|
12
|
+
|
|
13
|
+
- Keep public API in `src/bytesz/__init__.py`; internals live in `_`-prefixed modules.
|
|
14
|
+
- Add a test for the behaviour you change. Tests inject time, disk and network; they
|
|
15
|
+
never touch the real thing.
|
|
16
|
+
- Run `scripts/check.ps1` (format, lint, types, tests, and an end to end run of the
|
|
17
|
+
built wheel through `scripts/e2e_wheel.py`) and make sure it passes. The GNU
|
|
18
|
+
`numfmt` cross-check runs when WSL with GNU coreutils is available and is skipped
|
|
19
|
+
otherwise.
|
|
20
|
+
- One logical change per commit, imperative subject line.
|
|
21
|
+
|
|
22
|
+
Open an issue first if the change is large or shifts the public API.
|
bytesz-0.2.0/GUIDE.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# bytesz guide
|
|
2
|
+
|
|
3
|
+
This guide explains the rules `bytesz` follows and why. The short version: the unit
|
|
4
|
+
alone decides the meaning, arithmetic is exact, and nothing ambiguous is guessed.
|
|
5
|
+
|
|
6
|
+
## Two systems
|
|
7
|
+
|
|
8
|
+
| Factor | IEC symbol | IEC name | SI symbol | SI name | SI factor |
|
|
9
|
+
| ------ | ---------- | -------- | --------- | ------- | --------- |
|
|
10
|
+
| 1024^1 | KiB | kibibyte | kB | kilobyte | 1000^1 |
|
|
11
|
+
| 1024^2 | MiB | mebibyte | MB | megabyte | 1000^2 |
|
|
12
|
+
| 1024^3 | GiB | gibibyte | GB | gigabyte | 1000^3 |
|
|
13
|
+
| 1024^4 | TiB | tebibyte | TB | terabyte | 1000^4 |
|
|
14
|
+
| 1024^5 | PiB | pebibyte | PB | petabyte | 1000^5 |
|
|
15
|
+
| 1024^6 | EiB | exbibyte | EB | exabyte | 1000^6 |
|
|
16
|
+
| 1024^7 | ZiB | zebibyte | ZB | zettabyte | 1000^7 |
|
|
17
|
+
| 1024^8 | YiB | yobibyte | YB | yottabyte | 1000^8 |
|
|
18
|
+
| 1024^9 | RiB | robibyte | RB | ronnabyte | 1000^9 |
|
|
19
|
+
| 1024^10 | QiB | quebibyte | QB | quettabyte | 1000^10 |
|
|
20
|
+
|
|
21
|
+
- The IEC binary prefixes were approved in December 1998 and published as an
|
|
22
|
+
amendment to IEC 60027-2; NIST documents them at
|
|
23
|
+
https://physics.nist.gov/cuu/Units/binary.html. NIST notes that they are not part
|
|
24
|
+
of the SI and that the symbol is `Ki`, not `ki`.
|
|
25
|
+
- Zebi and yobi were added in 2005. Ronna and quetta joined the SI in 2022
|
|
26
|
+
(https://www.bipm.org/en/measurement-units/si-prefixes), and their binary partners
|
|
27
|
+
robi and quebi were adopted in IEC 80000-13:2025.
|
|
28
|
+
- The SI kilo is a lower case `k`. `bytesz` writes `kB` and refuses `KB` by default.
|
|
29
|
+
|
|
30
|
+
## Case rules
|
|
31
|
+
|
|
32
|
+
Units are case sensitive because case carries meaning: `B` is a byte, `b` is a bit,
|
|
33
|
+
`k` is kilo, `Ki` is kibi. `parse` accepts exactly the symbols above plus `B`, the
|
|
34
|
+
bare IEC prefixes (`1.5Mi`, as `numfmt --to=iec-i` writes them), and a bare number.
|
|
35
|
+
Anything that only matches when case is ignored (`KB`, `kib`, `Mb`, `GIB`) raises
|
|
36
|
+
`AmbiguousUnitError`; anything else raises `UnknownUnitError`. Whitespace between the
|
|
37
|
+
number and the unit is optional (`1.5GB` and `512 MiB` both work).
|
|
38
|
+
|
|
39
|
+
## The legacy "KB = 1024" usage
|
|
40
|
+
|
|
41
|
+
Before 1999, and still today in places, `KB`, `MB` and `GB` meant powers of 1024.
|
|
42
|
+
JEDEC keeps those definitions for semiconductor memory, saying they are "included
|
|
43
|
+
only to reflect common usage", and several operating systems label sizes that way.
|
|
44
|
+
NIST also recalls the "1.44 MB" diskette, which holds 1 474 560 bytes: 1440 times
|
|
45
|
+
1024, a third meaning of megabyte. So `KB` really is ambiguous, and `bytesz` will not
|
|
46
|
+
pick a side for you. If your input comes from such a source, say so:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from bytesz import parse
|
|
50
|
+
|
|
51
|
+
parse("4 GB", legacy=True) # 4294967296
|
|
52
|
+
parse("4G", legacy=True) # 4294967296, bare prefixes as numfmt --from=iec reads them
|
|
53
|
+
parse("4 kB", legacy=True) # 4000, lower case k is never legacy
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`legacy=True` cannot be combined with `system="si"`. Pinning `system="si"` on its
|
|
57
|
+
own makes bare prefixes (`1.5M`, `2K`) mean powers of 1000, as `numfmt --from=si` does.
|
|
58
|
+
|
|
59
|
+
## Bits
|
|
60
|
+
|
|
61
|
+
`parse` rejects `Mb` because a bit rate mistaken for a byte size is off by a factor
|
|
62
|
+
of eight. Use `parse_bits` and `format_bits` for bit counts. They write the
|
|
63
|
+
unambiguous `bit` symbol (`kbit`, `Mibit`) and also read the short `b` form (`kb`,
|
|
64
|
+
`Mb`, `Kib`). Upper case `Kb` stays ambiguous there too.
|
|
65
|
+
|
|
66
|
+
## Rounding and carry
|
|
67
|
+
|
|
68
|
+
`format` picks the largest unit that keeps the number at one or more, rounds to
|
|
69
|
+
`precision` decimals (default 2) and drops trailing zeros unless
|
|
70
|
+
`trailing_zeros=True`. If rounding pushes the number up to the base, it carries:
|
|
71
|
+
1048575 bytes is `1 MiB`, not `1024 KiB`. Note what that means for IEC: 999.95 KiB
|
|
72
|
+
rounds to `1000 KiB` at one decimal, and that is correct, because 1000 KiB is still
|
|
73
|
+
below 1024 KiB. In SI, 999.95 kB does carry to `1 MB`. GNU `numfmt` behaves the same.
|
|
74
|
+
|
|
75
|
+
| bytesz rounding | ties | numfmt `--round` |
|
|
76
|
+
| --------------- | ---- | ---------------- |
|
|
77
|
+
| `half-up` (default) | away from zero | `nearest` |
|
|
78
|
+
| `half-even` | to the even digit | none |
|
|
79
|
+
| `half-down` | toward zero | none |
|
|
80
|
+
| `up` | away from zero always | `from-zero` |
|
|
81
|
+
| `down` | toward zero always | `towards-zero` |
|
|
82
|
+
| `ceiling` | toward positive infinity | `up` |
|
|
83
|
+
| `floor` | toward negative infinity | `down` |
|
|
84
|
+
|
|
85
|
+
Note that `numfmt` defaults to `from-zero` while `bytesz` defaults to `half-up`.
|
|
86
|
+
Pass `unit="MiB"` to force a unit; there is no carry then, and the unit picks the
|
|
87
|
+
system unless you also pass `system` (a unit from the other one raises
|
|
88
|
+
`SystemMismatchError`). `convert(count, unit)`
|
|
89
|
+
gives the exact `Decimal` value with no rounding at all.
|
|
90
|
+
|
|
91
|
+
## Fractions when parsing
|
|
92
|
+
|
|
93
|
+
`parse("1.5 B")` raises `FractionalBytesError`: half a byte is a bug in the input.
|
|
94
|
+
Pass a rounding mode when you would rather round: `parse("0.1 KiB",
|
|
95
|
+
rounding="half-up")` is 102.
|
|
96
|
+
|
|
97
|
+
## Exactness and huge values
|
|
98
|
+
|
|
99
|
+
All arithmetic is integer or `Decimal` with enough precision for the input, so
|
|
100
|
+
`parse("123456789012345678901234567890 QiB")` is exact and
|
|
101
|
+
`parse(format(n, precision=110)) == n` holds for any `n`. Every power of 1000 or 1024
|
|
102
|
+
divides into a terminating decimal, so `convert` never rounds.
|
|
103
|
+
|
|
104
|
+
## Signs and separators
|
|
105
|
+
|
|
106
|
+
Negative values are refused unless `signed=True`, which also allows a leading `+`
|
|
107
|
+
when parsing. `decimal_point=","` reads and writes a comma; it is never inferred
|
|
108
|
+
from the locale. `separator` sets the text between number and unit when formatting.
|
|
109
|
+
|
|
110
|
+
## Errors
|
|
111
|
+
|
|
112
|
+
| Error | Raised when | Subclass of |
|
|
113
|
+
| ----- | ----------- | ----------- |
|
|
114
|
+
| `ByteszError` | base class | `ValueError` |
|
|
115
|
+
| `InvalidSizeError` | malformed input, a sign without `signed=True` | `ByteszError` |
|
|
116
|
+
| `AmbiguousUnitError` | `KB`, `Mb`, `kib`, a bare `M` unless `system="si"` or `legacy=True` | `ByteszError` |
|
|
117
|
+
| `UnknownUnitError` | not a size unit, or a bit unit given to `parse` (and the reverse) | `InvalidSizeError` |
|
|
118
|
+
| `SystemMismatchError` | an SI unit with `system="iec"` or the reverse | `InvalidSizeError` |
|
|
119
|
+
| `FractionalBytesError` | not a whole byte and no `rounding` | `InvalidSizeError` |
|
|
120
|
+
|
|
121
|
+
Each carries `.text` (the input) and `.unit` (the unit involved) when known. Bad
|
|
122
|
+
option values (an unknown `system` or `rounding`) raise a plain `ValueError`, and a
|
|
123
|
+
non-string input to `parse` raises `TypeError`.
|
|
124
|
+
|
|
125
|
+
## Command line
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
bytesz format [--system iec|si] [--precision N] [--rounding MODE] [--unit U]
|
|
129
|
+
[--signed] [--trailing-zeros] [--separator S] [--decimal-point ,]
|
|
130
|
+
[--bits] [--field N] [VALUES ...]
|
|
131
|
+
bytesz parse [--system iec|si] [--legacy] [--rounding MODE] [--signed]
|
|
132
|
+
[--decimal-point ,] [--bits] [--field N] [VALUES ...]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
With no values, each line of stdin is one value. `--field N` converts only the Nth
|
|
136
|
+
whitespace separated field and keeps the rest of the line, so a size in that field
|
|
137
|
+
must be written without an inner space (`1.5MiB`). Blank lines pass through and a
|
|
138
|
+
leading byte order mark is ignored. `format` takes plain ASCII integers only. The
|
|
139
|
+
first error stops the run, goes to stderr, and the exit status is 1; usage errors,
|
|
140
|
+
such as a negative `--precision` or an unknown `--unit`, exit with 2. Messages name
|
|
141
|
+
the flags (`--signed`, `--legacy`) rather than the keyword arguments.
|
bytesz-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Javier Lianes
|
|
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.
|
bytesz-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: bytesz
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Strict two-way conversion between byte counts and human sizes with explicit IEC and SI units
|
|
5
|
+
Project-URL: Homepage, https://lianes8studio.com
|
|
6
|
+
Project-URL: Repository, https://github.com/JaviLianes8/bytesz
|
|
7
|
+
Project-URL: Issues, https://github.com/JaviLianes8/bytesz/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/JaviLianes8/bytesz/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: LinkedIn, https://www.linkedin.com/in/jlianes/
|
|
10
|
+
Project-URL: Funding, https://buymeacoffee.com/jlianesglrs
|
|
11
|
+
Author-email: Javier Lianes <javier@lianes8studio.com>
|
|
12
|
+
License-Expression: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: bytes,format,human,iec,kib,mib,numfmt,parse,si,size
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Environment :: Console
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build; extra == 'dev'
|
|
26
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
29
|
+
Requires-Dist: twine; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# bytesz
|
|
33
|
+
|
|
34
|
+
Strict two-way conversion between byte counts and human sizes with explicit IEC and SI units.
|
|
35
|
+
Typed, exact (integer and `Decimal` arithmetic only), zero dependencies, with a small CLI.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
pip install bytesz
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from bytesz import format, parse
|
|
47
|
+
|
|
48
|
+
# Human size to an exact integer number of bytes. The unit picks the system.
|
|
49
|
+
parse("1.5 MiB") # 1572864 (IEC, 1024 based)
|
|
50
|
+
parse("1.5 MB") # 1500000 (SI, 1000 based)
|
|
51
|
+
parse("512 B") # 512
|
|
52
|
+
parse("2048") # 2048 (a bare number is bytes)
|
|
53
|
+
|
|
54
|
+
# Bytes back to a human size, for whichever system you ask for.
|
|
55
|
+
format(1572864) # "1.5 MiB" (IEC is the default)
|
|
56
|
+
format(1500000, system="si") # "1.5 MB"
|
|
57
|
+
format(1234567, system="si", precision=2) # "1.23 MB"
|
|
58
|
+
format(1048575) # "1 MiB" (rounding carries into the next unit)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
It refuses to guess:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from bytesz import AmbiguousUnitError, FractionalBytesError, SystemMismatchError, parse
|
|
65
|
+
|
|
66
|
+
parse("1 KB") # AmbiguousUnitError: KB could mean kB (1000) or KiB (1024)
|
|
67
|
+
parse("100 Mb") # AmbiguousUnitError: a lower case b means bits, see parse_bits
|
|
68
|
+
parse("1.5 B") # FractionalBytesError: not a whole number of bytes
|
|
69
|
+
parse("1 kB", system="iec") # SystemMismatchError: an SI unit under a pinned IEC system
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Every input error is a `ByteszError` (a `ValueError`) carrying `.text` and `.unit`. Bad
|
|
73
|
+
option values raise a plain `ValueError`, and a non-string input to `parse` a `TypeError`.
|
|
74
|
+
|
|
75
|
+
## Options
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from bytesz import convert, format, format_bits, parse, parse_bits
|
|
79
|
+
|
|
80
|
+
# Formatting
|
|
81
|
+
format(1250, system="si", precision=1, rounding="half-even") # "1.2 kB"
|
|
82
|
+
format(1_000_000, unit="MiB") # "0.95 MiB"
|
|
83
|
+
format(5000, unit="kB") # "5 kB" (the unit picks the system)
|
|
84
|
+
format(-1536, signed=True) # "-1.5 KiB"
|
|
85
|
+
format(1536, precision=3, trailing_zeros=True) # "1.500 KiB"
|
|
86
|
+
format(1536, decimal_point=",", separator="") # "1,5KiB"
|
|
87
|
+
|
|
88
|
+
# Parsing
|
|
89
|
+
parse("1.0005 kB", rounding="half-up") # 1001
|
|
90
|
+
parse("1,5 MiB", decimal_point=",") # 1572864
|
|
91
|
+
parse("-2 KiB", signed=True) # -2048
|
|
92
|
+
parse("1.5M", system="si") # 1500000 (bare prefixes, as numfmt writes them)
|
|
93
|
+
parse("1.5Mi") # 1572864 (bare IEC prefix)
|
|
94
|
+
parse("4 GB", legacy=True) # 4294967296 (JEDEC style KB/MB/GB = 1024, opt-in)
|
|
95
|
+
|
|
96
|
+
# Bits, with the unambiguous "bit" symbol
|
|
97
|
+
parse_bits("100 Mbit") # 100000000
|
|
98
|
+
format_bits(1_500_000, system="si") # "1.5 Mbit"
|
|
99
|
+
|
|
100
|
+
# Exact value in one unit, no rounding at all
|
|
101
|
+
convert(1, "KiB") # Decimal("0.0009765625")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Rounding modes: `half-up` (default, ties away from zero), `half-even`, `half-down`,
|
|
105
|
+
`up`, `down`, `ceiling`, `floor`. Units reach the 2022 SI prefixes `RB` and `QB` and
|
|
106
|
+
the IEC 80000-13:2025 prefixes `RiB` and `QiB`.
|
|
107
|
+
|
|
108
|
+
## Command line
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
bytesz format 1572864 # 1.5 MiB
|
|
112
|
+
bytesz format --system si 1234567 # 1.23 MB
|
|
113
|
+
bytesz parse "1.5 MiB" 2GB # 1572864, 2000000000
|
|
114
|
+
bytesz parse --legacy 4KB # 4096
|
|
115
|
+
du -b * | bytesz format --field 1 # rewrite one column, like numfmt --field
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Values come from the arguments or, when there are none, one per line from stdin.
|
|
119
|
+
Errors go to stderr with exit status 1. `bytesz --help` lists every option.
|
|
120
|
+
|
|
121
|
+
## Why
|
|
122
|
+
|
|
123
|
+
Human byte sizes are a well known trap: `KB` means 1000 bytes to a disk vendor and
|
|
124
|
+
1024 bytes to an operating system, and most libraries quietly pick one. The popular
|
|
125
|
+
options lean on guessing or only go one way: `humanize` and `hurry.filesize` format
|
|
126
|
+
numbers but do not parse them back, `humanfriendly` parses but treats `KB` and `KiB`
|
|
127
|
+
as the same 1024 based value, and `bitmath` mixes bits and bytes with a broad API.
|
|
128
|
+
|
|
129
|
+
`bytesz` is deliberately narrow and strict. The unit alone decides the system, both
|
|
130
|
+
directions are exact, and anything ambiguous or fractional is rejected with a clear
|
|
131
|
+
error instead of being rounded, unless you ask for rounding. Reach for it when a
|
|
132
|
+
wrong factor of 1024 would be a real bug, such as quotas, billing, or on-disk
|
|
133
|
+
accounting. With matching `--round` and `--format=%.Nf`, its formatting agrees with
|
|
134
|
+
GNU `numfmt` (checked in the test suite on seeded values), except where `numfmt` itself
|
|
135
|
+
drifts because it divides in floating point.
|
|
136
|
+
|
|
137
|
+
See [GUIDE.md](GUIDE.md) for the rules in depth.
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT. See [LICENSE](LICENSE).
|
|
142
|
+
|
|
143
|
+
## Links
|
|
144
|
+
|
|
145
|
+
- Website: https://lianes8studio.com
|
|
146
|
+
- GitHub: https://github.com/JaviLianes8
|
|
147
|
+
- LinkedIn: https://www.linkedin.com/in/jlianes/
|
bytesz-0.2.0/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# bytesz
|
|
2
|
+
|
|
3
|
+
Strict two-way conversion between byte counts and human sizes with explicit IEC and SI units.
|
|
4
|
+
Typed, exact (integer and `Decimal` arithmetic only), zero dependencies, with a small CLI.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
pip install bytesz
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from bytesz import format, parse
|
|
16
|
+
|
|
17
|
+
# Human size to an exact integer number of bytes. The unit picks the system.
|
|
18
|
+
parse("1.5 MiB") # 1572864 (IEC, 1024 based)
|
|
19
|
+
parse("1.5 MB") # 1500000 (SI, 1000 based)
|
|
20
|
+
parse("512 B") # 512
|
|
21
|
+
parse("2048") # 2048 (a bare number is bytes)
|
|
22
|
+
|
|
23
|
+
# Bytes back to a human size, for whichever system you ask for.
|
|
24
|
+
format(1572864) # "1.5 MiB" (IEC is the default)
|
|
25
|
+
format(1500000, system="si") # "1.5 MB"
|
|
26
|
+
format(1234567, system="si", precision=2) # "1.23 MB"
|
|
27
|
+
format(1048575) # "1 MiB" (rounding carries into the next unit)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
It refuses to guess:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from bytesz import AmbiguousUnitError, FractionalBytesError, SystemMismatchError, parse
|
|
34
|
+
|
|
35
|
+
parse("1 KB") # AmbiguousUnitError: KB could mean kB (1000) or KiB (1024)
|
|
36
|
+
parse("100 Mb") # AmbiguousUnitError: a lower case b means bits, see parse_bits
|
|
37
|
+
parse("1.5 B") # FractionalBytesError: not a whole number of bytes
|
|
38
|
+
parse("1 kB", system="iec") # SystemMismatchError: an SI unit under a pinned IEC system
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Every input error is a `ByteszError` (a `ValueError`) carrying `.text` and `.unit`. Bad
|
|
42
|
+
option values raise a plain `ValueError`, and a non-string input to `parse` a `TypeError`.
|
|
43
|
+
|
|
44
|
+
## Options
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from bytesz import convert, format, format_bits, parse, parse_bits
|
|
48
|
+
|
|
49
|
+
# Formatting
|
|
50
|
+
format(1250, system="si", precision=1, rounding="half-even") # "1.2 kB"
|
|
51
|
+
format(1_000_000, unit="MiB") # "0.95 MiB"
|
|
52
|
+
format(5000, unit="kB") # "5 kB" (the unit picks the system)
|
|
53
|
+
format(-1536, signed=True) # "-1.5 KiB"
|
|
54
|
+
format(1536, precision=3, trailing_zeros=True) # "1.500 KiB"
|
|
55
|
+
format(1536, decimal_point=",", separator="") # "1,5KiB"
|
|
56
|
+
|
|
57
|
+
# Parsing
|
|
58
|
+
parse("1.0005 kB", rounding="half-up") # 1001
|
|
59
|
+
parse("1,5 MiB", decimal_point=",") # 1572864
|
|
60
|
+
parse("-2 KiB", signed=True) # -2048
|
|
61
|
+
parse("1.5M", system="si") # 1500000 (bare prefixes, as numfmt writes them)
|
|
62
|
+
parse("1.5Mi") # 1572864 (bare IEC prefix)
|
|
63
|
+
parse("4 GB", legacy=True) # 4294967296 (JEDEC style KB/MB/GB = 1024, opt-in)
|
|
64
|
+
|
|
65
|
+
# Bits, with the unambiguous "bit" symbol
|
|
66
|
+
parse_bits("100 Mbit") # 100000000
|
|
67
|
+
format_bits(1_500_000, system="si") # "1.5 Mbit"
|
|
68
|
+
|
|
69
|
+
# Exact value in one unit, no rounding at all
|
|
70
|
+
convert(1, "KiB") # Decimal("0.0009765625")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Rounding modes: `half-up` (default, ties away from zero), `half-even`, `half-down`,
|
|
74
|
+
`up`, `down`, `ceiling`, `floor`. Units reach the 2022 SI prefixes `RB` and `QB` and
|
|
75
|
+
the IEC 80000-13:2025 prefixes `RiB` and `QiB`.
|
|
76
|
+
|
|
77
|
+
## Command line
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
bytesz format 1572864 # 1.5 MiB
|
|
81
|
+
bytesz format --system si 1234567 # 1.23 MB
|
|
82
|
+
bytesz parse "1.5 MiB" 2GB # 1572864, 2000000000
|
|
83
|
+
bytesz parse --legacy 4KB # 4096
|
|
84
|
+
du -b * | bytesz format --field 1 # rewrite one column, like numfmt --field
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Values come from the arguments or, when there are none, one per line from stdin.
|
|
88
|
+
Errors go to stderr with exit status 1. `bytesz --help` lists every option.
|
|
89
|
+
|
|
90
|
+
## Why
|
|
91
|
+
|
|
92
|
+
Human byte sizes are a well known trap: `KB` means 1000 bytes to a disk vendor and
|
|
93
|
+
1024 bytes to an operating system, and most libraries quietly pick one. The popular
|
|
94
|
+
options lean on guessing or only go one way: `humanize` and `hurry.filesize` format
|
|
95
|
+
numbers but do not parse them back, `humanfriendly` parses but treats `KB` and `KiB`
|
|
96
|
+
as the same 1024 based value, and `bitmath` mixes bits and bytes with a broad API.
|
|
97
|
+
|
|
98
|
+
`bytesz` is deliberately narrow and strict. The unit alone decides the system, both
|
|
99
|
+
directions are exact, and anything ambiguous or fractional is rejected with a clear
|
|
100
|
+
error instead of being rounded, unless you ask for rounding. Reach for it when a
|
|
101
|
+
wrong factor of 1024 would be a real bug, such as quotas, billing, or on-disk
|
|
102
|
+
accounting. With matching `--round` and `--format=%.Nf`, its formatting agrees with
|
|
103
|
+
GNU `numfmt` (checked in the test suite on seeded values), except where `numfmt` itself
|
|
104
|
+
drifts because it divides in floating point.
|
|
105
|
+
|
|
106
|
+
See [GUIDE.md](GUIDE.md) for the rules in depth.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT. See [LICENSE](LICENSE).
|
|
111
|
+
|
|
112
|
+
## Links
|
|
113
|
+
|
|
114
|
+
- Website: https://lianes8studio.com
|
|
115
|
+
- GitHub: https://github.com/JaviLianes8
|
|
116
|
+
- LinkedIn: https://www.linkedin.com/in/jlianes/
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bytesz"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Strict two-way conversion between byte counts and human sizes with explicit IEC and SI units"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Javier Lianes", email = "javier@lianes8studio.com" }]
|
|
14
|
+
keywords = ["bytes", "size", "human", "iec", "si", "kib", "mib", "numfmt", "parse", "format"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Topic :: Utilities",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = []
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
bytesz = "bytesz._cli:main"
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = ["ruff", "mypy", "pytest", "build", "twine"]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://lianes8studio.com"
|
|
35
|
+
Repository = "https://github.com/JaviLianes8/bytesz"
|
|
36
|
+
Issues = "https://github.com/JaviLianes8/bytesz/issues"
|
|
37
|
+
Changelog = "https://github.com/JaviLianes8/bytesz/blob/main/CHANGELOG.md"
|
|
38
|
+
LinkedIn = "https://www.linkedin.com/in/jlianes/"
|
|
39
|
+
Funding = "https://buymeacoffee.com/jlianesglrs"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.sdist]
|
|
42
|
+
exclude = ["IMPROVEMENTS-round*.md", ".github"]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/bytesz"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 100
|
|
49
|
+
target-version = "py310"
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint]
|
|
52
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
53
|
+
|
|
54
|
+
[tool.mypy]
|
|
55
|
+
python_version = "3.10"
|
|
56
|
+
strict = true
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
pythonpath = ["src"]
|
|
61
|
+
addopts = "-q"
|