From 0c3b10c301e16f4a5a7d99dfd090dc2aef41eb20 Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Wed, 2 Oct 2019 23:03:01 +0900 Subject: [PATCH 01/92] checks: support numpy.int64 Adds numbers.Integer as a valid parent. --- bitmath/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 6babc18..1bbafab 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -174,7 +174,7 @@ class Bitmath(object): """The base class for all the other prefix classes""" # All the allowed input types - valid_types = (int, float, long) + valid_types = (float, long, numbers.Integral) def __init__(self, value=0, bytes=None, bits=None): """Instantiate with `value` by the unit, in plain bytes, or From 8545ddf0f44c7f04d38f22e584fbfeacac64731a Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Thu, 26 Jan 2023 18:50:01 -0600 Subject: [PATCH 02/92] Begin dropping support for python 2 --- setup.py.in | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/setup.py.in b/setup.py.in index a688c5a..44b6e36 100644 --- a/setup.py.in +++ b/setup.py.in @@ -52,23 +52,22 @@ setup( 'Intended Audience :: System Administrators', 'Intended Audience :: Telecommunications Industry', 'License :: OSI Approved :: MIT License', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: POSIX :: Linux', - 'Operating System :: POSIX', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.1', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', + 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', + 'Topic :: Communications :: File Sharing', + 'Topic :: Internet', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Information Analysis', 'Topic :: Scientific/Engineering :: Mathematics', - 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: User Interfaces', - 'Topic :: Software Development :: Widget Sets', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Software Development :: Testing', + 'Topic :: Software Development :: Testing :: Acceptance', + 'Topic :: Software Development :: Testing :: Unit', 'Topic :: System :: Filesystems', + 'Topic :: System :: Systems Administration', 'Topic :: Text Processing :: Filters', 'Topic :: Utilities' ], From db006933b9341f9b16d743d541de51091ffa9c09 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Thu, 26 Jan 2023 20:01:46 -0600 Subject: [PATCH 03/92] Out with the old --- Makefile | 78 ++-------------------- NEWS.rst | 42 ++++++++++++ README.rst | 116 +-------------------------------- VERSION | 2 +- debian/.gitignore | 6 -- debian/changelog | 12 ---- debian/compat | 1 - debian/control | 34 ---------- debian/copyright | 20 ------ debian/docs | 3 - debian/python-bitmath.manpages | 1 - debian/rules | 31 --------- debian/source/format | 1 - debian/source/options | 1 - docsite/source/conf.py | 4 +- docsite/source/module.rst | 2 + 16 files changed, 56 insertions(+), 298 deletions(-) delete mode 100644 debian/.gitignore delete mode 100644 debian/changelog delete mode 100644 debian/compat delete mode 100644 debian/control delete mode 100644 debian/copyright delete mode 100644 debian/docs delete mode 100644 debian/python-bitmath.manpages delete mode 100755 debian/rules delete mode 100644 debian/source/format delete mode 100644 debian/source/options diff --git a/Makefile b/Makefile index 531f8e4..bd181c7 100644 --- a/Makefile +++ b/Makefile @@ -142,10 +142,6 @@ sdist: setup.py clean @echo "#############################################" python setup.py sdist -deb: setup.py clean - git archive --format=tar --prefix=bitmath/ HEAD | gzip -9 > ../bitmath_$(VERSION).$(RPMRELEASE).orig.tar.gz - debuild -us -uc - rpmcommon: sdist python-bitmath.spec setup.py @echo "#############################################" @echo "# Building (S)RPM Now" @@ -153,25 +149,6 @@ rpmcommon: sdist python-bitmath.spec setup.py @mkdir -p rpm-build @cp dist/$(NAME)-$(VERSION).$(RPMRELEASE).tar.gz rpm-build/$(VERSION).$(RPMRELEASE).tar.gz -srpm5: rpmcommon - rpmbuild --define "_topdir %(pwd)/rpm-build" \ - --define 'dist .el5' \ - --define "_builddir %{_topdir}" \ - --define "_rpmdir %{_topdir}" \ - --define "_srcrpmdir %{_topdir}" \ - --define "_specdir $(RPMSPECDIR)" \ - --define "_sourcedir %{_topdir}" \ - --define "_source_filedigest_algorithm 1" \ - --define "_binary_filedigest_algorithm 1" \ - --define "_binary_payload w9.gzdio" \ - --define "_source_payload w9.gzdio" \ - --define "_default_patch_fuzz 2" \ - -bs $(RPMSPEC) - @echo "#############################################" - @echo "$(PKGNAME) SRPM is built:" - @find rpm-build -maxdepth 2 -name '$(PKGNAME)*src.rpm' | awk '{print " " $$1}' - @echo "#############################################" - srpm: rpmcommon rpmbuild --define "_topdir %(pwd)/rpm-build" \ --define "_builddir %{_topdir}" \ @@ -198,45 +175,7 @@ rpm: rpmcommon @find rpm-build -maxdepth 2 -name '$(PKGNAME)*.rpm' | awk '{print " " $$1}' @echo "#############################################" -virtualenv2: - @echo "#############################################" - @echo "# Creating a virtualenv" - @echo "#############################################" - virtualenv $(NAME)env2 --python=python2 - . $(NAME)env2/bin/activate && pip install -r requirements.txt - -ci-unittests2: - @echo "#############################################" - @echo "# Running Unit Tests in virtualenv" - @echo "# Using python: $(shell ./bitmathenv2/bin/python --version 2>&1)" - @echo "#############################################" - . $(NAME)env2/bin/activate && export PYVER=PY2X && nosetests -v --with-coverage --cover-html --cover-min-percentage=90 --cover-package=bitmath tests/ - @echo "Testing argparse integration without progressbar dependency (#86)" - . $(NAME)env2/bin/activate && pip uninstall -y progressbar231 click - . $(NAME)env2/bin/activate && export PYVER=PY2X && nosetests -v --with-coverage --cover-html --cover-min-percentage=90 --cover-package=bitmath tests/test_argparse_type.py - -ci-list-deps2: - @echo "#############################################" - @echo "# Listing all pip deps" - @echo "#############################################" - . $(NAME)env2/bin/activate && pip freeze - -ci-pycodestyle2: - @echo "#############################################" - @echo "# Running PEP8 Compliance Tests in virtualenv" - @echo "#############################################" - . $(NAME)env2/bin/activate && pycodestyle -v --ignore=E501,E722 bitmath/__init__.py tests/*.py - -ci-pyflakes2: - @echo "#################################################" - @echo "# Running Pyflakes Compliance Tests in virtualenv" - @echo "#################################################" - . $(NAME)env2/bin/activate && pyflakes bitmath/__init__.py tests/*.py - -ci2: clean uniquetestnames virtualenv2 ci-list-deps2 ci-pycodestyle2 ci-pyflakes2 ci-unittests2 - : - -virtualenv3: +virtualenv: @echo "" @echo "#############################################" @echo "# Creating a virtualenv" @@ -244,7 +183,7 @@ virtualenv3: virtualenv $(NAME)env3 --python=python3 . $(NAME)env3/bin/activate && pip install -r requirements-py3.txt -ci-unittests3: +ci-unittests: @echo "" @echo "#############################################" @echo "# Running Unit Tests in virtualenv" @@ -255,31 +194,26 @@ ci-unittests3: . $(NAME)env3/bin/activate && pip uninstall -y progressbar33 click . $(NAME)env3/bin/activate && export PYVER=PY3X && nosetests -v --with-coverage --cover-html --cover-package=bitmath tests/test_argparse_type.py -ci-list-deps3: +ci-list-deps: @echo "" @echo "#############################################" @echo "# Listing all pip deps" @echo "#############################################" . $(NAME)env3/bin/activate && pip freeze -ci-pycodestyle3: +ci-pycodestyle: @echo "" @echo "#############################################" @echo "# Running PEP8 Compliance Tests in virtualenv" @echo "#############################################" . $(NAME)env3/bin/activate && pycodestyle -v --ignore=E501,E722 bitmath/__init__.py tests/*.py -ci-pyflakes3: +ci-pyflakes: @echo "" @echo "#################################################" @echo "# Running Pyflakes Compliance Tests in virtualenv" @echo "#################################################" . $(NAME)env3/bin/activate && pyflakes bitmath/__init__.py tests/*.py -ci3: clean uniquetestnames virtualenv3 ci-list-deps3 ci-pycodestyle3 ci-pyflakes3 ci-unittests3 - : - -ci: ci2 +ci: clean uniquetestnames virtualenv ci-list-deps ci-pycodestyle ci-pyflakes ci-unittests : - -ci-all: ci2 ci3 diff --git a/NEWS.rst b/NEWS.rst index 9f83be2..6b7798e 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -5,6 +5,48 @@ NEWS :depth: 1 :local: +.. _bitmath-2.0.0: + +bitmath-2.0.0 +************* + +bitmath-2.0.0 **will be** the first new release since 1.3.3 was +released in 2018! + +Beginning with the release of 2.0.0 bitmath will only officially +support supported python versions. It is considered a happy bonus if +bitmath works with an unsupported Python version. + +The focus of the first "bitmath 2" releases will be +simplification. Some things will go again, they may come back a little +later. + + +What to Expect +============== + +In the semantic versioning world, a major version number increase is +meant to express fundamental changes to the software. Changes which +will **almost certainly** result in breakages for at least some of the +user base. For bitmath this will be true as well, and in this case +that user base is specifically anyone still using the library on +Python 2.x. + +* The Bitmath API - The fundamental API will remain unchanged. What + will change are Python language features used and dropping of + workarounds for older Python versions +* Bitmath :ref:`Integrations + ` - Integrations will be + removed from the primary source code for now. Many of these can be + provided as code examples instead which will simplify packaging and + testing requirements for the project. +* Packaging - It looks like a lot has changed in the last 2.5 years in + the Python packaging world, and I have a lot to catch up on. I guess + we use TOML instead of setup.py now, that's neat. +* Distribution - I daily drive Fedora Linux and Mac OS X, I don't have + time to keep up with other platforms. If someone wants to bring back + debian packaging, contact me and we'll work something out. + .. _bitmath-1.4.0-1: diff --git a/README.rst b/README.rst index 2a60c1d..54fa8b0 100644 --- a/README.rst +++ b/README.rst @@ -72,20 +72,11 @@ Installation The easiest way to install bitmath is via ``dnf`` (or ``yum``) if you're on a Fedora/RHEL based distribution. bitmath is available in -the main Fedora repositories, as well as the `EPEL6 -`_ -and `EPEL7 -`_ -repositories. There are now dual python2.x and python3.x releases -available. +the main Fedora repositories, as well as EPEL Repositories. As of 2022 +bitmath is only developed, tested, and supported for currently +supported Python releases. -**Python 2.x**: - -.. code-block:: bash - - $ sudo dnf install python2-bitmath - **Python 3.x**: .. code-block:: bash @@ -118,18 +109,6 @@ You could also install bitmath from `PyPi for more information. -**PPA**: - -Ubuntu Xenial, Wily, Vivid, Trusty, and Precise users can install -bitmath from the `launchpad PPA -`_: - -.. code-block:: bash - - $ sudo add-apt-repository ppa:tbielawa/bitmath - $ sudo apt-get update - $ sudo apt-get install python-bitmath - **Source**: @@ -435,92 +414,3 @@ Formatting [1.000@KiB] [38.000@Byte] [10.000@Byte] - -``argparse`` Integration ------------------------- - -Example script using ``bitmath.integrations.bmargparse.BitmathType`` as an -argparser argument type: - -.. code-block:: python - - import argparse - from bitmath.integrations.bmargparse import BitmathType - parser = argparse.ArgumentParser( - description="Arg parser with a bitmath type argument") - parser.add_argument('--block-size', - type=BitmathType, - required=True) - - results = parser.parse_args() - print "Parsed in: {PARSED}; Which looks like {TOKIB} as a Kibibit".format( - PARSED=results.block_size, - TOKIB=results.block_size.Kib) - -If ran as a script the results would be similar to this: - -.. code-block:: bash - - $ python ./bmargparse.py --block-size 100MiB - Parsed in: 100.0 MiB; Which looks like 819200.0 Kib as a Kibibit - -``click`` Integration ---------------------- - -Example script using ``bitmath.integrations.bmclick.BitmathType`` as an -click parameter type: - -.. code-block:: python - - import click - from bitmath.integrations.bmclick import BitmathType - - @click.command() - @click.argument('size', type=BitmathType()) - def best_prefix(size): - click.echo(size.best_prefix()) - -If ran as a script the results should be similar to this: - -.. code-block:: bash - - $ python ./bestprefix.py "1024 KiB" - 1.0 MiB - -``progressbar`` Integration ---------------------------- - -Use ``bitmath.integrations.bmprogressbar.BitmathFileTransferSpeed`` as a -``progressbar`` file transfer speed widget to monitor download speeds: - -.. code-block:: python - - import requests - import progressbar - import bitmath - from bitmath.integrations.bmprogressbar import BitmathFileTransferSpeed - - FETCH = 'https://www.kernel.org/pub/linux/kernel/v3.0/patch-3.16.gz' - widgets = ['Bitmath Progress Bar Demo: ', ' ', - progressbar.Bar(marker=progressbar.RotatingMarker()), ' ', - BitmathFileTransferSpeed()] - - r = requests.get(FETCH, stream=True) - size = bitmath.Byte(int(r.headers['Content-Length'])) - pbar = progressbar.ProgressBar(widgets=widgets, maxval=int(size), - term_width=80).start() - chunk_size = 2048 - with open('/dev/null', 'wb') as fd: - for chunk in r.iter_content(chunk_size): - fd.write(chunk) - if (pbar.currval + chunk_size) < pbar.maxval: - pbar.update(pbar.currval + chunk_size) - pbar.finish() - - -If ran as a script the results would be similar to this: - -.. code-block:: bash - - $ python ./smalldl.py - Bitmath Progress Bar Demo: ||||||||||||||||||||||||||||||||||||||||| 1.58 MiB/s diff --git a/VERSION b/VERSION index e21e727..227cea2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.0 \ No newline at end of file +2.0.0 diff --git a/debian/.gitignore b/debian/.gitignore deleted file mode 100644 index e8653ae..0000000 --- a/debian/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -files -python-bitmath.debhelper.log -python-bitmath.postinst.debhelper -python-bitmath.prerm.debhelper -python-bitmath.substvars -python-bitmath/ diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index 333d576..0000000 --- a/debian/changelog +++ /dev/null @@ -1,12 +0,0 @@ -bitmath (1.3.1.1-1~ppa1~trusty1) trusty; urgency=low - - * New release - - -- Timothy Bielawa Sun, 17 Jul 2016 11:59:48 +0000 - - -bitmath (1.3.0.2-1~ppa1~trusty1) trusty; urgency=low - - * First deb package release - - -- Timothy Bielawa Sat, 02 Jul 2016 15:52:37 +0000 diff --git a/debian/compat b/debian/compat deleted file mode 100644 index 7f8f011..0000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -7 diff --git a/debian/control b/debian/control deleted file mode 100644 index 5111522..0000000 --- a/debian/control +++ /dev/null @@ -1,34 +0,0 @@ -Source: bitmath -Maintainer: Tim Bielawa -Section: python -Priority: optional -Build-Depends: python-setuptools (>= 0.6b3), python-all (>= 2.6.6-3), debhelper (>= 7.4.3) -Standards-Version: 3.9.5 -Homepage: http://bitmath.readthedocs.io/en/latest/ -Vcs-Browser: https://github.com/tbielawa/bitmath -Vcs-Git: https://github.com/tbielawa/bitmath.git - -Package: python-bitmath -Architecture: all -Homepage: http://bitmath.readthedocs.io/en/latest/ -Depends: ${misc:Depends}, ${python:Depends} -Description: Pythonic module for representing and manipulating file sizes - bitmath simplifies many facets of interacting with file sizes in - various units. Examples include: converting between SI and NIST prefix - units (GiB to kB), converting between units of the same type (SI to - SI, or NIST to NIST), basic arithmetic operations (subtracting 42KiB - from 50GiB), and rich comparison operations (1024 Bytes == 1KiB), - bitwise operations, sorting, automatic best human-readable prefix - selection, and completely customizable formatting. - . - In addition to the conversion and math operations, bitmath provides - human readable representations of values which are suitable for use in - interactive shells as well as larger scripts and applications. It can - also read the capacity of system storage devices. bitmath can parse - strings (like "1 KiB") into proper objects and has support for - integration with the argparse module as a custom argument type and the - progressbar module as a custom file transfer speed widget. - . - bitmath is thoroughly unittested, with almost 200 individual tests (a - number which is always increasing). bitmath's test-coverage is almost - always at 100%. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index 69fe7b7..0000000 --- a/debian/copyright +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright © 2014 Tim Bielawa - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/debian/docs b/debian/docs deleted file mode 100644 index 040474d..0000000 --- a/debian/docs +++ /dev/null @@ -1,3 +0,0 @@ -README.rst -NEWS.rst -docsite/source/ diff --git a/debian/python-bitmath.manpages b/debian/python-bitmath.manpages deleted file mode 100644 index 670e348..0000000 --- a/debian/python-bitmath.manpages +++ /dev/null @@ -1 +0,0 @@ -bitmath.1 diff --git a/debian/rules b/debian/rules deleted file mode 100755 index ed437d8..0000000 --- a/debian/rules +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/make -f - -# This file was automatically generated by stdeb 0.8.5 at -# Sat, 02 Jul 2016 15:52:37 +0000 - -%: - dh $@ --with python2 --buildsystem=python_distutils - - -override_dh_auto_clean: - python setup.py clean -a - find . -name \*.pyc -exec rm {} \; - - - -override_dh_auto_build: - python setup.py build --force - - - -override_dh_auto_install: - python setup.py install --force --root=debian/python-bitmath --no-compile -O0 --install-layout=deb - - - -override_dh_python2: - dh_python2 --no-guessing-versions - - - - diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 163aaf8..0000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/debian/source/options b/debian/source/options deleted file mode 100644 index bcc4bbb..0000000 --- a/debian/source/options +++ /dev/null @@ -1 +0,0 @@ -extend-diff-ignore="\.egg-info$" \ No newline at end of file diff --git a/docsite/source/conf.py b/docsite/source/conf.py index 2c724be..8fce262 100644 --- a/docsite/source/conf.py +++ b/docsite/source/conf.py @@ -55,9 +55,9 @@ # built documents. # # The short X.Y version. -version = '1.4.0' +version = '2.0.0' # The full version, including alpha/beta/rc tags. -release = '1.4.0' +release = '2.0.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docsite/source/module.rst b/docsite/source/module.rst index 48cf3e3..e7d868c 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -850,6 +850,8 @@ behavior. .. py:module:: bitmath.integrations +.. _bitmath_3rd_party_module_integrations: + 3rd Party Module Integrations ***************************** From 6da9b94581a95b8160d4e5e33fc46496f7e3cde3 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 08:40:35 -0600 Subject: [PATCH 04/92] Doc updates --- docsite/source/index.rst | 34 ++-------------------------------- docsite/source/index.rst.in | 32 ++------------------------------ 2 files changed, 4 insertions(+), 62 deletions(-) diff --git a/docsite/source/index.rst b/docsite/source/index.rst index e3a14bc..054de77 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -25,15 +25,10 @@ focusing on file size unit conversion, functionality now includes: * Rich comparison operations (``1024 Bytes == 1KiB``) * bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) * Reading a device's storage capacity (Linux/OS X support only) -* `argparse `_ - integration as a custom type -* `click `_ - integration as a custom parameter type -* `progressbar `_ - integration as a better file transfer speed widget * String parsing * Sorting - +* `argparse `_ + integration as a custom type In addition to the conversion and math operations, `bitmath` provides human readable representations of values which are suitable for use in @@ -66,25 +61,12 @@ the main Fedora repositories, as well as the EPEL6 and EPEL7 repositories. There are now dual python2.x and python3.x releases available. - -**Python 2.x**: - -.. code-block:: bash - - $ sudo dnf install python2-bitmath - **Python 3.x**: .. code-block:: bash $ sudo dnf install python3-bitmath -.. note:: - - **Upgrading**: If you have the old *python-bitmath* package - installed presently, you could also run ``sudo dnf update - python-bitmath`` instead - **PyPi**: @@ -104,18 +86,6 @@ You could also install bitmath from `PyPi for more information. -**PPA**: - -Ubuntu Xenial, Wily, Vivid, Trusty, and Precise users can install -bitmath from the `launchpad PPA -`_: - -.. code-block:: bash - - $ sudo add-apt-repository ppa:tbielawa/bitmath - $ sudo apt-get update - $ sudo apt-get install python-bitmath - **Source**: diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index 1626c23..55da3e3 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -25,13 +25,10 @@ focusing on file size unit conversion, functionality now includes: * Rich comparison operations (``1024 Bytes == 1KiB``) * bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) * Reading a device's storage capacity (Linux/OS X support only) -* `argparse `_ - integration as a custom type -* `progressbar `_ - integration as a better file transfer speed widget * String parsing * Sorting - +* `argparse `_ + integration as a custom type In addition to the conversion and math operations, `bitmath` provides human readable representations of values which are suitable for use in @@ -64,25 +61,12 @@ the main Fedora repositories, as well as the EPEL6 and EPEL7 repositories. There are now dual python2.x and python3.x releases available. - -**Python 2.x**: - -.. code-block:: bash - - $ sudo dnf install python2-bitmath - **Python 3.x**: .. code-block:: bash $ sudo dnf install python3-bitmath -.. note:: - - **Upgrading**: If you have the old *python-bitmath* package - installed presently, you could also run ``sudo dnf update - python-bitmath`` instead - **PyPi**: @@ -102,18 +86,6 @@ You could also install bitmath from `PyPi for more information. -**PPA**: - -Ubuntu Xenial, Wily, Vivid, Trusty, and Precise users can install -bitmath from the `launchpad PPA -`_: - -.. code-block:: bash - - $ sudo add-apt-repository ppa:tbielawa/bitmath - $ sudo apt-get update - $ sudo apt-get install python-bitmath - **Source**: From 450ee99d9eeea2c86ec72e723d01b8dc9b8bd5aa Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 09:21:38 -0600 Subject: [PATCH 05/92] Remove old integrations FOR NOW. They will return with proper credit in a future doc update --- bitmath/integrations/bmclick.py | 65 --------------------------- bitmath/integrations/bmprogressbar.py | 25 ----------- 2 files changed, 90 deletions(-) delete mode 100644 bitmath/integrations/bmclick.py delete mode 100644 bitmath/integrations/bmprogressbar.py diff --git a/bitmath/integrations/bmclick.py b/bitmath/integrations/bmclick.py deleted file mode 100644 index a71c154..0000000 --- a/bitmath/integrations/bmclick.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -# The MIT License (MIT) -# -# Copyright © 2014-2016 Tim Bielawa -# See GitHub Contributors Graph for more information -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sub-license, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import bitmath -import click - - -class BitmathType(click.ParamType): - """An parameter type for integrations with the click module. - -For more information see https://click.palletsprojects.com/en/7.x/parameters/ -and https://click.palletsprojects.com/en/7.x/options/#basic-value-options - -Example usage of the click Bitmath type for a click argument: - - from bitmath.integrations.bmclick import BitmathType - - @click.command() - @click.argument('size', type=BitmathType) - def best_prefix(size): - click.echo(size.best_prefix()) - -It can also be used for click options: - - from bitmath.integrations.bmclick import BitmathType - - @click.command() - @click.option('--size', required=True, type=BitmathType) - def best_prefix(size): - click.echo(size.best_prefix()) -""" - name = 'bitmath' - - def convert(self, value, param, ctx): - try: - return bitmath.parse_string(value) - except ValueError: - self.fail("'%s' can not be parsed into a valid bitmath object" % - value) - - -BITMATH = BitmathType() diff --git a/bitmath/integrations/bmprogressbar.py b/bitmath/integrations/bmprogressbar.py deleted file mode 100644 index 06fa1e0..0000000 --- a/bitmath/integrations/bmprogressbar.py +++ /dev/null @@ -1,25 +0,0 @@ -import bitmath -import progressbar.widgets - - -class BitmathFileTransferSpeed(progressbar.widgets.Widget): - """Widget for showing the transfer speed (useful for file transfers).""" - __slots__ = ('system', 'format') - - def __init__(self, system=bitmath.NIST, format="{value:.2f} {unit}/s"): - self.system = system - self.format = format - - def update(self, pbar): - """Updates the widget with the current NIST/SI speed. - -Basically, this calculates the average rate of update and figures out -how to make a "pretty" prefix unit""" - - if pbar.seconds_elapsed < 2e-6 or pbar.currval < 2e-6: - scaled = bitmath.Byte() - else: - speed = pbar.currval / pbar.seconds_elapsed - scaled = bitmath.Byte(speed).best_prefix(system=self.system) - - return scaled.format(self.format) From 8cf129e72d642d45e609b3043a20387fa905ffd0 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 09:21:49 -0600 Subject: [PATCH 06/92] More doc updates --- README.rst | 48 +++++++++------ bitmath.1 | 6 +- docsite/source/index.rst | 66 +------------------- docsite/source/index.rst.in | 5 +- docsite/source/module.rst | 118 ------------------------------------ 5 files changed, 38 insertions(+), 205 deletions(-) diff --git a/README.rst b/README.rst index 54fa8b0..e0f1db2 100644 --- a/README.rst +++ b/README.rst @@ -31,14 +31,10 @@ focusing on file size unit conversion, functionality now includes: * Rich comparison operations (``1024 Bytes == 1KiB``) * bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) * Reading a device's storage capacity (Linux/OS X support only) -* `argparse `_ - integration as a custom type -* `click `_ - integration as a custom parameter type -* `progressbar `_ - integration as a better file transfer speed widget * String parsing * Sorting +* `argparse `_ + integration as a custom type In addition to the conversion and math operations, `bitmath` provides @@ -72,24 +68,16 @@ Installation The easiest way to install bitmath is via ``dnf`` (or ``yum``) if you're on a Fedora/RHEL based distribution. bitmath is available in -the main Fedora repositories, as well as EPEL Repositories. As of 2022 +the main Fedora repositories, as well as EPEL Repositories. As of 2023 bitmath is only developed, tested, and supported for currently supported Python releases. -**Python 3.x**: - .. code-block:: bash $ sudo dnf install python3-bitmath -.. note:: - - **Upgrading**: If you have the old *python-bitmath* package - installed presently, you could also run ``sudo dnf update - python-bitmath`` instead - **PyPi**: @@ -140,8 +128,6 @@ Topics include: * Context Managers * Module Variables * ``argparse`` integration - * ``click`` integration - * ``progressbar`` integration * The ``bitmath`` command-line Tool @@ -414,3 +400,31 @@ Formatting [1.000@KiB] [38.000@Byte] [10.000@Byte] + +``argparse`` Integration +------------------------ + +Example script using ``bitmath.integrations.bmargparse.BitmathType`` as an +argparser argument type: + +.. code-block:: python + + import argparse + from bitmath.integrations.bmargparse import BitmathType + parser = argparse.ArgumentParser( + description="Arg parser with a bitmath type argument") + parser.add_argument('--block-size', + type=BitmathType, + required=True) + + results = parser.parse_args() + print "Parsed in: {PARSED}; Which looks like {TOKIB} as a Kibibit".format( + PARSED=results.block_size, + TOKIB=results.block_size.Kib) + +If ran as a script the results would be similar to this: + +.. code-block:: bash + + $ python ./bmargparse.py --block-size 100MiB + Parsed in: 100.0 MiB; Which looks like 819200.0 Kib as a Kibibit diff --git a/bitmath.1 b/bitmath.1 index ed9d199..c380692 100644 --- a/bitmath.1 +++ b/bitmath.1 @@ -2,12 +2,12 @@ .\" Title: bitmath .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 04/16/2026 +.\" Date: 02/04/2023 .\" Manual: python-bitmath -.\" Source: bitmath 1.4.0 +.\" Source: bitmath 2.0.0 .\" Language: English .\" -.TH "BITMATH" "1" "04/16/2026" "bitmath 1\&.4\&.0" "python\-bitmath" +.TH "BITMATH" "1" "02/04/2023" "bitmath 2\&.0\&.0" "python\-bitmath" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/docsite/source/index.rst b/docsite/source/index.rst index 054de77..abc779d 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -57,9 +57,8 @@ Installation The easiest way to install bitmath is via ``dnf`` (or ``yum``) if you're on a Fedora/RHEL based distribution. bitmath is available in -the main Fedora repositories, as well as the EPEL6 and EPEL7 -repositories. There are now dual python2.x and python3.x releases -available. +the main Fedora repositories, as well as the latest supported EPEL +repositories. **Python 3.x**: @@ -381,64 +380,3 @@ If ran as a script the results would be similar to this: $ python ./bmargparse.py --block-size 100MiB Parsed in: 100.0 MiB; Which looks like 819200.0 Kib as a Kibibit - -``click`` Integration ---------------------- - -Example script using ``bitmath.integrations.bmclick.BitmathType`` as an -click parameter type: - -.. code-block:: python - - import click - from bitmath.integrations.bmclick import BitmathType - - @click.command() - @click.argument('size', type=BitmathType()) - def best_prefix(size): - click.echo(size.best_prefix()) - -If ran as a script the results should be similar to this: - -.. code-block:: bash - - $ python ./bestprefix.py "1024 KiB" - 1.0 MiB - -``progressbar`` Integration ---------------------------- - -Use ``bitmath.integrations.bmprogressbar.BitmathFileTransferSpeed`` as a -``progressbar`` file transfer speed widget to monitor download speeds: - -.. code-block:: python - - import requests - import progressbar - import bitmath - from bitmath.integrations.bmprogressbar import BitmathFileTransferSpeed - - FETCH = 'https://www.kernel.org/pub/linux/kernel/v3.0/patch-3.16.gz' - widgets = ['Bitmath Progress Bar Demo: ', ' ', - progressbar.Bar(marker=progressbar.RotatingMarker()), ' ', - BitmathFileTransferSpeed()] - - r = requests.get(FETCH, stream=True) - size = bitmath.Byte(int(r.headers['Content-Length'])) - pbar = progressbar.ProgressBar(widgets=widgets, maxval=int(size), - term_width=80).start() - chunk_size = 2048 - with open('/dev/null', 'wb') as fd: - for chunk in r.iter_content(chunk_size): - fd.write(chunk) - if (pbar.currval + chunk_size) < pbar.maxval: - pbar.update(pbar.currval + chunk_size) - pbar.finish() - - -If ran as a script the results would be similar to this: - -.. code-block:: bash - - $ python ./smalldl.py - Bitmath Progress Bar Demo: ||||||||||||||||||||||||||||||||||||||||| 1.58 MiB/s diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index 55da3e3..aafa356 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -57,9 +57,8 @@ Installation The easiest way to install bitmath is via ``dnf`` (or ``yum``) if you're on a Fedora/RHEL based distribution. bitmath is available in -the main Fedora repositories, as well as the EPEL6 and EPEL7 -repositories. There are now dual python2.x and python3.x releases -available. +the main Fedora repositories, as well as the latest supported EPEL +repositories. **Python 3.x**: diff --git a/docsite/source/module.rst b/docsite/source/module.rst index e7d868c..a6a4f3f 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -939,121 +939,3 @@ argument or option should be interpreted as. which does not represent a recognizable unit, the bitmath library will automatically detect this for us and signal to the argument parser that an error has occurred. - - -.. _bitmath_BitmathFileTransferSpeed: - -progressbar -=========== - -.. versionadded:: 1.2.1 - -The `progressbar module -`_ is typically -used to display the progress of a long running task, such as a file -transfer operation. The module provides widgets for custom formatting -how exactly the 'progress' is displayed. Some examples include: -overall percentage complete, estimated time until completion, and an -ASCII progress bar which fills as the operation continues. - -While :mod:`progressbar` already includes a widget suitable for -displaying `file transfer rates -`_, -this widget does not support customizing its presentation, and is -limited to only prefix units from the SI system. - - -.. class:: BitmathFileTransferSpeed([system=bitmath.NIST, [format="{value:.2f} {unit}/s"]]) - - The :class:`BitmathFileTransferSpeed` class is a more functional - replacement for the upstream `FileTransferSpeed - `_ - widget. - - While both widgets are able to calculate average transfer rates - over a period of time, the :class:`BitmathFileTransferSpeed` widget - adds new support for `NIST `_ prefix units (the - upstream widget only supports SI prefix units). - - In addition to NIST unit support, :class:`BitmathFileTransferSpeed` - enables the user to have **full control** over the look and feel of - the displayed rates. - - :param system: **Default:** :py:data:`bitmath.NIST`. The preferred - system of units for the printed rate. - :type system: One of :py:data:`bitmath.NIST` or :py:data:`bitmath.SI` - :param string format: a formatting mini-language compat formatting - string. **Default** ``{value:.2f} {unit}/s`` - (e.g., ``13.37 GiB/s``) - - .. note:: - - See :ref:`instance attributes ` for a list - of available formatting items. See the section on - :ref:`formatting bitmath instances ` for more - information on this topic. - - - Use :class:`BitmathFileTransferSpeed` exactly like the upstream - ``FileTransferSpeed`` widget (example copied and modified from the - progressbar project page): - - .. code-block:: python - :linenos: - :emphasize-lines: 2,4 - - >>> from progressbar import ProgressBar, Percentage, Bar, ETA, RotatingMarker - >>> from bitmath.integrations import BitmathFileTransferSpeed - >>> widgets = ['Something: ', Percentage(), ' ', Bar(marker=RotatingMarker()), - ... ' ', ETA(), ' ', BitmathFileTransferSpeed()] - >>> pbar = ProgressBar(widgets=widgets, maxval=10000000).start() - >>> for i in range(1000000): - ... # do something - ... pbar.update(10*i+1) - >>> pbar.finish() - - If this was ran from a script we would see output similar to the - following:: - - Something: 100% ||||||||||||||||||||||||||||||||||| Time: 0:00:01 9.27 MiB/s - - If we wanted behavior identical to :class:`FileTransferSpeed` we - would set the ``system`` parameter to :py:data:`bitmath.SI` (line - **5** below): - - .. code-block:: python - :linenos: - :emphasize-lines: 5 - - >>> import bitmath - >>> # ... - >>> widgets = ['Something: ', Percentage(), ' ', Bar(marker=RotatingMarker()), - ... ' ', ETA(), ' ', - ... BitmathFileTransferSpeed(system=bitmath.SI)] - >>> pbar = ProgressBar(widgets=widgets, maxval=10000000).start() - >>> # ... - - If this was ran from a script we would see output similar to the - following:: - - Something: 100% ||||||||||||||||||||||||||||||||||| Time: 0:00:01 9.80 MB/s - - Note how the only difference is in the displayed unit. The former - example produced a rate with a unit of ``MiB`` (a NIST unit) - whereas the latter examples unit is ``MB`` (an SI unit). - - As noted previously, :class:`BitmathFileTransferSpeed` allows for - full control over the formatting of the calculated rate of - transfer. - - For example, if we wished to see the rate printed using more - verbose language and plauralized units, we could do exactly that by - constructing our widget in the following way: - - .. code-block:: python - - BitmathFileTransferSpeed(format="{value:.2f} {unit_plural} per second") - - And if this were run from a script like the previous examples:: - - Something: 100% ||||||||||||||||||||||||||||||||||| Time: 0:00:01 9.41 MiBs per second From 74209d7960199a03fc29bb483dfb1d3947fcccfb Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 09:23:47 -0600 Subject: [PATCH 07/92] Update all python.org doc links to version 3 --- README.rst | 4 ++-- docsite/source/appendices/mixed_math.rst | 2 +- docsite/source/contributing.rst | 4 ++-- docsite/source/index.rst | 2 +- docsite/source/index.rst.in | 2 +- docsite/source/instances.rst | 4 ++-- docsite/source/module.rst | 12 ++++++------ 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index e0f1db2..7922aaa 100644 --- a/README.rst +++ b/README.rst @@ -33,7 +33,7 @@ focusing on file size unit conversion, functionality now includes: * Reading a device's storage capacity (Linux/OS X support only) * String parsing * Sorting -* `argparse `_ +* `argparse `_ integration as a custom type @@ -42,7 +42,7 @@ human readable representations of values which are suitable for use in interactive shells as well as larger scripts and applications. The format produced for these representations is customizable via the functionality included in stdlibs `string.format -`_. +`_. In discussion we will refer to the NIST units primarily. I.e., instead of "megabyte" we will refer to "mebibyte". The former is ``10^3 = diff --git a/docsite/source/appendices/mixed_math.rst b/docsite/source/appendices/mixed_math.rst index 94ad2cb..7c78cf8 100644 --- a/docsite/source/appendices/mixed_math.rst +++ b/docsite/source/appendices/mixed_math.rst @@ -20,7 +20,7 @@ When coercion happens is determined by the following conditions and rules: 1. `Precedence and Associativity of Operators - `_ + `_ in Python\ [#precedence]_ 2. Situational semantics -- some operations, though mathematically valid, do not make logical sense when applied to context. diff --git a/docsite/source/contributing.rst b/docsite/source/contributing.rst index ab23ed5..b6e9ec0 100644 --- a/docsite/source/contributing.rst +++ b/docsite/source/contributing.rst @@ -107,7 +107,7 @@ leave a comment in the pull request. Automated Tests *************** -Write `unittests `_ +Write `unittests `_ for any new functionality, `if you are up to the task`. This is not a requirement, but it does get you a lot of karma. @@ -131,7 +131,7 @@ bitmath unit tests are integrated with/depend on the following items: with GitHub to notify you if a pull-request would improve/decrease overall code test coverage. -* `unittest `_ - +* `unittest `_ - Python unit testing framework. All bitmath tests are written using this framework. diff --git a/docsite/source/index.rst b/docsite/source/index.rst index abc779d..513bf85 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -35,7 +35,7 @@ human readable representations of values which are suitable for use in interactive shells as well as larger scripts and applications. The format produced for these representations is customizable via the functionality included in stdlibs `string.format -`_. +`_. In discussion we will refer to the NIST units primarily. I.e., instead of "megabyte" we will refer to "mebibyte". The former is ``10^3 = diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index aafa356..73f1a95 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -35,7 +35,7 @@ human readable representations of values which are suitable for use in interactive shells as well as larger scripts and applications. The format produced for these representations is customizable via the functionality included in stdlibs `string.format -`_. +`_. In discussion we will refer to the NIST units primarily. I.e., instead of "megabyte" we will refer to "mebibyte". The former is ``10^3 = diff --git a/docsite/source/instances.rst b/docsite/source/instances.rst index 89e693e..09d7aab 100644 --- a/docsite/source/instances.rst +++ b/docsite/source/instances.rst @@ -28,7 +28,7 @@ bitmath objects have several instance attributes: .. py:attribute:: BitMathInstance.binary The `Python binary representation - `_ of the + `_ of the instance's value (in bits) .. code-block:: python @@ -426,7 +426,7 @@ mini-language, read on. You may be asking yourself where these ``{value:.2f}`` and ``{unit}`` strings came from. These are part of the `Format Specification Mini-Language -`_ +`_ which is part of the Python standard library. To be explicitly clear about what's going on here, let's break the first specifier (``{value:.2f}``) down into it's component parts:: diff --git a/docsite/source/module.rst b/docsite/source/module.rst index a6a4f3f..5a5d3d9 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -86,7 +86,7 @@ bitmath.listdir() .. function:: listdir(search_base[, followlinks=False[, filter='*'[, relpath=False[, bestprefix=False[, system=NIST]]]]]) This is a `generator - `_ + `_ which recurses a directory tree yielding 2-tuples of: * The absolute/relative path to a discovered file @@ -99,7 +99,7 @@ bitmath.listdir() enables directory link following :param string filter: **Default:** ``*`` (everything). A glob to filter results with. See `fnmatch - `_ + `_ for more details about *globs* :param bool relpath: **Default:** ``False``, returns the fully qualified to each discovered file. ``True`` to @@ -532,7 +532,7 @@ Context Managers **************** This section describes all of the `context managers -`_ +`_ provided by the bitmath class. .. warning:: @@ -872,18 +872,18 @@ argparse .. versionadded:: 1.2.0 The `argparse module -`_ (part of stdlib) +`_ (part of stdlib) is used to parse command line arguments. By default, parsed options and arguments are turned into strings. However, one useful feature :py:mod:`argparse` provides is the ability to `specify what datatype -`_ any given +`_ any given argument or option should be interpreted as. .. function:: BitmathType(bmstring) The :func:`BitmathType` factory creates objects that can be passed to the type argument of `ArgumentParser.add_argument() - `_. Arguments + `_. Arguments that have :func:`BitmathType` objects as their type will automatically parse the command line argument into a matching :ref:`bitmath object `. From 57f0805a8bfd4bf1b8c1a0263443daa3c96f94d7 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 09:25:14 -0600 Subject: [PATCH 08/92] Fix doc reference in argparse integration --- bitmath/integrations/bmargparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitmath/integrations/bmargparse.py b/bitmath/integrations/bmargparse.py index bd5fdb2..9e7715c 100644 --- a/bitmath/integrations/bmargparse.py +++ b/bitmath/integrations/bmargparse.py @@ -32,7 +32,7 @@ def BitmathType(bmstring): """An 'argument type' for integrations with the argparse module. For more information, see -https://docs.python.org/2/library/argparse.html#type Of particular +https://docs.python.org/3/library/argparse.html#type Of particular interest to us is this bit: ``type=`` can take any callable that takes a single string From 1a693842a4ef1a6259ee61169369e9634e3751fd Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 09:58:27 -0600 Subject: [PATCH 09/92] Remove Python 2 types, 'long' and 'unicode' Previously there was a conditional that mapped the old Python 2.x long() and unicode() types to int() and str() types. That is no longer required now that we are Python 3.x only. In addition to the extra conditional, several type checks were able to be removed. Also this commit removes the click and progressbar integrations for now. They will return later as examples in the documentation/source. Full credit will be given to the original authors. --- bitmath/__init__.py | 40 +++---------- tests/test_click_type.py | 114 -------------------------------------- tests/test_progressbar.py | 92 ------------------------------ 3 files changed, 9 insertions(+), 237 deletions(-) delete mode 100644 tests/test_click_type.py delete mode 100644 tests/test_progressbar.py diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 1bbafab..bb0c7b1 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -36,21 +36,9 @@ man 7 units (from the Linux Documentation Project 'man-pages' package) -BEFORE YOU GET HASTY WITH EXCLUDING CODE FROM COVERAGE: If you -absolutely need to skip code coverage because of a strange Python 2.x -vs 3.x thing, use the fancy environment substitution stuff from the -.coverage RC file. In review: +* If you *NEED* to skip a statement because of something untestable: -* If you *NEED* to skip a statement because of Python 2.x issues add the following:: - - # pragma: PY2X no cover - -* If you *NEED* to skip a statement because of Python 3.x issues add the following:: - - # pragma: PY3X no cover - -In this configuration, statements which are skipped in 2.x are still -covered in 3.x, and the reverse holds true for tests skipped in 3.x. + # pragma: no cover """ from __future__ import print_function @@ -81,11 +69,6 @@ 'ALL_UNIT_TYPES', 'NIST', 'NIST_PREFIXES', 'NIST_STEPS', 'SI', 'SI_PREFIXES', 'SI_STEPS'] -# Python 3.x compat -if sys.version > '3': - long = int # pragma: PY2X no cover - unicode = str # pragma: PY2X no cover - #: A list of all the valid prefix unit types. Mostly for reference, #: also used by the CLI tool as valid types ALL_UNIT_TYPES = ['Bit', 'Byte', 'kb', 'kB', 'Mb', 'MB', 'Gb', 'GB', 'Tb', @@ -174,7 +157,7 @@ class Bitmath(object): """The base class for all the other prefix classes""" # All the allowed input types - valid_types = (float, long, numbers.Integral) + valid_types = (int, float) def __init__(self, value=0, bytes=None, bits=None): """Instantiate with `value` by the unit, in plain bytes, or @@ -862,17 +845,16 @@ def __rtruediv__(self, other): # num / bm = num return other / float(self.value) - """Called to implement the built-in functions complex(), int(), -long(), and float(). Should return a value of the appropriate type. + """Called to implement the built-in functions complex(), int(), and +float(). Should return a value of the appropriate type. If one of those methods does not support the operation with the supplied arguments, it should return NotImplemented. -For bitmath purposes, these methods return the int/long/float +For bitmath purposes, these methods return the int/float equivalent of the this instances prefix Unix value. That is to say: - int(KiB(3.336)) would return 3 - - long(KiB(3.336)) would return 3L - float(KiB(3.336)) would return 3.336 """ @@ -880,10 +862,6 @@ def __int__(self): """Return this instances prefix unit as an integer""" return int(self.prefix_value) - def __long__(self): - """Return this instances prefix unit as a long integer""" - return long(self.prefix_value) # pragma: PY3X no cover - def __float__(self): """Return this instances prefix unit as a floating point number""" return float(self.prefix_value) @@ -1399,7 +1377,7 @@ def parse_string(s): the unit. """ # Strings only please - if not isinstance(s, (str, unicode)): + if not isinstance(s, (str)): raise ValueError("parse_string only accepts string inputs but a %s was given" % type(s)) @@ -1459,7 +1437,7 @@ def parse_string_unsafe(s, system=SI): * Capitalization does not matter """ - if not isinstance(s, (str, unicode)) and \ + if not isinstance(s, (str)) and \ not isinstance(s, numbers.Number): raise ValueError("parse_string_unsafe only accepts string/number inputs but a %s was given" % type(s)) @@ -1474,7 +1452,7 @@ def parse_string_unsafe(s, system=SI): return Byte(s) # Test case: a number pretending to be a string - if isinstance(s, (str, unicode)): + if isinstance(s, (str)): try: # Can we turn it directly into a number? return Byte(float(s)) diff --git a/tests/test_click_type.py b/tests/test_click_type.py deleted file mode 100644 index 4e22adf..0000000 --- a/tests/test_click_type.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -# The MIT License (MIT) -# -# Copyright © 2014 Tim Bielawa -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - -""" -Test the click 'Bitmath' type integration -""" - -from . import TestCase -import bitmath -from bitmath.integrations.bmclick import BitmathType, BITMATH -import click -from click.testing import CliRunner - - -class TestClickType(TestCase): - def setUp(self): - self.runner = CliRunner() - - def test_click_BitmathType_good_one_arg(self): - @click.command() - @click.argument('arg', type=BitmathType()) - def func(arg): - click.echo(arg) - - result = self.runner.invoke(func, ['1000EB']) - self.assertFalse(result.exception) - self.assertEqual(result.output.splitlines(), [str(bitmath.EB(1000))]) - - def test_click_BitmathType_good_one_opt(self): - @click.command() - @click.option('--opt', type=BitmathType()) - def func(opt): - click.echo(opt) - - result = self.runner.invoke(func, ['--opt', '1007TB']) - self.assertFalse(result.exception) - self.assertEqual(result.output.splitlines(), [str(bitmath.TB(1007))]) - - def test_click_BitmathType_good_two_args(self): - @click.command() - @click.argument('arg1', type=BitmathType()) - @click.argument('arg2', type=BitmathType()) - def func(arg1, arg2): - click.echo(arg1) - click.echo(arg2) - - result = self.runner.invoke(func, ['1337B', '0.001GiB']) - self.assertFalse(result.exception) - self.assertEqual(result.output.splitlines(), [str(bitmath.Byte(1337)), - str(bitmath.GiB(0.001))]) - - def test_click_BitmathType_bad_wtfareyoudoing(self): - @click.command() - @click.argument('arg', type=BitmathType()) - def func(arg): - click.echo(arg) - - result = self.runner.invoke(func, ['2098329324kdsjflksdjf']) - self.assertTrue(result.exception) - - def test_click_BitmathType_good_spaces_in_value(self): - @click.command() - @click.argument('arg1', type=BitmathType()) - @click.argument('arg2', type=BitmathType()) - def func(arg1, arg2): - click.echo(arg1) - click.echo(arg2) - - result = self.runner.invoke(func, ['100 MiB', '200 KiB']) - self.assertFalse(result.exception) - self.assertEqual(result.output.splitlines(), [str(bitmath.MiB(100)), - str(bitmath.KiB(200))]) - - def test_click_BitmathType_bad_spaces_in_value(self): - @click.command() - @click.argument('arg', type=BitmathType()) - def func(arg): - click.echo(arg) - - result = self.runner.invoke(func, ['1000', 'EB']) - self.assertTrue(result.exception) - - def test_click_BITMATH_good_one_arg(self): - @click.command() - @click.argument('arg', type=BITMATH) - def func(arg): - click.echo(arg) - - result = self.runner.invoke(func, ['1234.5 TiB']) - self.assertFalse(result.exception) - self.assertEqual(result.output.splitlines(), [str(bitmath.TiB(1234.5))]) diff --git a/tests/test_progressbar.py b/tests/test_progressbar.py deleted file mode 100644 index 4a78f3d..0000000 --- a/tests/test_progressbar.py +++ /dev/null @@ -1,92 +0,0 @@ -# -*- coding: utf-8 -*- -# The MIT License (MIT) -# -# Copyright © 2014 Tim Bielawa -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - -""" -Test the progressbar 'FileTransferSpeed' integration -""" - -from . import TestCase -import bitmath -from bitmath.integrations.bmprogressbar import BitmathFileTransferSpeed -try: - from unittest import mock -except ImportError: - import mock -import progressbar - - -class TestProgressbar(TestCase): - def setUp(self): - """Needful for the tests""" - self.widget_NIST = BitmathFileTransferSpeed(system=bitmath.NIST) - self.widget_SI = BitmathFileTransferSpeed(system=bitmath.SI) - self.widget_formatted = BitmathFileTransferSpeed(format="{value:.6f} {unit_plural} per second") - - def test_FileTransferSpeed_0_seconds(self): - """Widget renders 0 correctly when no seconds have elapsed""" - pbar = mock.MagicMock(progressbar.ProgressBar) - pbar.seconds_elapsed = 0 - pbar.currval = 0 - update = self.widget_NIST.update(pbar) - self.assertEqual(update, '0.00 Byte/s') - - def test_FileTransferSpeed_1_seconds_Bytes(self): - """Widget renders a non-zero rate after time has elapsed in Bytes""" - pbar = mock.MagicMock(progressbar.ProgressBar) - pbar.seconds_elapsed = 1 - pbar.currval = 512 - update = self.widget_NIST.update(pbar) - self.assertEqual(update, '512.00 Byte/s') - - def test_FileTransferSpeed_10_seconds_MiB(self): - """Widget renders a rate after time has elapsed in MiB/s""" - pbar = mock.MagicMock(progressbar.ProgressBar) - pbar.seconds_elapsed = 10 - # Let's say we've downloaded 512 MiB in that time (we need - # that value in Bytes, though) - pbar.currval = bitmath.MiB(512).bytes - update = self.widget_NIST.update(pbar) - # 512 MiB in 10 seconds is equal to a rate of 51.20 MiB/s - self.assertEqual(update, '51.20 MiB/s') - - def test_FileTransferSpeed_10_seconds_MB(self): - """Widget renders a rate after time has elapsed in MB/s""" - pbar = mock.MagicMock(progressbar.ProgressBar) - pbar.seconds_elapsed = 10 - # Let's say we've downloaded 512 MB in that time (we need that - # value in Bytes, though) - pbar.currval = bitmath.MB(512).bytes - update = self.widget_SI.update(pbar) - # 512 MB in 10 seconds is equal to a rate of 51.20 MB/s - self.assertEqual(update, '51.20 MB/s') - - def test_FileTransferSpeed_custom_format(self): - """Widget renders a custom format string""" - pbar = mock.MagicMock(progressbar.ProgressBar) - pbar.seconds_elapsed = 10 - pbar.currval = 10240 - update = self.widget_formatted.update(pbar) - self.assertEqual(update, '1.000000 KiBs per second') From acdc26ccee1a7da3ac26f2370edccfad813ac2af Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 10:01:10 -0600 Subject: [PATCH 10/92] Fix up CI configuration, scripts, and requirements --- .coveragerc | 6 +----- Makefile | 3 --- requirements-py3.txt | 4 +--- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.coveragerc b/.coveragerc index af76843..27b36c4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -14,11 +14,7 @@ exclude_lines = # Explicitly label a line as 'do not cover' pragma: no cover - # Stuff to skip only on Python 2.x. Insert a line like:" # pragma: - # PY2X no cover" And export the env var "PYVER" as "PY2X". To skip - # certain things on only Py3.x export PYVER as PY3X - # - # Review: http://nedbatchelder.com/code/coverage/config.html#h_Syntax if you need a refresher + # Review: https://coverage.readthedocs.io/en/stable/config.html#syntax if you need a refresher # # BTW, the exports happen in the Makefile on lines like (for the 3.x tests): # diff --git a/Makefile b/Makefile index bd181c7..1aa536e 100644 --- a/Makefile +++ b/Makefile @@ -190,9 +190,6 @@ ci-unittests: @echo "# Using python: $(shell ./bitmathenv3/bin/python --version 2>&1)" @echo "#############################################" . $(NAME)env3/bin/activate && export PYVER=PY3X && nosetests -v --with-coverage --cover-html --cover-package=bitmath tests/ - @echo "Testing argparse integration without progressbar dependency (#86)" - . $(NAME)env3/bin/activate && pip uninstall -y progressbar33 click - . $(NAME)env3/bin/activate && export PYVER=PY3X && nosetests -v --with-coverage --cover-html --cover-package=bitmath tests/test_argparse_type.py ci-list-deps: @echo "" diff --git a/requirements-py3.txt b/requirements-py3.txt index 298e2cb..9946bfe 100644 --- a/requirements-py3.txt +++ b/requirements-py3.txt @@ -1,9 +1,7 @@ python-coveralls -coverage==4.0.3 +coverage mock nose nose-cover3 pyflakes pycodestyle -progressbar33 -click From 4ab22191720db422729adecb55cf89d6774e250a Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 11:55:39 -0600 Subject: [PATCH 11/92] Github action with python matrix? --- .github/workflows/makefile.yml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index dcdffd3..fec92cd 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -10,18 +10,15 @@ jobs: build: runs-on: ubuntu-latest - + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - - uses: actions/checkout@v3 - -# - name: configure -# run: ./configure - -# - name: Install dependencies -# run: make + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} - name: Run unittests run: make ci - -# - name: Run distcheck -# run: make distcheck From cf0b84b171abceca6d63715917133f4d5fb0ee72 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 12:00:57 -0600 Subject: [PATCH 12/92] Fix stupid yaml syntax --- .github/workflows/makefile.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index fec92cd..7214d26 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -8,7 +8,6 @@ on: jobs: build: - runs-on: ubuntu-latest strategy: matrix: @@ -19,6 +18,5 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - - name: Run unittests - run: make ci + - name: Run unittests + run: make ci From c0f7d06d0339724245c48cfcae124a6557007215 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 12:02:56 -0600 Subject: [PATCH 13/92] Actions install pip and virtualenv? --- .github/workflows/makefile.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 7214d26..d51b575 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -18,5 +18,8 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip virtualenv - name: Run unittests run: make ci From 0324bfdcac7365de54411a2d0cafe73ecf8f9e73 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 12:12:36 -0600 Subject: [PATCH 14/92] Fix actions --- .github/workflows/makefile.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index d51b575..f438046 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -1,10 +1,6 @@ name: Makefile CI -on: - push: - branches: [ "master", "2023-01-26-no-more-py2" ] - pull_request: - branches: [ "master", "2023-01-26-no-more-py2" ] +on: [push, pull_request] jobs: build: @@ -20,6 +16,4 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install --upgrade pip virtualenv - - name: Run unittests - run: make ci + python -m pip install --upgrade pip virtualenv From 788ca7bcbf812653a694eaa2dd370cae332ef61f Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 13:29:02 -0600 Subject: [PATCH 15/92] GitHub Workflow fixes. And badge --- .github/workflows/makefile.yml | 19 ------------------ .github/workflows/python.yml | 36 ++++++++++++++++++++++++++++++++++ README.rst | 12 ++---------- 3 files changed, 38 insertions(+), 29 deletions(-) delete mode 100644 .github/workflows/makefile.yml create mode 100644 .github/workflows/python.yml diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml deleted file mode 100644 index f438046..0000000 --- a/.github/workflows/makefile.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Makefile CI - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip virtualenv diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000..5e5dc94 --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,36 @@ +name: Python CI + +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + python-version: ["3.7", "3.8"] + os: ["macos-latest", "ubuntu-latest"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + - name: install dependencies + run: pip install -r requirements-py3.txt + # - name: Install dependencies + # run: | + # python -m pip install --upgrade pip python-coveralls coverage mock nose pyflakes pycodestyle + + - name: Ensure that code is poppin fresh (Check Code Style) + run: | + pycodestyle -v --ignore=E501,E722 bitmath/__init__.py tests/*.py + - name: Flakey or Crunchy (Run PyFlakes) + run: | + pyflakes bitmath/__init__.py tests/*.py + - name: Verify Test Case Names Unique + run: | + ./tests/test_unique_testcase_names.sh + - name: Run Unit Tests + run: | + nosetests -v tests/ diff --git a/README.rst b/README.rst index 7922aaa..afca7f0 100644 --- a/README.rst +++ b/README.rst @@ -1,14 +1,6 @@ -.. image:: https://api.travis-ci.org/tbielawa/bitmath.png - :target: https://travis-ci.org/tbielawa/bitmath/ - :align: right - :height: 19 - :width: 77 +.. image:: https://github.com/tbielawa/bitmath/actions/workflows/python.yml/badge.svg + :target: https://github.com/tbielawa/bitmath/actions/workflows/python.yml -.. image:: https://coveralls.io/repos/tbielawa/bitmath/badge.png?branch=master - :target: https://coveralls.io/r/tbielawa/bitmath?branch=master - :align: right - :height: 19 - :width: 77 .. image:: https://readthedocs.org/projects/bitmath/badge/?version=latest :target: http://bitmath.rtfd.org/ From 852c149789fd80cb66aa284137ba951b61efea64 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 16:25:31 -0600 Subject: [PATCH 16/92] No more old plain requirements file --- requirements.txt | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index ca41d5b..0000000 --- a/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -python-coveralls -coverage==4.0.3 -mock -nose -pyflakes -pycodestyle -progressbar231 -click From 0eec71f42e8a244838b60fe5f767c0fd6a05f0b5 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 16:34:52 -0600 Subject: [PATCH 17/92] Update contact page. No more IRC or twitter for now. --- docsite/source/contact.rst | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/docsite/source/contact.rst b/docsite/source/contact.rst index 9b3ec87..9d0b3ac 100644 --- a/docsite/source/contact.rst +++ b/docsite/source/contact.rst @@ -20,12 +20,6 @@ ahead: * `GitHub: tbielawa `_ -**Tweet-Tweet** - I have been known to tweet from time to time. - - * `@tbielawa `_ - - **Bugs/Issues/Requests** All contributions related directly to the bitmath project, i.e. bug reports or feature requests, should be posted to the project issue @@ -34,14 +28,6 @@ ahead: * :ref:`Contributing ` -**Saying hello** - I'm on the `freenode `_ IRC network Monday - through Friday, from around 9am EST through 5pm EST. - - * Issue a ``/who tbielawa*`` command to the server, a handle with the - netmask ``~tbielawa@redhat/tbielawa`` will appear for you to - ``/query`` if I'm online. - **E-Mail** If you want to contact me directly, `clone the project `_ and look at any of `my From 88fba1faf8f672f3d91cc4108a260c2d6e6af6f3 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 18:19:24 -0600 Subject: [PATCH 18/92] Unbreak requirements file maybe? --- .github/workflows/python.yml | 4 ++-- requirements-py3.txt => requirements.txt | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename requirements-py3.txt => requirements.txt (100%) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 5e5dc94..2d390a2 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -16,8 +16,8 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: 'pip' - - name: install dependencies - run: pip install -r requirements-py3.txt + # - name: install dependencies + # run: pip install -r requirements-py3.txt # - name: Install dependencies # run: | # python -m pip install --upgrade pip python-coveralls coverage mock nose pyflakes pycodestyle diff --git a/requirements-py3.txt b/requirements.txt similarity index 100% rename from requirements-py3.txt rename to requirements.txt From fc7c2ed6dbbb9da50cc9039c6788039b7638cfe1 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 18:22:19 -0600 Subject: [PATCH 19/92] Unbreak requirements file maybe? What is using normal reqs file? --- .github/workflows/python.yml | 4 ++-- requirements-py3.txt | 7 +++++++ requirements.txt | 7 ------- 3 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 requirements-py3.txt diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 2d390a2..5e5dc94 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -16,8 +16,8 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: 'pip' - # - name: install dependencies - # run: pip install -r requirements-py3.txt + - name: install dependencies + run: pip install -r requirements-py3.txt # - name: Install dependencies # run: | # python -m pip install --upgrade pip python-coveralls coverage mock nose pyflakes pycodestyle diff --git a/requirements-py3.txt b/requirements-py3.txt new file mode 100644 index 0000000..9946bfe --- /dev/null +++ b/requirements-py3.txt @@ -0,0 +1,7 @@ +python-coveralls +coverage +mock +nose +nose-cover3 +pyflakes +pycodestyle diff --git a/requirements.txt b/requirements.txt index 9946bfe..e69de29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +0,0 @@ -python-coveralls -coverage -mock -nose -nose-cover3 -pyflakes -pycodestyle From 8a2827542163b4d47ead664ba42e6c41b72fbabe Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sat, 4 Feb 2023 18:29:25 -0600 Subject: [PATCH 20/92] Maybe simpler now --- .github/workflows/python.yml | 19 +++++++++++-------- requirements-py3.txt | 7 ------- requirements.txt | 7 +++++++ 3 files changed, 18 insertions(+), 15 deletions(-) delete mode 100644 requirements-py3.txt diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 5e5dc94..9acf501 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -10,27 +10,30 @@ jobs: os: ["macos-latest", "ubuntu-latest"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - name: "GitHub Checks it out :sunglasses-face:" + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} cache: 'pip' + + - name: Verify Test Case Names Unique + run: | + ./tests/test_unique_testcase_names.sh + - name: install dependencies - run: pip install -r requirements-py3.txt - # - name: Install dependencies - # run: | - # python -m pip install --upgrade pip python-coveralls coverage mock nose pyflakes pycodestyle + run: pip install -r requirements.txt - name: Ensure that code is poppin fresh (Check Code Style) run: | pycodestyle -v --ignore=E501,E722 bitmath/__init__.py tests/*.py + - name: Flakey or Crunchy (Run PyFlakes) run: | pyflakes bitmath/__init__.py tests/*.py - - name: Verify Test Case Names Unique - run: | - ./tests/test_unique_testcase_names.sh + - name: Run Unit Tests run: | nosetests -v tests/ diff --git a/requirements-py3.txt b/requirements-py3.txt deleted file mode 100644 index 9946bfe..0000000 --- a/requirements-py3.txt +++ /dev/null @@ -1,7 +0,0 @@ -python-coveralls -coverage -mock -nose -nose-cover3 -pyflakes -pycodestyle diff --git a/requirements.txt b/requirements.txt index e69de29..9946bfe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,7 @@ +python-coveralls +coverage +mock +nose +nose-cover3 +pyflakes +pycodestyle From c6c5724c9d6a86db726041e1b4122c57ddd9cbb0 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sun, 5 Feb 2023 15:09:34 -0600 Subject: [PATCH 21/92] Combine gh action steps, switch to pytest --- .github/workflows/python.yml | 15 ++++++++------- bitmath/__init__.py | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 9acf501..4dc43e4 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -6,7 +6,7 @@ jobs: build: strategy: matrix: - python-version: ["3.7", "3.8"] + python-version: ["3.7", "3.8", "3.9", "3.10"] os: ["macos-latest", "ubuntu-latest"] runs-on: ${{ matrix.os }} steps: @@ -24,16 +24,17 @@ jobs: ./tests/test_unique_testcase_names.sh - name: install dependencies - run: pip install -r requirements.txt - - - name: Ensure that code is poppin fresh (Check Code Style) run: | - pycodestyle -v --ignore=E501,E722 bitmath/__init__.py tests/*.py + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest + pip install pytest-cov - - name: Flakey or Crunchy (Run PyFlakes) + - name: Pre-Tests code smell validation run: | + pycodestyle -v --ignore=E501,E722 bitmath/__init__.py tests/*.py pyflakes bitmath/__init__.py tests/*.py - name: Run Unit Tests run: | - nosetests -v tests/ + pytest -v --cov=bitmath --cov-report term-missing --cov-report term:skip-covered tests diff --git a/bitmath/__init__.py b/bitmath/__init__.py index bb0c7b1..d436401 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -1364,7 +1364,7 @@ def listdir(search_base, followlinks=False, filter='*', yield (_return_path, getsize(_path, bestprefix=bestprefix, system=system)) else: if os.path.isdir(_path) or os.path.islink(_path): - pass + pass # pragma: no cover else: yield (_return_path, getsize(_path, bestprefix=bestprefix, system=system)) From 0ab40b6b60e31308651131c2a3687dcda27b3251 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sun, 5 Feb 2023 15:14:17 -0600 Subject: [PATCH 22/92] Get rid of old deps from requirements --- .github/workflows/python.yml | 2 -- requirements.txt | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 4dc43e4..10a7d8e 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -27,8 +27,6 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install pytest - pip install pytest-cov - name: Pre-Tests code smell validation run: | diff --git a/requirements.txt b/requirements.txt index 9946bfe..b1581d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,5 @@ -python-coveralls -coverage mock -nose -nose-cover3 pyflakes pycodestyle +pytest +pytest-cov From 69eeae3edc80836cbdb36c461d49f24ea639ff1b Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sun, 5 Feb 2023 15:31:24 -0600 Subject: [PATCH 23/92] Dedupe Makefile targets. Align with gh actions script --- Makefile | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index 1aa536e..250b371 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,6 @@ docsite/source/index.rst: docsite/source/index.rst.in README.rst VERSION @echo "#############################################" awk 'BEGIN{P=0} /^Examples/ { P=1} { if (P == 1) print $$0 }' README.rst | cat $< - > $@ - # Regenerate %.1.asciidoc if %.1.asciidoc.in has been modified more # recently than %.1.asciidoc. %.1.asciidoc: %.1.asciidoc.in VERSION @@ -101,29 +100,10 @@ tag: tests: uniquetestnames unittests pycodestyle pyflakes : -unittests: - @echo "#############################################" - @echo "# Running Unit Tests" - @echo "#############################################" - nosetests -v --with-coverage --cover-html --cover-package=bitmath --cover-min-percentage=90 - clean: @find . -type f -regex ".*\.py[co]$$" -delete @find . -type f \( -name "*~" -or -name "#*" \) -delete - @rm -fR build cover dist rpm-build MANIFEST htmlcov .coverage bitmathenv bitmathenv2 bitmathenv3 docsite/build/html/ docsite/build/doctrees/ bitmath.egg-info - -pycodestyle: - @echo "#############################################" - @echo "# Running PEP8 Compliance Tests" - @echo "#############################################" - pycodestyle -v --ignore=E501,E722 bitmath/__init__.py tests/*.py - -pyflakes: - @echo "#############################################" - @echo "# Running Pyflakes Sanity Tests" - @echo "# Note: most import errors may be ignored" - @echo "#############################################" - -pyflakes bitmath/__init__.py tests/*.py + @rm -fR build cover dist rpm-build MANIFEST htmlcov .coverage bitmathenv3 docsite/build/html/ docsite/build/doctrees/ bitmath.egg-info uniquetestnames: @echo "#############################################" @@ -181,7 +161,7 @@ virtualenv: @echo "# Creating a virtualenv" @echo "#############################################" virtualenv $(NAME)env3 --python=python3 - . $(NAME)env3/bin/activate && pip install -r requirements-py3.txt + . $(NAME)env3/bin/activate && pip install -r requirements.txt ci-unittests: @echo "" @@ -189,7 +169,7 @@ ci-unittests: @echo "# Running Unit Tests in virtualenv" @echo "# Using python: $(shell ./bitmathenv3/bin/python --version 2>&1)" @echo "#############################################" - . $(NAME)env3/bin/activate && export PYVER=PY3X && nosetests -v --with-coverage --cover-html --cover-package=bitmath tests/ + . $(NAME)env3/bin/activate && pytest -v --cov=bitmath --cov-report term-missing --cov-report term:skip-covered tests ci-list-deps: @echo "" From 5b871cfb9d7d7a01f744d2088c03330e667ed1cc Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sun, 5 Feb 2023 15:44:37 -0600 Subject: [PATCH 24/92] update readme/index --- README.rst | 11 +++++------ docsite/source/index.rst | 6 +++--- docsite/source/index.rst.in | 6 +++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index afca7f0..ac17a44 100644 --- a/README.rst +++ b/README.rst @@ -48,9 +48,9 @@ most of the time what you're really seeing are the base-2 sizes/rates. And did we mention there's almost 200 unittests? `Check them out for yourself `_. -Running the tests should be as simple as calling the ``ci-all`` target -in the Makefile: ``make ci-all``. Please file a bug report if you run -into issues. +Running the tests should be as simple as calling the ``ci`` target in +the Makefile: ``make ci``. Please file a bug report if you run into +issues. @@ -61,8 +61,8 @@ Installation The easiest way to install bitmath is via ``dnf`` (or ``yum``) if you're on a Fedora/RHEL based distribution. bitmath is available in the main Fedora repositories, as well as EPEL Repositories. As of 2023 -bitmath is only developed, tested, and supported for currently -supported Python releases. +bitmath is only developed, tested, and supported for `currently +supported `_ Python releases. .. code-block:: bash @@ -70,7 +70,6 @@ supported Python releases. $ sudo dnf install python3-bitmath - **PyPi**: You could also install bitmath from `PyPi diff --git a/docsite/source/index.rst b/docsite/source/index.rst index 513bf85..8b83d67 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -57,10 +57,10 @@ Installation The easiest way to install bitmath is via ``dnf`` (or ``yum``) if you're on a Fedora/RHEL based distribution. bitmath is available in -the main Fedora repositories, as well as the latest supported EPEL -repositories. +the main Fedora repositories, as well as EPEL Repositories. As of 2023 +bitmath is only developed, tested, and supported for `currently +supported `_ Python releases. -**Python 3.x**: .. code-block:: bash diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index 73f1a95..454c49a 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -57,10 +57,10 @@ Installation The easiest way to install bitmath is via ``dnf`` (or ``yum``) if you're on a Fedora/RHEL based distribution. bitmath is available in -the main Fedora repositories, as well as the latest supported EPEL -repositories. +the main Fedora repositories, as well as EPEL Repositories. As of 2023 +bitmath is only developed, tested, and supported for `currently +supported `_ Python releases. -**Python 3.x**: .. code-block:: bash From e817bcd2047adf95e7427a6081abe323470f2c7e Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sun, 5 Feb 2023 17:25:50 -0600 Subject: [PATCH 25/92] Seeing what's up with all this hatch packaging stuff --- hatch.toml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 27 +++++++++++++-------------- 2 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 hatch.toml diff --git a/hatch.toml b/hatch.toml new file mode 100644 index 0000000..e3d185a --- /dev/null +++ b/hatch.toml @@ -0,0 +1,52 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[build.targets.wheel] +packages = ["bitmath"] + + +[project] +name = "bitmath" +version = "2.0.0" +authors = [ + { name="Tim Bielawa", email="timbielawa@gmail.com" }, + ] + description = "Pythonic module for representing and manipulating file sizes with different prefix notations (file size unit conversion)" + readme = "README.rst" + requires-python = ">=3.7" + classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'Intended Audience :: Telecommunications Industry', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', + 'Topic :: Communications :: File Sharing', + 'Topic :: Internet', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Information Analysis', + 'Topic :: Scientific/Engineering :: Mathematics', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Software Development :: Testing', + 'Topic :: Software Development :: Testing :: Acceptance', + 'Topic :: Software Development :: Testing :: Unit', + 'Topic :: System :: Filesystems', + 'Topic :: System :: Systems Administration', + 'Topic :: Text Processing :: Filters', + 'Topic :: Utilities' + ] + +[project.urls] +"Homepage" = "https://bitmath.readthedocs.io/en/latest/index.html" +"Bug Tracker" = "https://github.com/tbielawa/bitmath/issues" +"Git Repo" = "https://github.com/tbielawa/bitmath" + +[publish.index] +disable = true \ No newline at end of file diff --git a/setup.py b/setup.py index d2f9f35..9aab738 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ setup( name='bitmath', - version='1.4.0.1', + version='2.0.0.1', description='Pythonic module for representing and manipulating file sizes with different prefix notations (file size unit conversion)', long_description=pypi_notice, maintainer='Tim Bielawa', @@ -52,23 +52,22 @@ 'Intended Audience :: System Administrators', 'Intended Audience :: Telecommunications Industry', 'License :: OSI Approved :: MIT License', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: POSIX :: Linux', - 'Operating System :: POSIX', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.1', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', + 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', + 'Topic :: Communications :: File Sharing', + 'Topic :: Internet', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Information Analysis', 'Topic :: Scientific/Engineering :: Mathematics', - 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: User Interfaces', - 'Topic :: Software Development :: Widget Sets', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Software Development :: Testing', + 'Topic :: Software Development :: Testing :: Acceptance', + 'Topic :: Software Development :: Testing :: Unit', 'Topic :: System :: Filesystems', + 'Topic :: System :: Systems Administration', 'Topic :: Text Processing :: Filters', 'Topic :: Utilities' ], From 3e3fed3fa59eed31cd28c69c774c9d4042d1b93d Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sun, 5 Feb 2023 17:52:13 -0600 Subject: [PATCH 26/92] Fix print statement to print function Closes #98 --- README.rst | 32 ++++----- bitmath/__init__.py | 6 +- docsite/source/appendices/mixed_math.rst | 4 +- docsite/source/classes.rst | 4 +- docsite/source/index.rst | 32 ++++----- docsite/source/instances.rst | 44 ++++++------- docsite/source/module.rst | 84 ++++++++++++------------ docsite/source/real_life_examples.rst | 26 ++++---- docsite/source/simple_examples.rst | 8 +-- tests/test_file_size.py | 2 +- 10 files changed, 120 insertions(+), 122 deletions(-) diff --git a/README.rst b/README.rst index ac17a44..806b423 100644 --- a/README.rst +++ b/README.rst @@ -175,12 +175,12 @@ Arithmetic >>> import bitmath >>> log_size = bitmath.kB(137.4) >>> log_zipped_size = bitmath.Byte(987) - >>> print "Compression saved %s space" % (log_size - log_zipped_size) + >>> print("Compression saved %s space" % (log_size - log_zipped_size)) Compression saved 136.413kB space >>> thumb_drive = bitmath.GiB(12) >>> song_size = bitmath.MiB(5) >>> songs_per_drive = thumb_drive / song_size - >>> print songs_per_drive + >>> print(songs_per_drive) 2457.6 @@ -193,7 +193,7 @@ File size unit conversion: >>> from bitmath import * >>> dvd_size = GiB(4.7) - >>> print "DVD Size in MiB: %s" % dvd_size.to_MiB() + >>> print("DVD Size in MiB: %s" % dvd_size.to_MiB()) DVD Size in MiB: 4812.8 MiB @@ -205,9 +205,9 @@ Select a human-readable unit >>> small_number = kB(100) >>> ugly_number = small_number.to_TiB() - >>> print ugly_number + >>> print(ugly_number) 9.09494701773e-08 TiB - >>> print ugly_number.best_prefix() + >>> print(ugly_number.best_prefix()) 97.65625 KiB @@ -235,7 +235,7 @@ Sorting KiB(2326.0), KiB(4003.0), KiB(48.0), KiB(1770.0), KiB(7892.0), KiB(4190.0)] - >>> print sorted(sizes) + >>> print(sorted(sizes)) [KiB(48.0), KiB(1441.0), KiB(1770.0), KiB(2126.0), KiB(2178.0), KiB(2326.0), KiB(4003.0), KiB(4190.0), KiB(7337.0), KiB(7892.0)] @@ -261,7 +261,7 @@ Example: ...: The instance is {bits} bits large ...: bytes/bits without trailing decimals: {bytes:.0f}/{bits:.0f}""" % str(ugly_number) - >>> print ugly_number.format(longer_format) + >>> print(ugly_number.format(longer_format)) Formatting attributes for 5.96046447754 MiB This instances prefix unit is MiB, which is a NIST type unit The unit value is 5.96046447754 @@ -280,7 +280,7 @@ Utility Functions .. code-block:: python - >>> print bitmath.getsize('python-bitmath.spec') + >>> print(bitmath.getsize('python-bitmath.spec')) 3.7060546875 KiB **bitmath.parse_string()** @@ -291,9 +291,9 @@ Parse a string with standard units: >>> import bitmath >>> a_dvd = bitmath.parse_string("4.7 GiB") - >>> print type(a_dvd) + >>> print(type(a_dvd)) - >>> print a_dvd + >>> print(a_dvd) 4.7 GiB **bitmath.parse_string_unsafe()** @@ -304,7 +304,7 @@ Parse a string with ambiguous units: >>> import bitmath >>> a_gig = bitmath.parse_string_unsafe("1gb") - >>> print type(a_gig) + >>> print(type(a_gig)) >>> a_gig == bitmath.GB(1) True @@ -319,7 +319,7 @@ Parse a string with ambiguous units: >>> import bitmath >>> with open('/dev/sda') as fp: ... root_disk = bitmath.query_device_capacity(fp) - ... print root_disk.best_prefix() + ... print(root_disk.best_prefix()) ... 238.474937439 GiB @@ -328,7 +328,7 @@ Parse a string with ambiguous units: .. code-block:: python >>> for i in bitmath.listdir('./tests/', followlinks=True, relpath=True, bestprefix=True): - ... print i + ... print(i) ... ('tests/test_file_size.py', KiB(9.2900390625)) ('tests/test_basic_math.py', KiB(7.1767578125)) @@ -364,7 +364,7 @@ Formatting >>> with bitmath.format(fmt_str="[{value:.3f}@{unit}]"): ... for i in bitmath.listdir('./tests/', followlinks=True, relpath=True, bestprefix=True): - ... print i[1] + ... print(i[1]) ... [9.290@KiB] [7.177@KiB] @@ -409,9 +409,9 @@ argparser argument type: required=True) results = parser.parse_args() - print "Parsed in: {PARSED}; Which looks like {TOKIB} as a Kibibit".format( + print("Parsed in: {PARSED}; Which looks like {TOKIB} as a Kibibit".format( PARSED=results.block_size, - TOKIB=results.block_size.Kib) + TOKIB=results.block_size.Kib)) If ran as a script the results would be similar to this: diff --git a/bitmath/__init__.py b/bitmath/__init__.py index d436401..a4c5e38 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -41,8 +41,6 @@ # pragma: no cover """ -from __future__ import print_function - import argparse import contextlib import fnmatch @@ -370,7 +368,7 @@ def from_other(cls, item): >>> import bitmath >>> kib = bitmath.KiB.from_other(bitmath.MiB(1)) - >>> print kib + >>> print(kib) KiB(1024.0) """ @@ -1228,7 +1226,7 @@ def query_device_capacity(device_fd): # Confirm this character is right by running (on Linux): # # >>> import struct - # >>> print 8 == struct.calcsize('L') + # >>> print(8 == struct.calcsize('L')) # # The result should be true as long as your kernel # headers define BLKGETSIZE64 as a u64 type (please diff --git a/docsite/source/appendices/mixed_math.rst b/docsite/source/appendices/mixed_math.rst index 7c78cf8..a7e8cec 100644 --- a/docsite/source/appendices/mixed_math.rst +++ b/docsite/source/appendices/mixed_math.rst @@ -160,7 +160,7 @@ Let's look at an example of this in action: In [9]: bm = PiB(24) - In [10]: print num + bm + In [10]: print(num + bm) 66.0 Equivalently, divorcing the bitmath instance from it's value (this is @@ -170,7 +170,7 @@ coercion): In [12]: bm_value = bm.value - In [13]: print num + bm_value + In [13]: print(num + bm_value) 66.0 What it all boils down to is this: if we don't provide a unit then diff --git a/docsite/source/classes.rst b/docsite/source/classes.rst index 77cc762..027d59d 100644 --- a/docsite/source/classes.rst +++ b/docsite/source/classes.rst @@ -201,7 +201,7 @@ bitmath class, however that is a valid use case. >>> a_mebibyte == a_mebibyte_sized_kibibyte True - >>> print a_mebibyte, a_mebibyte_sized_kibibyte + >>> print(a_mebibyte, a_mebibyte_sized_kibibyte) 1.0 MiB 1024.0 KiB Or, using the :py:meth:`BitMathClass.from_other` class method: @@ -216,5 +216,5 @@ bitmath class, however that is a valid use case. >>> a_mebibyte == a_big_kibibyte True - >>> print a_mebibyte, a_big_kibibyte + >>> print(a_mebibyte, a_big_kibibyte) 1.0 MiB 1024.0 KiB diff --git a/docsite/source/index.rst b/docsite/source/index.rst index 8b83d67..a784141 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -136,12 +136,12 @@ Arithmetic >>> import bitmath >>> log_size = bitmath.kB(137.4) >>> log_zipped_size = bitmath.Byte(987) - >>> print "Compression saved %s space" % (log_size - log_zipped_size) + >>> print("Compression saved %s space" % (log_size - log_zipped_size)) Compression saved 136.413kB space >>> thumb_drive = bitmath.GiB(12) >>> song_size = bitmath.MiB(5) >>> songs_per_drive = thumb_drive / song_size - >>> print songs_per_drive + >>> print(songs_per_drive) 2457.6 @@ -154,7 +154,7 @@ File size unit conversion: >>> from bitmath import * >>> dvd_size = GiB(4.7) - >>> print "DVD Size in MiB: %s" % dvd_size.to_MiB() + >>> print("DVD Size in MiB: %s" % dvd_size.to_MiB()) DVD Size in MiB: 4812.8 MiB @@ -166,9 +166,9 @@ Select a human-readable unit >>> small_number = kB(100) >>> ugly_number = small_number.to_TiB() - >>> print ugly_number + >>> print(ugly_number) 9.09494701773e-08 TiB - >>> print ugly_number.best_prefix() + >>> print(ugly_number.best_prefix()) 97.65625 KiB @@ -196,7 +196,7 @@ Sorting KiB(2326.0), KiB(4003.0), KiB(48.0), KiB(1770.0), KiB(7892.0), KiB(4190.0)] - >>> print sorted(sizes) + >>> print(sorted(sizes)) [KiB(48.0), KiB(1441.0), KiB(1770.0), KiB(2126.0), KiB(2178.0), KiB(2326.0), KiB(4003.0), KiB(4190.0), KiB(7337.0), KiB(7892.0)] @@ -222,7 +222,7 @@ Example: ...: The instance is {bits} bits large ...: bytes/bits without trailing decimals: {bytes:.0f}/{bits:.0f}""" % str(ugly_number) - >>> print ugly_number.format(longer_format) + >>> print(ugly_number.format(longer_format)) Formatting attributes for 5.96046447754 MiB This instances prefix unit is MiB, which is a NIST type unit The unit value is 5.96046447754 @@ -241,7 +241,7 @@ Utility Functions .. code-block:: python - >>> print bitmath.getsize('python-bitmath.spec') + >>> print(bitmath.getsize('python-bitmath.spec')) 3.7060546875 KiB **bitmath.parse_string()** @@ -252,9 +252,9 @@ Parse a string with standard units: >>> import bitmath >>> a_dvd = bitmath.parse_string("4.7 GiB") - >>> print type(a_dvd) + >>> print(type(a_dvd)) - >>> print a_dvd + >>> print(a_dvd) 4.7 GiB **bitmath.parse_string_unsafe()** @@ -265,7 +265,7 @@ Parse a string with ambiguous units: >>> import bitmath >>> a_gig = bitmath.parse_string_unsafe("1gb") - >>> print type(a_gig) + >>> print(type(a_gig)) >>> a_gig == bitmath.GB(1) True @@ -280,7 +280,7 @@ Parse a string with ambiguous units: >>> import bitmath >>> with open('/dev/sda') as fp: ... root_disk = bitmath.query_device_capacity(fp) - ... print root_disk.best_prefix() + ... print(root_disk.best_prefix()) ... 238.474937439 GiB @@ -289,7 +289,7 @@ Parse a string with ambiguous units: .. code-block:: python >>> for i in bitmath.listdir('./tests/', followlinks=True, relpath=True, bestprefix=True): - ... print i + ... print(i) ... ('tests/test_file_size.py', KiB(9.2900390625)) ('tests/test_basic_math.py', KiB(7.1767578125)) @@ -325,7 +325,7 @@ Formatting >>> with bitmath.format(fmt_str="[{value:.3f}@{unit}]"): ... for i in bitmath.listdir('./tests/', followlinks=True, relpath=True, bestprefix=True): - ... print i[1] + ... print(i[1]) ... [9.290@KiB] [7.177@KiB] @@ -370,9 +370,9 @@ argparser argument type: required=True) results = parser.parse_args() - print "Parsed in: {PARSED}; Which looks like {TOKIB} as a Kibibit".format( + print("Parsed in: {PARSED}; Which looks like {TOKIB} as a Kibibit".format( PARSED=results.block_size, - TOKIB=results.block_size.Kib) + TOKIB=results.block_size.Kib)) If ran as a script the results would be similar to this: diff --git a/docsite/source/instances.rst b/docsite/source/instances.rst index 09d7aab..1b798d7 100644 --- a/docsite/source/instances.rst +++ b/docsite/source/instances.rst @@ -22,7 +22,7 @@ bitmath objects have several instance attributes: .. code-block:: python >>> b = bitmath.Byte(1337) - >>> print b.base + >>> print(b.base) 2 .. py:attribute:: BitMathInstance.binary @@ -34,7 +34,7 @@ bitmath objects have several instance attributes: .. code-block:: python >>> b = bitmath.Byte(1337) - >>> print b.binary + >>> print(b.binary) 0b10100111001000 .. py:attribute:: BitMathInstance.bin @@ -48,7 +48,7 @@ bitmath objects have several instance attributes: .. code-block:: python >>> b = bitmath.Byte(1337) - >>> print b.bits + >>> print(b.bits) 10696.0 .. py:attribute:: BitMathInstance.bytes @@ -58,7 +58,7 @@ bitmath objects have several instance attributes: .. code-block:: python >>> b = bitmath.Byte(1337) - >>> print b.bytes + >>> print(b.bytes) 1337 .. py:attribute:: BitMathInstance.power @@ -68,7 +68,7 @@ bitmath objects have several instance attributes: .. code-block:: python >>> b = bitmath.Byte(1337) - >>> print b.power + >>> print(b.power) 0 .. py:attribute:: BitMathInstance.system @@ -78,7 +78,7 @@ bitmath objects have several instance attributes: .. code-block:: python >>> b = bitmath.Byte(1337) - >>> print b.system + >>> print(b.system) NIST .. py:attribute:: BitMathInstance.value @@ -88,7 +88,7 @@ bitmath objects have several instance attributes: .. code-block:: python >>> b = bitmath.Byte(1337) - >>> print b.value + >>> print(b.value) 1337.0 .. py:attribute:: BitMathInstance.unit @@ -98,7 +98,7 @@ bitmath objects have several instance attributes: .. code-block:: python >>> b = bitmath.Byte(1337) - >>> print b.unit + >>> print(b.unit) Byte .. py:attribute:: BitMathInstance.unit_plural @@ -108,7 +108,7 @@ bitmath objects have several instance attributes: .. code-block:: python >>> b = bitmath.Byte(1337) - >>> print b.unit_plural + >>> print(b.unit_plural) Bytes .. py:attribute:: BitMathInstance.unit_singular @@ -119,7 +119,7 @@ bitmath objects have several instance attributes: .. code-block:: python >>> b = bitmath.Byte(1337) - >>> print b.unit_singular + >>> print(b.unit_singular) Byte @@ -136,8 +136,8 @@ and what you can expect their printed representation to look like: :linenos: >>> dvd_capacity = GB(4.7) - >>> print "Capacity in bits: %s\nbytes: %s\n" % \ - (dvd_capacity.bits, dvd_capacity.bytes) + >>> print("Capacity in bits: %s\nbytes: %s\n" % \ + (dvd_capacity.bits, dvd_capacity.bytes)) Capacity in bits: 37600000000.0 bytes: 4700000000.0 @@ -181,12 +181,12 @@ classes. You can even ``to_THING()`` an instance into itself again: True >>> another_mib = one_mib.to_MiB() - >>> print one_mib, one_mib_in_kb, another_mib + >>> print(one_mib, one_mib_in_kb, another_mib) 1.0 MiB 8388.608 kb 1.0 MiB >>> six_TB = TB(6) >>> six_TB_in_bits = six_TB.to_Bit() - >>> print six_TB, six_TB_in_bits + >>> print(six_TB, six_TB_in_bits) 6.0 TB 4.8e+13 Bit >>> six_TB == six_TB_in_bits @@ -239,7 +239,7 @@ even easier to read. >>> for _rate in tx_rate(): - ... print "Rate: %s/second" % Bit(_rate) + ... print("Rate: %s/second" % Bit(_rate)) ... time.sleep(1) Rate: 100.0 Bit/sec @@ -258,7 +258,7 @@ And now using a custom formatting definition: .. code-block:: python >>> for _rate in tx_rate(): - ... print Bit(_rate).best_prefix().format("Rate: {value:.3f} {unit}/sec") + ... print(Bit(_rate).best_prefix().format("Rate: {value:.3f} {unit}/sec")) ... time.sleep(1) Rate: 12.500 Byte/sec @@ -293,7 +293,7 @@ bitmath instances come with a verbose built-in string representation: .. code-block:: python >>> leet_bits = Bit(1337) - >>> print leet_bits + >>> print(leet_bits) 1337.0 Bit However, for instances which aren't whole numbers (as in ``MiB(1/3.0) @@ -327,7 +327,7 @@ First, for reference, the default formatting: .. code-block:: python >>> ugly_number = MB(50).to_MiB() / 8.0 - >>> print ugly_number + >>> print(ugly_number) 5.96046447754 MiB Now, let's use the :py:meth:`format` method to limit that to two @@ -335,7 +335,7 @@ digits of precision: .. code-block:: python - >>> print ugly_number.format("{value:.2f}{unit}") + >>> print(ugly_number.format("{value:.2f}{unit}")) 5.96 MiB By changing the **2** character, you increase or decrease the @@ -364,7 +364,7 @@ of how an attribute may be referenced multiple times. ...: The instance is {bits} bits large ...: bytes/bits without trailing decimals: {bytes:.0f}/{bits:.0f}""" % str(ugly_number) - >>> print ugly_number.format(longer_format) + >>> print(ugly_number.format(longer_format)) Formatting attributes for 5.96046447754 MiB This instances prefix unit is MiB, which is a NIST type unit The unit value is 5.96046447754 @@ -401,11 +401,11 @@ classes. Under the covers these properties call ``to_THING``. >>> one_mib == one_mib.kb True - >>> print one_mib, one_mib.kb, one_mib.MiB + >>> print(one_mib, one_mib.kb, one_mib.MiB) 1.0 MiB 8388.608 kb 1.0 MiB >>> six_TB = TB(6) - >>> print six_TB, six_TB.Bit + >>> print(six_TB, six_TB.Bit) 6.0 TB 4.8e+13 Bit >>> six_TB == six_TB.Bit diff --git a/docsite/source/module.rst b/docsite/source/module.rst index 5a5d3d9..7033999 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -46,7 +46,7 @@ bitmath.getsize() .. code-block:: python >>> import bitmath - >>> print bitmath.getsize('./bitmath/__init__.py') + >>> print(bitmath.getsize('./bitmath/__init__.py')) 33.3583984375 KiB Let's say we want to see the results in bytes. We can do this by @@ -55,7 +55,7 @@ bitmath.getsize() .. code-block:: python >>> import bitmath - >>> print bitmath.getsize('./bitmath/__init__.py', bestprefix=False) + >>> print(bitmath.getsize('./bitmath/__init__.py', bestprefix=False)) 34159.0 Byte Recall, the default for representation is with the best @@ -67,11 +67,11 @@ bitmath.getsize() :linenos: :emphasize-lines: 1-4 - >>> print bitmath.getsize('./bitmath/__init__.py') + >>> print(bitmath.getsize('./bitmath/__init__.py')) 33.3583984375 KiB - >>> print bitmath.getsize('./bitmath/__init__.py', system=bitmath.NIST) + >>> print(bitmath.getsize('./bitmath/__init__.py', system=bitmath.NIST)) 33.3583984375 KiB - >>> print bitmath.getsize('./bitmath/__init__.py', system=bitmath.SI) + >>> print(bitmath.getsize('./bitmath/__init__.py', system=bitmath.SI)) 34.159 kB We can see in lines **1** → **4** that the same result is returned @@ -163,12 +163,12 @@ bitmath.listdir() >>> import bitmath >>> for f in bitmath.listdir('./some_files'): - ... print f + ... print(f) ... ('/tmp/tmp.P5lqtyqwPh/some_files/first_file', Byte(1337.0)) ('/tmp/tmp.P5lqtyqwPh/some_files/deeper_files/second_file', Byte(13370.0)) >>> for f in bitmath.listdir('./some_files', relpath=True): - ... print f + ... print(f) ... ('some_files/first_file', Byte(1337.0)) ('some_files/deeper_files/second_file', Byte(13370.0)) @@ -183,7 +183,7 @@ bitmath.listdir() .. code-block:: python >>> for f in bitmath.listdir('./some_files', filter='second*'): - ... print f + ... print(f) ... ('/tmp/tmp.P5lqtyqwPh/some_files/deeper_files/second_file', Byte(13370.0)) @@ -194,7 +194,7 @@ bitmath.listdir() .. code-block:: python >>> files = list(bitmath.listdir('./some_files')) - >>> print files + >>> print(files) [('/tmp/tmp.P5lqtyqwPh/some_files/first_file', Byte(1337.0)), ('/tmp/tmp.P5lqtyqwPh/some_files/deeper_files/second_file', Byte(13370.0))] Here's a more advanced example where we will sum the size of all @@ -205,13 +205,13 @@ bitmath.listdir() .. code-block:: python >>> discovered_files = [f[1] for f in bitmath.listdir('./some_files')] - >>> print discovered_files + >>> print(discovered_files) [Byte(1337.0), Byte(13370.0)] - >>> print reduce(lambda x,y: x+y, discovered_files) + >>> print(reduce(lambda x,y: x+y, discovered_files)) 14707.0 Byte - >>> print reduce(lambda x,y: x+y, discovered_files).best_prefix() + >>> print(reduce(lambda x,y: x+y, discovered_files).best_prefix()) 14.3623046875 KiB - >>> print reduce(lambda x,y: x+y, discovered_files).best_prefix().format("{value:.3f} {unit}") + >>> print(reduce(lambda x,y: x+y, discovered_files).best_prefix().format("{value:.3f} {unit}")) 14.362 KiB @@ -242,9 +242,9 @@ bitmath.parse_string() >>> import bitmath >>> a_dvd = bitmath.parse_string("4.7 GiB") - >>> print type(a_dvd) + >>> print(type(a_dvd)) - >>> print a_dvd + >>> print(a_dvd) 4.7 GiB .. caution:: @@ -267,7 +267,7 @@ bitmath.parse_string() >>> try: ... a_dvd = bitmath.parse_string("4.7 G") ... except ValueError: - ... print "Error while parsing string into bitmath object" + ... print("Error while parsing string into bitmath object") ... Error while parsing string into bitmath object @@ -281,9 +281,9 @@ bitmath.parse_string() >>> sizes = [ 1337, 1337.7, "1337", "1337.7", "1337 B", "1337B" ] >>> for size in sizes: ... try: - ... print "Parsed size into %s" % bitmath.parse_string(size).best_prefix() + ... print("Parsed size into %s" % bitmath.parse_string(size).best_prefix()) ... except ValueError: - ... print "Could not parse input: %s" % size + ... print("Could not parse input: %s" % size) ... Could not parse input: 1337 Could not parse input: 1337.7 @@ -313,11 +313,11 @@ bitmath.parse_string() >>> import bitmath >>> a_mebibyte = bitmath.parse_string("1 MiB") >>> a_mebioctet = bitmath.parse_string("1 Mio") - >>> print a_mebibyte, a_mebioctet + >>> print(a_mebibyte, a_mebioctet) 1.0 MiB 1.0 MiB - >>> print bitmath.parse_string("1Po") + >>> print(bitmath.parse_string("1Po")) 1.0 PB - >>> print bitmath.parse_string("1337 Eio") + >>> print(bitmath.parse_string("1337 Eio")) 1337.0 EiB Notice how on lines **4** and **5** that the variable @@ -422,7 +422,7 @@ bitmath.parse_string_unsafe() ... _ = fp.readline() ... for line in fp.readlines(): ... cols = line.split()[0:4] - ... print """Filesystem: %s + ... print("""Filesystem: %s) ... - Used: %s""" % (cols[0], bitmath.parse_string_unsafe(cols[1])) Filesystem: /dev/mapper/luks-ca8d5493-72bb-4691-afe1 - Used: 107.0 GB @@ -457,10 +457,10 @@ bitmath.parse_string_unsafe() ... _ = fp.readline() ... for line in fp.readlines(): ... cols = line.split()[0:4] - ... print """Filesystem: %s + ... print("""Filesystem: %s ... - Used: %s""" % (cols[0], ... bitmath.parse_string_unsafe(cols[1], \ - ... system=bitmath.NIST)) + ... system=bitmath.NIST))) Filesystem: /dev/mapper/luks-ca8d5493-72bb-4691-afe1 - Used: 100.0 GiB Filesystem: /dev/sda1 @@ -513,7 +513,7 @@ bitmath.query_device_capacity() >>> import bitmath >>> with open("/dev/sda") as device: ... size = bitmath.query_device_capacity(device).best_prefix() - ... print "Device %s capacity: %s (%s Bytes)" % (device.name, size, size_bytes) + ... print("Device %s capacity: %s (%s Bytes)" % (device.name, size, size_bytes)) Device /dev/sda capacity: 238.474937439 GiB (2.56060514304e+11 Bytes) @@ -620,8 +620,8 @@ bitmath.format() 'always_plural': always_plural_kbs } - print """None of the following will be pluralized, because that feature is turned off - """ + print("""None of the following will be pluralized, because that feature is turned off + """) test_string = """ One unit of 'Bit': {not_plural} @@ -630,18 +630,18 @@ bitmath.format() several items of a unit will always be pluralized in normal US English speech: {always_plural}""" - print test_string.format(**formatting_args) + print(test_string.format(**formatting_args)) - print """ + print(""" ---------------------------------------------------------------------- - """ + """) - print """Now, we'll use the bitmath.format() context manager + print("""Now, we'll use the bitmath.format() context manager to print the same test string, but with pluralization enabled. - """ + """) with bitmath.format(plural=True): - print test_string.format(**formatting_args) + print(test_string.format(**formatting_args)) The context manager is demonstrated in lines **33** → **34**. In these lines we use the :py:func:`bitmath.format` context manager, @@ -685,13 +685,13 @@ bitmath.format() :linenos: >>> import bitmath - >>> print "Some instances: %s, %s" % (bitmath.KiB(1 / 3.0), bitmath.Bit(512)) + >>> print("Some instances: %s, %s" % (bitmath.KiB(1 / 3.0), bitmath.Bit(512))) Some instances: 0.333333333333 KiB, 512.0 Bit >>> with bitmath.format("{value:e}-{unit}"): - ... print "Some instances: %s, %s" % (bitmath.KiB(1 / 3.0), bitmath.Bit(512)) + ... print("Some instances: %s, %s" % (bitmath.KiB(1 / 3.0), bitmath.Bit(512))) ... Some instances: 3.333333e-01-KiB, 5.120000e+02-Bit - >>> print "Some instances: %s, %s" % (bitmath.KiB(1 / 3.0), bitmath.Bit(512)) + >>> print("Some instances: %s, %s" % (bitmath.KiB(1 / 3.0), bitmath.Bit(512))) Some instances: 0.333333333333 KiB, 512.0 Bit @@ -736,7 +736,7 @@ behavior. .. code-block:: python >>> from bitmath import * - >>> print MiB(1337), kb(0.1234567), Byte(0) + >>> print(MiB(1337), kb(0.1234567), Byte(0)) 1337.0 MiB 0.1234567 kb 0.0 Byte We can make these instances print however we want to. Let's wrap @@ -748,7 +748,7 @@ behavior. >>> import bitmath >>> bitmath.format_string = "[{value:.2f}-{unit}]" - >>> print bitmath.MiB(1337), bitmath.kb(0.1234567), bitmath.Byte(0) + >>> print(bitmath.MiB(1337), bitmath.kb(0.1234567), bitmath.Byte(0)) [1337.00-MiB] [0.12-kb] [0.00-Byte] .. py:data:: format_plural @@ -763,7 +763,7 @@ behavior. .. code-block:: python >>> import bitmath - >>> print bitmath.MiB(1337) + >>> print(bitmath.MiB(1337)) 1337.0 MiB And now we'll enable pluralization (line **2**): @@ -774,10 +774,10 @@ behavior. >>> import bitmath >>> bitmath.format_plural = True - >>> print bitmath.MiB(1337) + >>> print(bitmath.MiB(1337)) 1337.0 MiBs >>> bitmath.format_plural = False - >>> print bitmath.MiB(1337) + >>> print(bitmath.MiB(1337)) 1337.0 MiB On line **5** we disable pluralization again and then see that the @@ -926,7 +926,7 @@ argument or option should be interpreted as. >>> parser.add_argument('--block-size', type=bitmath.BitmathType) >>> args = "--block-size 1MiB" >>> results = parser.parse_args(args.split()) - >>> print type(results.block_size) + >>> print(type(results.block_size)) On line **3** we add the ``--block-size`` option to the parser, diff --git a/docsite/source/real_life_examples.rst b/docsite/source/real_life_examples.rst index e92bd82..6b64bb0 100644 --- a/docsite/source/real_life_examples.rst +++ b/docsite/source/real_life_examples.rst @@ -24,7 +24,7 @@ as such: >>> import bitmath >>> downstream = bitmath.Mib(50) - >>> print downstream.to_MB() + >>> print(downstream.to_MB()) MB(6.25) This tells us that if our ISP advertises **50Mbps** we can expect to @@ -51,7 +51,7 @@ in this case is: :emphasize-lines: 3 >>> song_size = GB(5) / 1000 - >>> print song_size + >>> print(song_size) 0.005GB Or, using ``best_prefix``, (line **2**) to generate a more @@ -62,7 +62,7 @@ human-readable form: :emphasize-lines: 2 >>> song_size = GB(5) / 1000 - >>> print song_size.best_prefix() + >>> print(song_size.best_prefix()) 5.0MB That's great, if you have normal radio-length songs. But how many of @@ -78,7 +78,7 @@ MB) large. >>> ipod_capacity = GB(5) >>> bootleg_size = MB(19.5) - >>> print ipod_capacity / bootleg_size + >>> print(ipod_capacity / bootleg_size) 256.41025641 The result on line **4** tells tells us that we could fit **256** @@ -102,7 +102,7 @@ returns). We can use ``bitmath`` to do that too: >>> these_files = os.listdir('.') >>> for f in these_files: ... f_size = Byte(os.path.getsize(f)) - ... print "%s - %s" % (f, f_size.to_KiB()) + ... print("%s - %s" % (f, f_size.to_KiB())) test_basic_math.py - 3.048828125 KiB __init__.py - 0.1181640625 KiB @@ -122,7 +122,7 @@ directly into a bitmath object: >>> import bitmath >>> these_files = os.listdir('.') >>> for f in these_files: - ... print "%s - %s" % (f, bitmath.getsize(f)) + ... print("%s - %s" % (f, bitmath.getsize(f))) test_basic_math.py - 3.048828125 KiB __init__.py - 0.1181640625 KiB @@ -255,7 +255,7 @@ using the :py:mod:`bitmath` library. Let's see how: >>> bdp = (GB(tx * rtt)).to_Byte() - >>> print bdp.to_KiB() + >>> print(bdp.to_KiB()) KiB(24.2919921875) @@ -266,7 +266,7 @@ We could shorten that even further: .. code-block:: python - >>> print (GB((1/8.0) * (0.199 * 10**-3))).to_Byte() + >>> print((GB((1/8.0) * (0.199 * 10**-3))).to_Byte()) 24875.0Byte **Get the current kernel parameters** @@ -285,7 +285,7 @@ Recall, these values are in bytes. What are they in KiB? .. code-block:: python - >>> print Byte(212992).to_KiB() + >>> print(Byte(212992).to_KiB()) KiB(208.0) This means our core networking buffer sizes are set to 208KiB @@ -335,11 +335,11 @@ size is ``4096 bytes``, but you can check by running the command: >>> sys_buffer = Byte(sys_pages * page_size) - >>> print sys_buffer.to_MiB() + >>> print(sys_buffer.to_MiB()) 2192.4375MiB - >>> print sys_buffer.to_GiB() + >>> print(sys_buffer.to_GiB()) 2.14105224609GiB @@ -417,7 +417,7 @@ example system. >>> fh = open('/dev/sda', 'r') >>> sda_capacity = bitmath.query_device_capacity(fh) >>> fh.close() - >>> print sda_capacity.best_prefix() + >>> print(sda_capacity.best_prefix()) 238.474937439 GiB We can simplify this so that the file handle is automatically closed @@ -427,5 +427,5 @@ for us by using the ``with`` context manager. >>> with open('/dev/sda', 'r') as fh: ... sda_capacity = bitmath.query_device_capacity(fh) - >>> print sda_capacity.best_prefix() + >>> print(sda_capacity.best_prefix()) 238.474937439 GiB diff --git a/docsite/source/simple_examples.rst b/docsite/source/simple_examples.rst index bb180a6..3bb6bcc 100644 --- a/docsite/source/simple_examples.rst +++ b/docsite/source/simple_examples.rst @@ -192,19 +192,19 @@ out sorted by increasing magnitude (lines **10** and **11**, and >>> for f in os.listdir('./tests/'): ... sizes.append(KiB(os.path.getsize('./tests/' + f))) - >>> print sizes + >>> print(sizes) [KiB(7337.0), KiB(1441.0), KiB(2126.0), KiB(2178.0), KiB(2326.0), KiB(4003.0), KiB(48.0), KiB(1770.0), KiB(7892.0), KiB(4190.0)] - >>> print sorted(sizes) + >>> print(sorted(sizes)) [KiB(48.0), KiB(1441.0), KiB(1770.0), KiB(2126.0), KiB(2178.0), KiB(2326.0), KiB(4003.0), KiB(4190.0), KiB(7337.0), KiB(7892.0)] >>> human_sizes = [s.best_prefix() for s in sizes] - >>> print sorted(human_sizes) + >>> print(sorted(human_sizes)) [KiB(48.0), MiB(1.4072265625), MiB(1.728515625), MiB(2.076171875), MiB(2.126953125), MiB(2.271484375), MiB(3.9091796875), MiB(4.091796875), MiB(7.1650390625), MiB(7.70703125)] Now print them out in descending magnitude .. code-block:: python - >>> print sorted(human_sizes, reverse=True) + >>> print(sorted(human_sizes, reverse=True)) [KiB(7892.0), KiB(7337.0), KiB(4190.0), KiB(4003.0), KiB(2326.0), KiB(2178.0), KiB(2126.0), KiB(1770.0), KiB(1441.0), KiB(48.0)] diff --git a/tests/test_file_size.py b/tests/test_file_size.py index 389a14c..1a4b7f8 100644 --- a/tests/test_file_size.py +++ b/tests/test_file_size.py @@ -99,7 +99,7 @@ def test_listdir_nosymlinks(self): Then: >>> for f in bitmath.listdir('./tests/listdir_nosymlinks'): - ... print f + ... print(f) Would yield 2-tuple's of: From 5b51b9c85a75c044b8467d803b193c2a5f524455 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sun, 5 Feb 2023 18:28:45 -0600 Subject: [PATCH 27/92] Fix up contributing page. Add CoC notice. Refresh CI description. Closes #73 --- docsite/source/contributing.rst | 60 +++++++++++++++------------------ 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/docsite/source/contributing.rst b/docsite/source/contributing.rst index b6e9ec0..d2ded9e 100644 --- a/docsite/source/contributing.rst +++ b/docsite/source/contributing.rst @@ -10,6 +10,18 @@ This section describes the guidelines for contributing to bitmath. :local: + +.. _contributing_code_of_conduct: + +Code of Conduct +*************** + +All persons submitting code or otherwise interacting with the bitmath +project on GitHub must accept and abide by the terms of the `Code of +Conduct +`_. + + .. _contributing_issue_reporting: Issue Reporting @@ -120,29 +132,20 @@ Components bitmath unit tests are integrated with/depend on the following items: -* `Travis CI `_ - Free online service - providing `continuous integration` functionality for open source - projects. Tests are ran automatically on every git - commit. Integrates with GitHub to notify you if a pull request - passes or fails all unitests. - -* `Coveralls `_ - Free - online service providing code test coverage reporting. Integrates - with GitHub to notify you if a pull-request would improve/decrease - overall code test coverage. +* `GitHub Actions `_ - + GitHub Actions provideFree online `continuous integration` + functionality for projects. Tests are ran automatically on every git + commit. In the past we used Travis for this functionality. * `unittest `_ - Python unit testing framework. All bitmath tests are written using this framework. -* `nose `_ - Per the **nose** - website: "`extends unittest to make testing easier`". **nose** is - used to run our unit tests. - -* `coverage `_ - A tool for - measuring code coverage of Python programs. For bitmath we require a - minimum test coverage of **90%**. This is invoked by **nose** - automatically. +* `PyTest `_ - Per the **pytest** + website: "`The pytest framework makes it easy to write small, + readable tests, and can scale to support complex functional testing + for applications and libraries`". **pytest** is used to run our unit + tests. * `pycodestyle `_ - A tool to check Python code against some of the style conventions in :pep:`0008`. @@ -168,27 +171,18 @@ of `makefile targets`. For the purpose of this documentation, we can think of these `targets` as pre-defined commands coded in a makefile. bitmath testing targets include: -* ``ci`` - Run the tests exactly how they are ran in Travis-CI. The - ``ci`` target automatically calls the ``pycodestyle``, ``pyflakes``, - ``uniquetestnames``, and ``unittests`` targets. -* ``ci3`` - Is the same as the ``ci`` target, except it runs using the - Python 3.x interpreter. -* ``unittests`` - Run the functional test suite. -* ``pycodestyle`` - Run :pep:`0008` syntax checks. -* ``pyflakes`` - Run `pyflakes` error checks. +* ``ci`` - Run all of the tests +* ``ci-pycodestyle`` - Run :pep:`0008` syntax checks +* ``ci-pyflakes`` - Run `pyflakes` error checks +* ``ci-unittests`` - Run the tests in a virtual env * ``clean`` - Remove temporary files and build artifacts from the checked-out repository. * ``uniquetestnames`` - Ensures no unit tests have the same name. -* ``tests`` - A quicker version of ``ci``. Different from ``ci`` in - that ``tests`` uses libraries installed on the local development - workstation. ``tests`` runs the ``unittests``, ``pycodestyle``, - ``uniquetestnames``, and ``pyflakes`` tests automatically. To ensure the highest degree of confidence in test results you should -**always use** the ``ci`` and ``ci3`` targets. +**always use** the ``ci`` target, or make a pull request on GitHub and +let it run the tests on all of the supported platforms. -When Travis-CI runs an integration test, it calls the ``ci`` and -``ci3`` targets. Running the Tests ================= From b36b6e49d3f3c9d689c783c534ef4240fd168cfc Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sun, 5 Feb 2023 18:30:12 -0600 Subject: [PATCH 28/92] Add more badge flair --- Makefile | 3 --- README.rst | 33 +++++++++++++++++++++++++++---- docsite/source/index.rst | 39 ++++++++++++++++++++++++++----------- docsite/source/index.rst.in | 39 ++++++++++++++++++++++++++----------- 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index 250b371..5517632 100644 --- a/Makefile +++ b/Makefile @@ -97,9 +97,6 @@ pypitest: tag: git tag -s -m $(TAG) $(TAG) -tests: uniquetestnames unittests pycodestyle pyflakes - : - clean: @find . -type f -regex ".*\.py[co]$$" -delete @find . -type f \( -name "*~" -or -name "#*" \) -delete diff --git a/README.rst b/README.rst index 806b423..151ad54 100644 --- a/README.rst +++ b/README.rst @@ -1,13 +1,38 @@ -.. image:: https://github.com/tbielawa/bitmath/actions/workflows/python.yml/badge.svg - :target: https://github.com/tbielawa/bitmath/actions/workflows/python.yml - - .. image:: https://readthedocs.org/projects/bitmath/badge/?version=latest :target: http://bitmath.rtfd.org/ :align: right :height: 19 :width: 77 +.. image:: https://github.com/tbielawa/bitmath/actions/workflows/python.yml/badge.svg + :target: https://github.com/tbielawa/bitmath/actions/workflows/python.yml + +.. image:: https://img.shields.io/github/issues/tbielawa/bitmath?style=flat-square + :target: https://github.com/tbielawa/bitmath/issues + :alt: Open issues + +.. image:: https://img.shields.io/github/issues-pr/tbielawa/bitmath?style=flat-square + :target: https://github.com/tbielawa/bitmath/pulls + :alt: Open pull requests + +.. image:: https://img.shields.io/pypi/dm/bitmath?style=flat-square + :target: https://pypistats.org/packages/bitmath + :alt: PyPI - Package Downloads + +.. image:: https://img.shields.io/github/stars/tbielawa/bitmath?style=flat-square + :target: https://pypistats.org/packages/bitmath + :alt: GitHub Project Popularity + +.. image:: https://img.shields.io/pypi/l/bitmath?style=flat-square + :target: https://opensource.org/licenses/MIT + :alt: PyPI - License + +.. image:: https://img.shields.io/pypi/implementation/bitmath?style=flat-square + :alt: PyPI - Implementation + +.. image:: https://img.shields.io/pypi/pyversions/bitmath?style=flat-square + :alt: PyPI - Python Version + bitmath ======= diff --git a/docsite/source/index.rst b/docsite/source/index.rst index a784141..a323cee 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -1,15 +1,32 @@ -.. image:: https://api.travis-ci.org/tbielawa/bitmath.png - :target: https://travis-ci.org/tbielawa/bitmath/ - :align: right - :height: 19 - :width: 77 - -.. image:: https://coveralls.io/repos/tbielawa/bitmath/badge.png?branch=master - :target: https://coveralls.io/github/tbielawa/bitmath - :align: right - :height: 19 - :width: 77 +.. image:: https://github.com/tbielawa/bitmath/actions/workflows/python.yml/badge.svg + :target: https://github.com/tbielawa/bitmath/actions/workflows/python.yml + :alt: Build Status on GitHub +.. image:: https://img.shields.io/github/issues/tbielawa/bitmath?style=flat-square + :target: https://github.com/tbielawa/bitmath/issues + :alt: Open Issues + +.. image:: https://img.shields.io/github/issues-pr/tbielawa/bitmath?style=flat-square + :target: https://github.com/tbielawa/bitmath/pulls + :alt: Open Pull Requests + +.. image:: https://img.shields.io/pypi/dm/bitmath?style=flat-square + :target: https://pypistats.org/packages/bitmath + :alt: PyPI - Package Popularity + +.. image:: https://img.shields.io/github/stars/tbielawa/bitmath?style=flat-square + :target: https://pypistats.org/packages/bitmath + :alt: GitHub Project Popularity + +.. image:: https://img.shields.io/pypi/l/bitmath?style=flat-square + :target: https://opensource.org/licenses/MIT + :alt: PyPI - License + +.. image:: https://img.shields.io/pypi/implementation/bitmath?style=flat-square + :alt: PyPI - Implementation + +.. image:: https://img.shields.io/pypi/pyversions/bitmath?style=flat-square + :alt: PyPI - Python Version bitmath ####### diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index 454c49a..1ff2a9c 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -1,15 +1,32 @@ -.. image:: https://api.travis-ci.org/tbielawa/bitmath.png - :target: https://travis-ci.org/tbielawa/bitmath/ - :align: right - :height: 19 - :width: 77 - -.. image:: https://coveralls.io/repos/tbielawa/bitmath/badge.png?branch=master - :target: https://coveralls.io/github/tbielawa/bitmath - :align: right - :height: 19 - :width: 77 +.. image:: https://github.com/tbielawa/bitmath/actions/workflows/python.yml/badge.svg + :target: https://github.com/tbielawa/bitmath/actions/workflows/python.yml + :alt: Build Status on GitHub +.. image:: https://img.shields.io/github/issues/tbielawa/bitmath?style=flat-square + :target: https://github.com/tbielawa/bitmath/issues + :alt: Open Issues + +.. image:: https://img.shields.io/github/issues-pr/tbielawa/bitmath?style=flat-square + :target: https://github.com/tbielawa/bitmath/pulls + :alt: Open Pull Requests + +.. image:: https://img.shields.io/pypi/dm/bitmath?style=flat-square + :target: https://pypistats.org/packages/bitmath + :alt: PyPI - Package Popularity + +.. image:: https://img.shields.io/github/stars/tbielawa/bitmath?style=flat-square + :target: https://pypistats.org/packages/bitmath + :alt: GitHub Project Popularity + +.. image:: https://img.shields.io/pypi/l/bitmath?style=flat-square + :target: https://opensource.org/licenses/MIT + :alt: PyPI - License + +.. image:: https://img.shields.io/pypi/implementation/bitmath?style=flat-square + :alt: PyPI - Implementation + +.. image:: https://img.shields.io/pypi/pyversions/bitmath?style=flat-square + :alt: PyPI - Python Version bitmath ####### From 3e7d458c1bf3abf4f133fa92e493bd854dc9b4c8 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Sun, 5 Feb 2023 18:51:41 -0600 Subject: [PATCH 29/92] Simple commit to trigger doc build? --- NEWS.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index 6b7798e..7f9e784 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -39,7 +39,8 @@ Python 2.x. ` - Integrations will be removed from the primary source code for now. Many of these can be provided as code examples instead which will simplify packaging and - testing requirements for the project. + testing requirements for the project. They'll be in the docs or just + in git, not sure yet. * Packaging - It looks like a lot has changed in the last 2.5 years in the Python packaging world, and I have a lot to catch up on. I guess we use TOML instead of setup.py now, that's neat. From 3e72958b93dc5241bd269eb08183aa71fef32b98 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Mon, 6 Feb 2023 10:57:16 -0600 Subject: [PATCH 30/92] Doc updates, make class references working links, fuss with some examples, update copyright --- docsite/source/classes.rst | 12 ++++---- docsite/source/conf.py | 2 +- docsite/source/conf.py.in | 2 +- docsite/source/index.rst | 40 ++++++++------------------- docsite/source/index.rst.in | 40 ++++++++------------------- docsite/source/instances.rst | 5 ++-- docsite/source/module.rst | 13 ++++----- docsite/source/real_life_examples.rst | 11 -------- 8 files changed, 39 insertions(+), 86 deletions(-) diff --git a/docsite/source/classes.rst b/docsite/source/classes.rst index 027d59d..e58aebd 100644 --- a/docsite/source/classes.rst +++ b/docsite/source/classes.rst @@ -119,7 +119,7 @@ Initializing :param int value: **Default: 0**. The value of the instance in *prefix units*. For example, if we were - instantiating a ``bitmath.KiB`` object to + instantiating a :class:`.KiB` object to represent 13.37 KiB, the ``value`` parameter would be **13.37**. For instance, ``k = bitmath.KiB(13.37)``. @@ -163,8 +163,8 @@ Class Method: from_other() ========================== bitmath **class objects** have one public class method, -:py:meth:`BitMathClass.from_other` which provides an -alternative way to initialize a bitmath class. +:py:meth:`.from_other` which provides an alternative way to initialize +a bitmath class. This method may be called on bitmath class objects directly. That is to say: you do not need to call this method on an instance of a @@ -176,8 +176,8 @@ bitmath class, however that is a valid use case. Instantiate any ``BitMathClass`` using another instance as reference for it's initial value. - The ``from_other()`` class method has one required parameter: an - instance of a bitmath class. + The :py:meth:`.from_other` class method has one required parameter: + an instance of a bitmath class. :param BitMathInstance item: An instance of a bitmath class. :return: a bitmath instance of type ``BitMathClass`` equivalent in @@ -204,7 +204,7 @@ bitmath class, however that is a valid use case. >>> print(a_mebibyte, a_mebibyte_sized_kibibyte) 1.0 MiB 1024.0 KiB - Or, using the :py:meth:`BitMathClass.from_other` class method: + Or, using the :py:meth:`.from_other` class method: .. code-block:: python :linenos: diff --git a/docsite/source/conf.py b/docsite/source/conf.py index 8fce262..d7f5637 100644 --- a/docsite/source/conf.py +++ b/docsite/source/conf.py @@ -48,7 +48,7 @@ # General information about the project. project = u'bitmath' -copyright = u'2014-2016, Tim Bielawa' +copyright = u'2014-2023, Tim Bielawa' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docsite/source/conf.py.in b/docsite/source/conf.py.in index 162184e..bf157ef 100644 --- a/docsite/source/conf.py.in +++ b/docsite/source/conf.py.in @@ -48,7 +48,7 @@ master_doc = 'index' # General information about the project. project = u'bitmath' -copyright = u'2014-2016, Tim Bielawa' +copyright = u'2014-2023, Tim Bielawa' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docsite/source/index.rst b/docsite/source/index.rst index a323cee..0e94e43 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -72,38 +72,23 @@ yourself `_. Installation ############ -The easiest way to install bitmath is via ``dnf`` (or ``yum``) if -you're on a Fedora/RHEL based distribution. bitmath is available in -the main Fedora repositories, as well as EPEL Repositories. As of 2023 -bitmath is only developed, tested, and supported for `currently -supported `_ Python releases. +bitmath is available in Fedora and EPEL repositories, as well as +directly available via `PyPI +`_. As of 2023 bitmath is only +developed, tested, and supported for `currently supported +`_ Python releases. +**Package Managers** .. code-block:: bash $ sudo dnf install python3-bitmath + $ pip install --user bitmath -**PyPi**: +**Source**:: -You could also install bitmath from `PyPi -`_ if you like: - -.. code-block:: bash - - $ sudo pip install bitmath - -.. note:: - - **pip** installs need pip >= 1.1. To workaround this, `download - bitmath `_, from - PyPi and then ``pip install bitmath-x.y.z.tar.gz``. See `issue #57 - `_ - for more information. - - - -**Source**: + NOTE: UPDATE THIS FOR THE NEW HATCH.TOML THING Or, if you want to install from source: @@ -111,11 +96,8 @@ Or, if you want to install from source: $ sudo python ./setup.py install -If you want the bitmath manpage installed as well: - -.. code-block:: bash - - $ sudo make install +If you want the ``/bin/bitmath`` manpage too, then ``sudo make +install`` will install the Python library and the manpage. Contents diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index 1ff2a9c..332e0e3 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -72,38 +72,23 @@ yourself `_. Installation ############ -The easiest way to install bitmath is via ``dnf`` (or ``yum``) if -you're on a Fedora/RHEL based distribution. bitmath is available in -the main Fedora repositories, as well as EPEL Repositories. As of 2023 -bitmath is only developed, tested, and supported for `currently -supported `_ Python releases. +bitmath is available in Fedora and EPEL repositories, as well as +directly available via `PyPI +`_. As of 2023 bitmath is only +developed, tested, and supported for `currently supported +`_ Python releases. +**Package Managers** .. code-block:: bash $ sudo dnf install python3-bitmath + $ pip install --user bitmath -**PyPi**: +**Source**:: -You could also install bitmath from `PyPi -`_ if you like: - -.. code-block:: bash - - $ sudo pip install bitmath - -.. note:: - - **pip** installs need pip >= 1.1. To workaround this, `download - bitmath `_, from - PyPi and then ``pip install bitmath-x.y.z.tar.gz``. See `issue #57 - `_ - for more information. - - - -**Source**: + NOTE: UPDATE THIS FOR THE NEW HATCH.TOML THING Or, if you want to install from source: @@ -111,11 +96,8 @@ Or, if you want to install from source: $ sudo python ./setup.py install -If you want the bitmath manpage installed as well: - -.. code-block:: bash - - $ sudo make install +If you want the ``/bin/bitmath`` manpage too, then ``sudo make +install`` will install the Python library and the manpage. Contents diff --git a/docsite/source/instances.rst b/docsite/source/instances.rst index 1b798d7..f0e702e 100644 --- a/docsite/source/instances.rst +++ b/docsite/source/instances.rst @@ -21,9 +21,10 @@ bitmath objects have several instance attributes: .. code-block:: python - >>> b = bitmath.Byte(1337) - >>> print(b.base) + >>> print(bitmath.Byte(1337).base) 2 + >>> print(bitmath.kB(1337).base) + 10 .. py:attribute:: BitMathInstance.binary diff --git a/docsite/source/module.rst b/docsite/source/module.rst index 7033999..31240f8 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -30,7 +30,7 @@ bitmath.getsize() :param bool bestprefix: **Default:** ``True``, the returned instance will be in the best human-readable prefix unit. If set to ``False`` the result - is a ``bitmath.Byte`` instance. + is a :class:`.Byte` instance. :param system: **Default:** :py:data:`bitmath.NIST`. The preferred system of units for the returned instance. :type system: One of :py:data:`bitmath.NIST` or :py:data:`bitmath.SI` @@ -110,7 +110,7 @@ bitmath.listdir() :py:func:`os.path.realpath` to normalize path references :param bool bestprefix: **Default:** ``False``, returns - ``bitmath.Byte`` instances. Set to ``True`` + :class:`.Byte` instances. Set to ``True`` to return the best human-readable prefix unit for representation :param system: **Default:** :py:data:`bitmath.NIST`. Set a prefix @@ -123,7 +123,7 @@ bitmath.listdir() * This function does **not** return tuples for directory entities. Including directories in results is `scheduled for introduction `_ - in the upcoming 1.1.0 release. + in an upcoming release. * Symlinks to **files** are followed automatically @@ -224,8 +224,6 @@ bitmath.parse_string() .. function:: parse_string(str_repr) - .. versionadded:: 1.1.0 - Parse a string representing a unit into a proper bitmath object. All non-string inputs are rejected and will raise a :py:exc:`ValueError`. Strings without units are also rejected. See @@ -337,14 +335,14 @@ bitmath.parse_string() forgiving with input. Please read the documentation carefully so you understand the risks you assume using the ``unsafe`` parser. + .. versionadded:: 1.1.0 + bitmath.parse_string_unsafe() ============================= .. function:: parse_string_unsafe(repr[, system=bitmath.SI]) - .. versionadded:: 1.3.1 - Parse a string or number into a proper bitmath object. This is the less strict version of the :py:func:`bitmath.parse_string` function. While :py:func:`bitmath.parse_string` only accepts SI and @@ -472,6 +470,7 @@ bitmath.parse_string_unsafe() Capital **G** followed by a lower-case **i** ending with a capital **B**, ``GiB``. + .. versionadded:: 1.3.1 bitmath.query_device_capacity() diff --git a/docsite/source/real_life_examples.rst b/docsite/source/real_life_examples.rst index 6b64bb0..597aa92 100644 --- a/docsite/source/real_life_examples.rst +++ b/docsite/source/real_life_examples.rst @@ -411,17 +411,6 @@ object with the ``query_device_capacity`` function. Here's an example where we read the capacity of device ``sda``, the first device on the example system. -.. code-block:: python - - >>> import bitmath - >>> fh = open('/dev/sda', 'r') - >>> sda_capacity = bitmath.query_device_capacity(fh) - >>> fh.close() - >>> print(sda_capacity.best_prefix()) - 238.474937439 GiB - -We can simplify this so that the file handle is automatically closed -for us by using the ``with`` context manager. .. code-block:: python From 7c3f7a32d28d761a37a9781baf998d1b8e3efba3 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Mon, 6 Feb 2023 10:57:36 -0600 Subject: [PATCH 31/92] Add docstrings to properties --- bitmath/__init__.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index a4c5e38..86d52f7 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -251,30 +251,29 @@ def _norm(self, value): # Properties #: The mathematical base of an instance - base = property(lambda s: s._base) + base = property(lambda s: s._base, + doc="The mathematical base of the unit of the instance (this will be 2 or 10)") - binary = property(lambda s: bin(int(s.bits))) - """The binary representation of an instance in binary 1s and 0s. Note + binary = property(lambda s: bin(int(s.bits)), + doc="""The binary representation of an instance in binary 1s and 0s. Note that for very large numbers this will mean a lot of 1s and 0s. For -example, GiB(100) would be represented as:: +example, GiB(100) would be represented in Python as:: 0b1100100000000000000000000000000000000000 - That leading ``0b`` is normal. That's how Python represents binary. - - """ +""") #: Alias for :attr:`binary` - bin = property(lambda s: s.binary) + bin = property(lambda s: s.binary, doc="Alias for the 'binary' property") #: The number of bits in an instance - bits = property(lambda s: s._bit_value) + bits = property(lambda s: s._bit_value, doc="The number of bits in an instance") #: The number of bytes in an instance - bytes = property(lambda s: s._byte_value) + bytes = property(lambda s: s._byte_value, doc="The number of bytes in an instance") #: The mathematical power of an instance - power = property(lambda s: s._power) + power = property(lambda s: s._power, doc="The mathematical power of an instance") @property def system(self): @@ -304,7 +303,6 @@ def unit(self): >>> Byte(1).unit == 'Byte' >>> Byte(1.1).unit == 'Bytes' >>> Gb(2).unit == 'Gbs' - """ global format_plural @@ -328,7 +326,6 @@ def unit_plural(self): >>> KiB(1).unit_plural == 'KiB' >>> Byte(1024).unit_plural == 'Bytes' >>> Gb(1).unit_plural == 'Gb' - """ return self._name_plural From 46e8bbdea84246634ac5d04a73dbc846ff888be9 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Mon, 13 Feb 2023 16:14:06 -0600 Subject: [PATCH 32/92] Remove a dead project link, update SymPy link --- .../source/appendices/related_projects.rst | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/docsite/source/appendices/related_projects.rst b/docsite/source/appendices/related_projects.rst index e74411a..e88a007 100644 --- a/docsite/source/appendices/related_projects.rst +++ b/docsite/source/appendices/related_projects.rst @@ -71,27 +71,5 @@ In contrast, the bitmath module includes classes representing the full spectrum of byte and bit based units, out of the box. No conversion or derivation code required of the user. -* `Units Homepage & Docs `_ +* `Units Homepage & Docs `_ * Download available through ``pip``, or your distribution's package system - - -Unum -==== - - - *Unum stands for 'unit-numbers'. It is a Python module that allows - to define and manipulate true quantities, i.e. numbers with units - such as 60 seconds, [...], 30 dollars etc. The module validates - unit consistency in arithmetic expressions; it provides also - automatic conversion and output formatting. Unum is designed to be - reliable, easy-to-use, customizable and open to any unit - definition.* - -**Unum**, by Pierre X. Denis, is another extensible library for unit -manipulation. The module does not appear to have seen any activity in -quite some time. Looking over the docs gives me the impression that it -also has a tendency to pollute your namespace with objects like ``M`` -and anything else it pre-defines. - -* `Unum Homepage and Docs `_ -* `Unum Source Download `_ From 22ec5f7fa0aafb1444a907d5f6058b227e7adbaf Mon Sep 17 00:00:00 2001 From: Tim Case Date: Thu, 16 Apr 2026 22:29:38 -0500 Subject: [PATCH 33/92] Add pyproject.toml (replaces hatch.toml, PEP 517/518 compliant) --- pyproject.toml | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3760e32 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,57 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "bitmath" +version = "2.0.0" +description = "Pythonic module for representing and manipulating file sizes with different prefix notations (file size unit conversion)" +readme = "README.rst" +requires-python = ">=3.11" +authors = [ + { name = "Tim Bielawa", email = "timbielawa@gmail.com" }, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "Intended Audience :: Telecommunications Industry", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Communications :: File Sharing", + "Topic :: Internet", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Information Analysis", + "Topic :: Scientific/Engineering :: Mathematics", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: Testing", + "Topic :: Software Development :: Testing :: Acceptance", + "Topic :: Software Development :: Testing :: Unit", + "Topic :: System :: Filesystems", + "Topic :: System :: Systems Administration", + "Topic :: Text Processing :: Filters", + "Topic :: Utilities", +] + +[project.urls] +Homepage = "https://bitmath.readthedocs.io/en/latest/index.html" +"Bug Tracker" = "https://github.com/tbielawa/bitmath/issues" +"Git Repo" = "https://github.com/tbielawa/bitmath" + +[project.scripts] +bitmath = "bitmath:cli_script" + +[tool.hatch.build.targets.wheel] +packages = ["bitmath", "bitmath.integrations"] + +[tool.hatch.publish.index] +disable = true From 14dc2d1cb6a4c5ec3545a7d29e45bf379931a8a5 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Thu, 16 Apr 2026 22:35:10 -0500 Subject: [PATCH 34/92] Remove legacy packaging files, drop mock from requirements --- hatch.toml | 52 ------------------------------- requirements.txt | 1 - setup.py | 79 ------------------------------------------------ setup.py.in | 79 ------------------------------------------------ 4 files changed, 211 deletions(-) delete mode 100644 hatch.toml delete mode 100644 setup.py delete mode 100644 setup.py.in diff --git a/hatch.toml b/hatch.toml deleted file mode 100644 index e3d185a..0000000 --- a/hatch.toml +++ /dev/null @@ -1,52 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[build.targets.wheel] -packages = ["bitmath"] - - -[project] -name = "bitmath" -version = "2.0.0" -authors = [ - { name="Tim Bielawa", email="timbielawa@gmail.com" }, - ] - description = "Pythonic module for representing and manipulating file sizes with different prefix notations (file size unit conversion)" - readme = "README.rst" - requires-python = ">=3.7" - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Intended Audience :: System Administrators', - 'Intended Audience :: Telecommunications Industry', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3 :: Only', - 'Topic :: Communications :: File Sharing', - 'Topic :: Internet', - 'Topic :: Scientific/Engineering', - 'Topic :: Scientific/Engineering :: Information Analysis', - 'Topic :: Scientific/Engineering :: Mathematics', - 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Software Development :: Testing', - 'Topic :: Software Development :: Testing :: Acceptance', - 'Topic :: Software Development :: Testing :: Unit', - 'Topic :: System :: Filesystems', - 'Topic :: System :: Systems Administration', - 'Topic :: Text Processing :: Filters', - 'Topic :: Utilities' - ] - -[project.urls] -"Homepage" = "https://bitmath.readthedocs.io/en/latest/index.html" -"Bug Tracker" = "https://github.com/tbielawa/bitmath/issues" -"Git Repo" = "https://github.com/tbielawa/bitmath" - -[publish.index] -disable = true \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b1581d7..c98a1c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -mock pyflakes pycodestyle pytest diff --git a/setup.py b/setup.py deleted file mode 100644 index 9aab738..0000000 --- a/setup.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -# The MIT License (MIT) -# -# Copyright © 2014 Tim Bielawa -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from __future__ import print_function -try: - from setuptools import setup -except ImportError: - print("Command line script will not be created.") - from distutils.core import setup - - -pypi_notice = open('README.rst', 'r').read() - -setup( - name='bitmath', - version='2.0.0.1', - description='Pythonic module for representing and manipulating file sizes with different prefix notations (file size unit conversion)', - long_description=pypi_notice, - maintainer='Tim Bielawa', - maintainer_email='timbielawa@gmail.com', - url='https://github.com/tbielawa/bitmath', - license='MIT', - package_dir={'bitmath': 'bitmath'}, - packages=['bitmath', 'bitmath.integrations'], - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Intended Audience :: System Administrators', - 'Intended Audience :: Telecommunications Industry', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3 :: Only', - 'Topic :: Communications :: File Sharing', - 'Topic :: Internet', - 'Topic :: Scientific/Engineering', - 'Topic :: Scientific/Engineering :: Information Analysis', - 'Topic :: Scientific/Engineering :: Mathematics', - 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Software Development :: Testing', - 'Topic :: Software Development :: Testing :: Acceptance', - 'Topic :: Software Development :: Testing :: Unit', - 'Topic :: System :: Filesystems', - 'Topic :: System :: Systems Administration', - 'Topic :: Text Processing :: Filters', - 'Topic :: Utilities' - ], - entry_points = { - 'console_scripts': [ - 'bitmath = bitmath:cli_script', - ], - } -) diff --git a/setup.py.in b/setup.py.in deleted file mode 100644 index 44b6e36..0000000 --- a/setup.py.in +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -# The MIT License (MIT) -# -# Copyright © 2014 Tim Bielawa -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from __future__ import print_function -try: - from setuptools import setup -except ImportError: - print("Command line script will not be created.") - from distutils.core import setup - - -pypi_notice = open('README.rst', 'r').read() - -setup( - name='bitmath', - version='%VERSION%.%RELEASE%', - description='Pythonic module for representing and manipulating file sizes with different prefix notations (file size unit conversion)', - long_description=pypi_notice, - maintainer='Tim Bielawa', - maintainer_email='timbielawa@gmail.com', - url='https://github.com/tbielawa/bitmath', - license='MIT', - package_dir={'bitmath': 'bitmath'}, - packages=['bitmath', 'bitmath.integrations'], - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Intended Audience :: System Administrators', - 'Intended Audience :: Telecommunications Industry', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3 :: Only', - 'Topic :: Communications :: File Sharing', - 'Topic :: Internet', - 'Topic :: Scientific/Engineering', - 'Topic :: Scientific/Engineering :: Information Analysis', - 'Topic :: Scientific/Engineering :: Mathematics', - 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Software Development :: Testing', - 'Topic :: Software Development :: Testing :: Acceptance', - 'Topic :: Software Development :: Testing :: Unit', - 'Topic :: System :: Filesystems', - 'Topic :: System :: Systems Administration', - 'Topic :: Text Processing :: Filters', - 'Topic :: Utilities' - ], - entry_points = { - 'console_scripts': [ - 'bitmath = bitmath:cli_script', - ], - } -) From e4eb79c8bdc076c50579786a97afbd65919e2f55 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Thu, 16 Apr 2026 22:35:48 -0500 Subject: [PATCH 35/92] Update CI: Python 3.11-3.13 matrix, checkout@v4, setup-python@v5 --- .github/workflows/python.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 10a7d8e..5a55ecf 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -6,15 +6,15 @@ jobs: build: strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.11", "3.12", "3.13"] os: ["macos-latest", "ubuntu-latest"] runs-on: ${{ matrix.os }} steps: - name: "GitHub Checks it out :sunglasses-face:" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' From 27029bf00346182ef209b8c63063dc4c12f97684 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Thu, 16 Apr 2026 22:37:52 -0500 Subject: [PATCH 36/92] Add CodeQL security scanning workflow --- .github/workflows/codeql.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..93411e5 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,35 @@ +name: CodeQL Security Scan + +on: + push: + branches: ["master", "bitmath2"] + pull_request: + branches: ["master"] + schedule: + - cron: "0 0 * * 0" + +jobs: + analyze: + name: Analyze Python + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: python + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:python" From 8da9d4648c3b316aa8cf89a85daf6d2b679272e0 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Thu, 16 Apr 2026 23:04:46 -0500 Subject: [PATCH 37/92] =?UTF-8?q?Update=20GitHub=20handle=20tbielawa?= =?UTF-8?q?=E2=86=92timlnx,=20name=20Tim=20Bielawa=E2=86=92Tim=20Case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all references to old GitHub handle and author name across source, docs, and tests. --- NEWS.rst | 62 +++++++++++++-------------- README.rst | 20 ++++----- bitmath/__init__.py | 4 +- bitmath/integrations/__init__.py | 2 +- bitmath/integrations/bmargparse.py | 2 +- docsite/source/conf.py | 8 ++-- docsite/source/contact.rst | 8 ++-- docsite/source/contributing.rst | 14 +++--- docsite/source/copyright.rst | 2 +- docsite/source/index.rst | 18 ++++---- docsite/source/module.rst | 4 +- docsite/source/real_life_examples.rst | 2 +- pyproject.toml | 6 +-- tests/__init__.py | 2 +- tests/test_argparse_type.py | 2 +- tests/test_basic_math.py | 2 +- tests/test_best_prefix_BASE.py | 2 +- tests/test_best_prefix_NIST.py | 2 +- tests/test_best_prefix_SI.py | 2 +- tests/test_bitwise_operations.py | 2 +- tests/test_cli.py | 2 +- tests/test_context_manager.py | 2 +- tests/test_file_size.py | 2 +- tests/test_future_math.py | 2 +- tests/test_init.py | 2 +- tests/test_instantiating.py | 2 +- tests/test_parse.py | 4 +- tests/test_properties.py | 2 +- tests/test_query_device_capacity.py | 2 +- tests/test_representation.py | 2 +- tests/test_rich_comparison.py | 2 +- tests/test_sorting.py | 2 +- tests/test_to_Type_conversion.py | 2 +- tests/test_to_built_in_conversion.py | 2 +- tests/test_utils.py | 2 +- 35 files changed, 99 insertions(+), 99 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 7f9e784..d2dcf35 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -87,7 +87,7 @@ bitmath-1.3.3-1 *************** `bitmath-1.3.3-1 -`__ was +`__ was published on 2018-08-23. @@ -101,7 +101,7 @@ updates to keep up with changing standards. Minor bug fixes and documentation tweaks are included as well. The project now has an official `Code of Conduct -`_, +`_, as well as issue and pull request templates. @@ -116,7 +116,7 @@ Changes `Alexander Kapshuna `_ has submitted `several fixes -`_ +`_ since the last release. Thanks! * Packaging requirements fixes @@ -124,11 +124,11 @@ since the last release. Thanks! * Subclassing and Type checking fixes/improvements `Marcus Kazmierczak `_ submitted `a fix -`_ for some broken +`_ for some broken documentation links. And `Dawid Gosławski `_ make sure our -`documentation `_ +`documentation `_ is accurate. @@ -141,7 +141,7 @@ bitmath-1.3.1-1 *************** `bitmath-1.3.1-1 -`__ was +`__ was published on 2016-07-17. Changes @@ -156,7 +156,7 @@ Changes * Inspired by `@darkblaze69 `_'s request in `#60 "Problems in parse_string" - `_. + `_. Project @@ -169,12 +169,12 @@ Project * Ubuntu builds inspired by `@hkraal `_ reporting an `installation issue - `_ on Ubuntu systems. + `_ on Ubuntu systems. **Documentation** -* `Cleaned up a lot `_ +* `Cleaned up a lot `_ of broken or re-directing links using output from the Sphinx ``make linkcheck`` command. @@ -185,7 +185,7 @@ bitmath-1.3.0-1 *************** `bitmath-1.3.0-1 -`__ was +`__ was published on 2016-01-08. Changes @@ -194,7 +194,7 @@ Changes **Bug Fixes** * Closed `GitHub Issue #55 - `_ "best_prefix for + `_ "best_prefix for negative values". Now :func:`bitmath.best_prefix` returns correct prefix units for negative values. Thanks `mbdm `_! @@ -206,7 +206,7 @@ bitmath-1.2.4-1 *************** `bitmath-1.2.4-1 -`__ was +`__ was published on 2015-11-30. Changes @@ -220,17 +220,17 @@ Changes * The :func:`bitmath.parse_string` function now can parse 'octet' based units. Enhancement requested in `#53 parse french unit names - `_ by `walidsa3d + `_ by `walidsa3d `_. **Bug Fixes** -* `#49 `_ - Fix handling +* `#49 `_ - Fix handling unicode input in the `bitmath.parse_string `__ function. Thanks `drewbrew `_! -* `#50 `_ - Update the +* `#50 `_ - Update the ``setup.py`` script to be python3.x compat. Thanks `ssut `_! @@ -264,7 +264,7 @@ bitmath-1.2.3-1 *************** `bitmath-1.2.3-1 -`__ was +`__ was published on 2015-01-03. Changes @@ -307,7 +307,7 @@ bitmath-1.2.0-1 *************** `bitmath-1.2.0-1 -`__ was +`__ was published on 2014-12-29. Changes @@ -323,7 +323,7 @@ Documentation ============= * The command-line ``bitmath`` tool now has a `proper manpage - `_ + `_ Project ======= @@ -340,10 +340,10 @@ bitmath-1.1.0-1 *************** `bitmath-1.1.0-1 -`_ was +`_ was published on 2014-12-20. -* `GitHub Milestone Tracker for 1.1.0 `_ +* `GitHub Milestone Tracker for 1.1.0 `_ Changes ======= @@ -351,12 +351,12 @@ Changes **Added Functionality** * New ``bitmath`` `command-line tool - `_ added. Provides + `_ added. Provides CLI access to basic unit conversion functions * New utility function `bitmath.parse_string `_ for parsing a human-readable string into a bitmath object. `Patch - submitted `_ by new + submitted `_ by new contributor `tonycpsu `_. .. _bitmath-1.0.8-1: @@ -365,10 +365,10 @@ bitmath-1.0.5-1 through 1.0.8-1 ******************************* `bitmath-1.0.8-1 -`__ was +`__ was published on 2014-08-14. -* `GitHub Milestone Tracker for 1.0.8 `_ +* `GitHub Milestone Tracker for 1.0.8 `_ Major Updates ============= @@ -382,17 +382,17 @@ Major Updates (`pkg info `_) * merged 6 `pull requests - `_ + `_ from 3 `contributors - `_ + `_ Bug Fixes ========= * fixed some math implementation bugs - * `commutative multiplication `_ - * `true division `_ + * `commutative multiplication `_ + * `true division `_ Changes ======= @@ -427,13 +427,13 @@ Project **Tests** * Test suite is now implemented using `Python virtualenv's - `_ + `_ for consistency across across platforms * Test suite now contains 150 unit tests. This is **110** more tests than the previous major release (`1.0.4-1 `__) * Test suite now runs on EPEL6 and EPEL7 * `Code coverage - `_ is stable + `_ is stable around 95-100% @@ -443,7 +443,7 @@ bitmath-1.0.4-1 *************** This is the first release of **bitmath**. `bitmath-1.0.4-1 -`__ was +`__ was published on 2014-03-20. Project diff --git a/README.rst b/README.rst index 151ad54..5878054 100644 --- a/README.rst +++ b/README.rst @@ -4,22 +4,22 @@ :height: 19 :width: 77 -.. image:: https://github.com/tbielawa/bitmath/actions/workflows/python.yml/badge.svg - :target: https://github.com/tbielawa/bitmath/actions/workflows/python.yml +.. image:: https://github.com/timlnx/bitmath/actions/workflows/python.yml/badge.svg + :target: https://github.com/timlnx/bitmath/actions/workflows/python.yml -.. image:: https://img.shields.io/github/issues/tbielawa/bitmath?style=flat-square - :target: https://github.com/tbielawa/bitmath/issues +.. image:: https://img.shields.io/github/issues/timlnx/bitmath?style=flat-square + :target: https://github.com/timlnx/bitmath/issues :alt: Open issues -.. image:: https://img.shields.io/github/issues-pr/tbielawa/bitmath?style=flat-square - :target: https://github.com/tbielawa/bitmath/pulls +.. image:: https://img.shields.io/github/issues-pr/timlnx/bitmath?style=flat-square + :target: https://github.com/timlnx/bitmath/pulls :alt: Open pull requests .. image:: https://img.shields.io/pypi/dm/bitmath?style=flat-square :target: https://pypistats.org/packages/bitmath :alt: PyPI - Package Downloads -.. image:: https://img.shields.io/github/stars/tbielawa/bitmath?style=flat-square +.. image:: https://img.shields.io/github/stars/timlnx/bitmath?style=flat-square :target: https://pypistats.org/packages/bitmath :alt: GitHub Project Popularity @@ -68,10 +68,10 @@ bytes. When you see file sizes or transfer rates in your web browser, most of the time what you're really seeing are the base-2 sizes/rates. **Don't Forget!** The source for bitmath `is available on GitHub -`_. +`_. And did we mention there's almost 200 unittests? `Check them out for -yourself `_. +yourself `_. Running the tests should be as simple as calling the ``ci`` target in the Makefile: ``make ci``. Please file a bug report if you run into @@ -109,7 +109,7 @@ You could also install bitmath from `PyPi **pip** installs need pip >= 1.1. To workaround this, `download bitmath `_, from PyPi and then ``pip install bitmath-x.y.z.tar.gz``. See `issue #57 - `_ + `_ for more information. diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 86d52f7..c25753f 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014-2016 Tim Bielawa +# Copyright © 2014-2016 Tim Case # See GitHub Contributors Graph for more information # # Permission is hereby granted, free of charge, to any person @@ -1228,7 +1228,7 @@ def query_device_capacity(device_fd): # The result should be true as long as your kernel # headers define BLKGETSIZE64 as a u64 type (please # file a bug report at - # https://github.com/tbielawa/bitmath/issues/new if + # https://github.com/timlnx/bitmath/issues/new if # this does *not* work for you) ], # func is how the final result is decided. Because the diff --git a/bitmath/integrations/__init__.py b/bitmath/integrations/__init__.py index 3a249ec..b44dea9 100644 --- a/bitmath/integrations/__init__.py +++ b/bitmath/integrations/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014-2016 Tim Bielawa +# Copyright © 2014-2016 Tim Case # See GitHub Contributors Graph for more information # # Permission is hereby granted, free of charge, to any person diff --git a/bitmath/integrations/bmargparse.py b/bitmath/integrations/bmargparse.py index 9e7715c..959e372 100644 --- a/bitmath/integrations/bmargparse.py +++ b/bitmath/integrations/bmargparse.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014-2016 Tim Bielawa +# Copyright © 2014-2016 Tim Case # See GitHub Contributors Graph for more information # # Permission is hereby granted, free of charge, to any person diff --git a/docsite/source/conf.py b/docsite/source/conf.py index d7f5637..79d49d1 100644 --- a/docsite/source/conf.py +++ b/docsite/source/conf.py @@ -48,7 +48,7 @@ # General information about the project. project = u'bitmath' -copyright = u'2014-2023, Tim Bielawa' +copyright = u'2014-2023, Tim Case' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -200,7 +200,7 @@ # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'bitmath.tex', u'bitmath Documentation', - u'Tim Bielawa', 'manual'), + u'Tim Case', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -230,7 +230,7 @@ # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'bitmath', u'bitmath Documentation', - [u'Tim Bielawa'], 1) + [u'Tim Case'], 1) ] # If true, show URL addresses after external links. @@ -244,7 +244,7 @@ # dir menu entry, description, category) texinfo_documents = [ ('index', 'bitmath', u'bitmath Documentation', - u'Tim Bielawa', 'bitmath', 'One line description of project.', + u'Tim Case', 'bitmath', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docsite/source/contact.rst b/docsite/source/contact.rst index 9d0b3ac..2d3a7d7 100644 --- a/docsite/source/contact.rst +++ b/docsite/source/contact.rst @@ -3,7 +3,7 @@ Contact ####### -Hi, I'm Tim Bielawa, the bitmath maintainer. Would you like to get in +Hi, I'm Tim Case, the bitmath maintainer. Would you like to get in touch? Maybe you want to peek at other stuff I'm working on? Go right ahead: @@ -18,7 +18,7 @@ ahead: Almost every project I work on, code or not, ends up on GitHub eventually. You can see what else I've been busy with on my profile. - * `GitHub: tbielawa `_ + * `GitHub: tbielawa `_ **Bugs/Issues/Requests** All contributions related directly to the bitmath project, i.e. bug @@ -30,7 +30,7 @@ ahead: **E-Mail** If you want to contact me directly, `clone the project - `_ and look at any of `my + `_ and look at any of `my bitmath commits - `_ + `_ to find my email address. diff --git a/docsite/source/contributing.rst b/docsite/source/contributing.rst index d2ded9e..074828e 100644 --- a/docsite/source/contributing.rst +++ b/docsite/source/contributing.rst @@ -19,7 +19,7 @@ Code of Conduct All persons submitting code or otherwise interacting with the bitmath project on GitHub must accept and abide by the terms of the `Code of Conduct -`_. +`_. .. _contributing_issue_reporting: @@ -30,8 +30,8 @@ Issue Reporting If you are encounter an issue with the bitmath library, please use the provided template. -* `Open a new issue `_ -* `View open issues `_ +* `Open a new issue `_ +* `View open issues `_ Code Style/Formatting @@ -85,13 +85,13 @@ For example:: Pull Requests ************* -After a `pull request `_ is +After a `pull request `_ is submitted on GitHub two automatic processes are started: -#. `Travis-CI `_ clones the +#. `Travis-CI `_ clones the new pull request and runs the :ref:`automated test suite `. -#. `Coveralls `_ clones +#. `Coveralls `_ clones the new pull request and determines if the request would increase or decrease the overall code test coverage. @@ -132,7 +132,7 @@ Components bitmath unit tests are integrated with/depend on the following items: -* `GitHub Actions `_ - +* `GitHub Actions `_ - GitHub Actions provideFree online `continuous integration` functionality for projects. Tests are ran automatically on every git commit. In the past we used Travis for this functionality. diff --git a/docsite/source/copyright.rst b/docsite/source/copyright.rst index f9e4f2c..bff7190 100644 --- a/docsite/source/copyright.rst +++ b/docsite/source/copyright.rst @@ -2,7 +2,7 @@ Copyright ######### The MIT License (MIT) -Copyright © 2014-2016 Tim Bielawa +Copyright © 2014-2016 Tim Case Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/docsite/source/index.rst b/docsite/source/index.rst index 0e94e43..3ff2f06 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -1,20 +1,20 @@ -.. image:: https://github.com/tbielawa/bitmath/actions/workflows/python.yml/badge.svg - :target: https://github.com/tbielawa/bitmath/actions/workflows/python.yml +.. image:: https://github.com/timlnx/bitmath/actions/workflows/python.yml/badge.svg + :target: https://github.com/timlnx/bitmath/actions/workflows/python.yml :alt: Build Status on GitHub -.. image:: https://img.shields.io/github/issues/tbielawa/bitmath?style=flat-square - :target: https://github.com/tbielawa/bitmath/issues +.. image:: https://img.shields.io/github/issues/timlnx/bitmath?style=flat-square + :target: https://github.com/timlnx/bitmath/issues :alt: Open Issues -.. image:: https://img.shields.io/github/issues-pr/tbielawa/bitmath?style=flat-square - :target: https://github.com/tbielawa/bitmath/pulls +.. image:: https://img.shields.io/github/issues-pr/timlnx/bitmath?style=flat-square + :target: https://github.com/timlnx/bitmath/pulls :alt: Open Pull Requests .. image:: https://img.shields.io/pypi/dm/bitmath?style=flat-square :target: https://pypistats.org/packages/bitmath :alt: PyPI - Package Popularity -.. image:: https://img.shields.io/github/stars/tbielawa/bitmath?style=flat-square +.. image:: https://img.shields.io/github/stars/timlnx/bitmath?style=flat-square :target: https://pypistats.org/packages/bitmath :alt: GitHub Project Popularity @@ -61,10 +61,10 @@ bytes. When you see file sizes or transfer rates in your web browser, most of the time what you're really seeing are the base-2 sizes/rates. **Don't Forget!** The source for bitmath `is available on GitHub -`_. +`_. And did we mention there's almost 200 unittests? `Check them out for -yourself `_. +yourself `_. * :ref:`Examples ` after the TOC. diff --git a/docsite/source/module.rst b/docsite/source/module.rst index 31240f8..935b421 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -122,7 +122,7 @@ bitmath.listdir() * This function does **not** return tuples for directory entities. Including directories in results is `scheduled for - introduction `_ + introduction `_ in an upcoming release. * Symlinks to **files** are followed automatically @@ -294,7 +294,7 @@ bitmath.parse_string() .. versionchanged:: 1.2.4 Added support for parsing *octet* units via issue `#53 - parse french units - `_. The `usage + `_. The `usage `_ of "octet" is still common in some `RFCs `_, as well diff --git a/docsite/source/real_life_examples.rst b/docsite/source/real_life_examples.rst index 597aa92..f42260a 100644 --- a/docsite/source/real_life_examples.rst +++ b/docsite/source/real_life_examples.rst @@ -388,7 +388,7 @@ Creating Download Progress Bars .. literalinclude:: ../../full_demo.py * View the the source for the `demo suite - `_ + `_ on GitHub diff --git a/pyproject.toml b/pyproject.toml index 3760e32..931c37d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ description = "Pythonic module for representing and manipulating file sizes with readme = "README.rst" requires-python = ">=3.11" authors = [ - { name = "Tim Bielawa", email = "timbielawa@gmail.com" }, + { name = "Tim Case", email = "timbielawa@gmail.com" }, ] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -44,8 +44,8 @@ classifiers = [ [project.urls] Homepage = "https://bitmath.readthedocs.io/en/latest/index.html" -"Bug Tracker" = "https://github.com/tbielawa/bitmath/issues" -"Git Repo" = "https://github.com/tbielawa/bitmath" +"Bug Tracker" = "https://github.com/timlnx/bitmath/issues" +"Git Repo" = "https://github.com/timlnx/bitmath" [project.scripts] bitmath = "bitmath:cli_script" diff --git a/tests/__init__.py b/tests/__init__.py index c2cd580..8bb658b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_argparse_type.py b/tests/test_argparse_type.py index 14ec4f6..35c169b 100644 --- a/tests/test_argparse_type.py +++ b/tests/test_argparse_type.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_basic_math.py b/tests/test_basic_math.py index a938e86..4ed97b0 100644 --- a/tests/test_basic_math.py +++ b/tests/test_basic_math.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_best_prefix_BASE.py b/tests/test_best_prefix_BASE.py index aa98396..86fc34d 100644 --- a/tests/test_best_prefix_BASE.py +++ b/tests/test_best_prefix_BASE.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_best_prefix_NIST.py b/tests/test_best_prefix_NIST.py index d979706..684b479 100644 --- a/tests/test_best_prefix_NIST.py +++ b/tests/test_best_prefix_NIST.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_best_prefix_SI.py b/tests/test_best_prefix_SI.py index c6d5ce1..b51886e 100644 --- a/tests/test_best_prefix_SI.py +++ b/tests/test_best_prefix_SI.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_bitwise_operations.py b/tests/test_bitwise_operations.py index a6a9353..f7718c7 100644 --- a/tests/test_bitwise_operations.py +++ b/tests/test_bitwise_operations.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_cli.py b/tests/test_cli.py index 6f46a4b..bd29ccb 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_context_manager.py b/tests/test_context_manager.py index 612ed44..8ee1f7f 100644 --- a/tests/test_context_manager.py +++ b/tests/test_context_manager.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_file_size.py b/tests/test_file_size.py index 1a4b7f8..d9d124a 100644 --- a/tests/test_file_size.py +++ b/tests/test_file_size.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_future_math.py b/tests/test_future_math.py index c644072..c86c280 100644 --- a/tests/test_future_math.py +++ b/tests/test_future_math.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_init.py b/tests/test_init.py index aa290f9..6869f82 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2015 Tim Bielawa +# Copyright © 2015 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_instantiating.py b/tests/test_instantiating.py index 4723f25..2063656 100644 --- a/tests/test_instantiating.py +++ b/tests/test_instantiating.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_parse.py b/tests/test_parse.py index 7c21405..5c3b01e 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files @@ -275,7 +275,7 @@ def test_parse_string_unsafe_request_NIST(self): def test_parse_string_unsafe_github_issue_60(self): """parse_string_unsafe can parse the examples reported in issue #60 -https://github.com/tbielawa/bitmath/issues/60 +https://github.com/timlnx/bitmath/issues/60 """ issue_input1 = '7.5KB' _parsed1 = bitmath.parse_string_unsafe(issue_input1) diff --git a/tests/test_properties.py b/tests/test_properties.py index 74e9ddc..24818c3 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_query_device_capacity.py b/tests/test_query_device_capacity.py index bda2e7e..4bd4178 100644 --- a/tests/test_query_device_capacity.py +++ b/tests/test_query_device_capacity.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2015 Tim Bielawa +# Copyright © 2015 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_representation.py b/tests/test_representation.py index 1403eb0..09b3390 100644 --- a/tests/test_representation.py +++ b/tests/test_representation.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_rich_comparison.py b/tests/test_rich_comparison.py index c8f2bcd..aaa6f4d 100644 --- a/tests/test_rich_comparison.py +++ b/tests/test_rich_comparison.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_sorting.py b/tests/test_sorting.py index f036cfa..72c1c64 100644 --- a/tests/test_sorting.py +++ b/tests/test_sorting.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_to_Type_conversion.py b/tests/test_to_Type_conversion.py index be558c2..4759e87 100644 --- a/tests/test_to_Type_conversion.py +++ b/tests/test_to_Type_conversion.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_to_built_in_conversion.py b/tests/test_to_built_in_conversion.py index 0f29066..03e02e4 100644 --- a/tests/test_to_built_in_conversion.py +++ b/tests/test_to_built_in_conversion.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files diff --git a/tests/test_utils.py b/tests/test_utils.py index 4b8d70f..6c300db 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) # -# Copyright © 2014 Tim Bielawa +# Copyright © 2014 Tim Case # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files From f0ab3c237a27b4060171cd1acc63b7eaeb399bde Mon Sep 17 00:00:00 2001 From: Tim Case Date: Thu, 16 Apr 2026 23:05:04 -0500 Subject: [PATCH 38/92] =?UTF-8?q?Fix=20contact.rst=20link=20text:=20tbiela?= =?UTF-8?q?wa=E2=86=92timlnx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docsite/source/contact.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docsite/source/contact.rst b/docsite/source/contact.rst index 2d3a7d7..ce6e5af 100644 --- a/docsite/source/contact.rst +++ b/docsite/source/contact.rst @@ -18,7 +18,7 @@ ahead: Almost every project I work on, code or not, ends up on GitHub eventually. You can see what else I've been busy with on my profile. - * `GitHub: tbielawa `_ + * `GitHub: timlnx `_ **Bugs/Issues/Requests** All contributions related directly to the bitmath project, i.e. bug From 74a06fda936abdbb017901c6e0e00e64e5dff6ac Mon Sep 17 00:00:00 2001 From: Tim Case Date: Thu, 16 Apr 2026 23:06:41 -0500 Subject: [PATCH 39/92] Fix conf.py.in: drop deprecated get_html_theme_path(), update author name sphinx_rtd_theme >= 1.0 no longer requires html_theme_path to be set. --- docsite/source/conf.py.in | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docsite/source/conf.py.in b/docsite/source/conf.py.in index bf157ef..18c11c8 100644 --- a/docsite/source/conf.py.in +++ b/docsite/source/conf.py.in @@ -48,7 +48,7 @@ master_doc = 'index' # General information about the project. project = u'bitmath' -copyright = u'2014-2023, Tim Bielawa' +copyright = u'2014-2023, Tim Case' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -105,7 +105,6 @@ pygments_style = 'sphinx' # a list of builtin themes. if _RTD_THEME: html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] else: html_theme = 'default' @@ -200,7 +199,7 @@ latex_elements = { # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'bitmath.tex', u'bitmath Documentation', - u'Tim Bielawa', 'manual'), + u'Tim Case', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -230,7 +229,7 @@ latex_documents = [ # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'bitmath', u'bitmath Documentation', - [u'Tim Bielawa'], 1) + [u'Tim Case'], 1) ] # If true, show URL addresses after external links. @@ -244,7 +243,7 @@ man_pages = [ # dir menu entry, description, category) texinfo_documents = [ ('index', 'bitmath', u'bitmath Documentation', - u'Tim Bielawa', 'bitmath', 'One line description of project.', + u'Tim Case', 'bitmath', 'One line description of project.', 'Miscellaneous'), ] From a1481e7c0d2b4861334e0afa366000fbed14c11c Mon Sep 17 00:00:00 2001 From: Tim Case Date: Thu, 16 Apr 2026 23:08:51 -0500 Subject: [PATCH 40/92] Fix module.rst: replace invalid :option: role with literal du -B --- docsite/source/conf.py | 1 - docsite/source/module.rst | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docsite/source/conf.py b/docsite/source/conf.py index 79d49d1..e51e97e 100644 --- a/docsite/source/conf.py +++ b/docsite/source/conf.py @@ -105,7 +105,6 @@ # a list of builtin themes. if _RTD_THEME: html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] else: html_theme = 'default' diff --git a/docsite/source/module.rst b/docsite/source/module.rst index 935b421..affcf2a 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -902,7 +902,7 @@ argument or option should be interpreted as. A feature found in many command-line utilities is the ability to specify some kind of file size using a string which roughly describes some kind of parameter. For example, let's look at the - :program:`du` (disk usage) command. Invoking it as :option:`du -B` + :program:`du` (disk usage) command. Invoking it as ``du -B`` allows one to specify a desired block-size scaling factor in printed results. From e6e53772c984867b94a1c7c864c12da182e50a84 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Thu, 16 Apr 2026 23:11:27 -0500 Subject: [PATCH 41/92] make docs: auto-install doc-requirements.txt before building Ensures sphinx_rtd_theme is always present for local doc builds. --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 5517632..fc47cc9 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ MANPAGES := bitmath.1 # Documentation. YAY!!!! docs: conf.py $(MANPAGES) docsite/source/index.rst + pip3 install -q -r doc-requirements.txt cd docsite; make html; cd - # Add examples to the RTD docs by taking it from the README From 1be004941e923e5cd56b42586ca5178cecfc2142 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Thu, 16 Apr 2026 23:13:27 -0500 Subject: [PATCH 42/92] make docs: use bitmath2 Python 3.12 venv for doc builds Adds docs-venv target that creates the venv on first run and installs doc-requirements.txt into it. The docs target activates it before invoking sphinx. Venv directory added to .gitignore. --- .gitignore | 1 + Makefile | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a9034a3..93bb6e3 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ docsite/build/html docsite/build/doctrees bitmathenv3 bitmathenv2 +bitmath2 diff --git a/Makefile b/Makefile index fc47cc9..c5ccaf6 100644 --- a/Makefile +++ b/Makefile @@ -45,9 +45,17 @@ MANPAGES := bitmath.1 ###################################################################### # Documentation. YAY!!!! -docs: conf.py $(MANPAGES) docsite/source/index.rst - pip3 install -q -r doc-requirements.txt - cd docsite; make html; cd - +DOCSVENV := bitmath2 + +docs-venv: + @if [ ! -d "$(DOCSVENV)" ]; then \ + echo "Creating docs virtualenv '$(DOCSVENV)' with Python 3.12..."; \ + python3.12 -m venv $(DOCSVENV); \ + fi + . $(DOCSVENV)/bin/activate && pip install -q -r doc-requirements.txt + +docs: docs-venv conf.py $(MANPAGES) docsite/source/index.rst + . $(DOCSVENV)/bin/activate && cd docsite && make html # Add examples to the RTD docs by taking it from the README docsite/source/index.rst: docsite/source/index.rst.in README.rst VERSION From 5abeefaff272e8ccb2d63701134c2e8c85a5e33b Mon Sep 17 00:00:00 2001 From: Tim Case Date: Thu, 16 Apr 2026 23:25:07 -0500 Subject: [PATCH 43/92] Testing testing --- .github/workflows/python.yml | 2 +- bitmath/__init__.py | 6 +++--- requirements.txt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 5a55ecf..43ba22f 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -31,7 +31,7 @@ jobs: - name: Pre-Tests code smell validation run: | pycodestyle -v --ignore=E501,E722 bitmath/__init__.py tests/*.py - pyflakes bitmath/__init__.py tests/*.py + flake8 --select=F bitmath/__init__.py tests/*.py - name: Run Unit Tests run: | diff --git a/bitmath/__init__.py b/bitmath/__init__.py index c25753f..0381eb6 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -304,7 +304,7 @@ def unit(self): >>> Byte(1.1).unit == 'Bytes' >>> Gb(2).unit == 'Gbs' """ - global format_plural + global format_plural # noqa: F841 if self.prefix_value == 1: # If it's a '1', return it singular, no matter what @@ -383,12 +383,12 @@ def from_other(cls, item): def __repr__(self): """Representation of this object as you would expect to see in an interpreter""" - global _FORMAT_REPR + global _FORMAT_REPR # noqa: F841 return self.format(_FORMAT_REPR) def __str__(self): """String representation of this object""" - global format_string + global format_string # noqa: F841 return self.format(format_string) def format(self, fmt): diff --git a/requirements.txt b/requirements.txt index c98a1c0..ae1ba7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -pyflakes +flake8 pycodestyle pytest pytest-cov From 351bbe3a9f2d1c41bad47b7c09108d50e2fab2b4 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 00:53:07 -0500 Subject: [PATCH 44/92] =?UTF-8?q?Fix=20noqa=20codes:=20F841=E2=86=92F824?= =?UTF-8?q?=20for=20unused=20global=20declarations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit flake8 reports unused global as F824, not F841. --- bitmath/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 0381eb6..89cceb4 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -304,7 +304,7 @@ def unit(self): >>> Byte(1.1).unit == 'Bytes' >>> Gb(2).unit == 'Gbs' """ - global format_plural # noqa: F841 + global format_plural # noqa: F824 if self.prefix_value == 1: # If it's a '1', return it singular, no matter what @@ -383,12 +383,12 @@ def from_other(cls, item): def __repr__(self): """Representation of this object as you would expect to see in an interpreter""" - global _FORMAT_REPR # noqa: F841 + global _FORMAT_REPR # noqa: F824 return self.format(_FORMAT_REPR) def __str__(self): """String representation of this object""" - global format_string # noqa: F841 + global format_string # noqa: F824 return self.format(format_string) def format(self, fmt): From 7d1d7ea2619866add9cc721a4827f8cd877aadd9 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 01:00:23 -0500 Subject: [PATCH 45/92] Add SPDX-License-Identifier: MIT to all source files --- bitmath/__init__.py | 1 + bitmath/integrations/__init__.py | 1 + bitmath/integrations/bmargparse.py | 1 + full_demo.py | 1 + tests/__init__.py | 1 + tests/test_argparse_type.py | 1 + tests/test_basic_math.py | 1 + tests/test_best_prefix_BASE.py | 1 + tests/test_best_prefix_NIST.py | 1 + tests/test_best_prefix_SI.py | 1 + tests/test_bitwise_operations.py | 1 + tests/test_cli.py | 1 + tests/test_context_manager.py | 1 + tests/test_file_size.py | 1 + tests/test_future_math.py | 1 + tests/test_init.py | 1 + tests/test_instantiating.py | 1 + tests/test_parse.py | 1 + tests/test_properties.py | 1 + tests/test_query_device_capacity.py | 1 + tests/test_representation.py | 1 + tests/test_rich_comparison.py | 1 + tests/test_sorting.py | 1 + tests/test_to_Type_conversion.py | 1 + tests/test_to_built_in_conversion.py | 1 + tests/test_utils.py | 1 + 26 files changed, 26 insertions(+) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 89cceb4..8bf6e57 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014-2016 Tim Case diff --git a/bitmath/integrations/__init__.py b/bitmath/integrations/__init__.py index b44dea9..6b7b09b 100644 --- a/bitmath/integrations/__init__.py +++ b/bitmath/integrations/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014-2016 Tim Case diff --git a/bitmath/integrations/bmargparse.py b/bitmath/integrations/bmargparse.py index 959e372..37efcf8 100644 --- a/bitmath/integrations/bmargparse.py +++ b/bitmath/integrations/bmargparse.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014-2016 Tim Case diff --git a/full_demo.py b/full_demo.py index 6a492f6..a066da9 100755 --- a/full_demo.py +++ b/full_demo.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# SPDX-License-Identifier: MIT from __future__ import print_function import logging import time diff --git a/tests/__init__.py b/tests/__init__.py index 8bb658b..eda90bc 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_argparse_type.py b/tests/test_argparse_type.py index 35c169b..0fecb93 100644 --- a/tests/test_argparse_type.py +++ b/tests/test_argparse_type.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_basic_math.py b/tests/test_basic_math.py index 4ed97b0..aa6e23c 100644 --- a/tests/test_basic_math.py +++ b/tests/test_basic_math.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_best_prefix_BASE.py b/tests/test_best_prefix_BASE.py index 86fc34d..582c64d 100644 --- a/tests/test_best_prefix_BASE.py +++ b/tests/test_best_prefix_BASE.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_best_prefix_NIST.py b/tests/test_best_prefix_NIST.py index 684b479..ce2fbe1 100644 --- a/tests/test_best_prefix_NIST.py +++ b/tests/test_best_prefix_NIST.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_best_prefix_SI.py b/tests/test_best_prefix_SI.py index b51886e..7cfe15e 100644 --- a/tests/test_best_prefix_SI.py +++ b/tests/test_best_prefix_SI.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_bitwise_operations.py b/tests/test_bitwise_operations.py index f7718c7..96cdae5 100644 --- a/tests/test_bitwise_operations.py +++ b/tests/test_bitwise_operations.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_cli.py b/tests/test_cli.py index bd29ccb..0a0adf1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_context_manager.py b/tests/test_context_manager.py index 8ee1f7f..a758060 100644 --- a/tests/test_context_manager.py +++ b/tests/test_context_manager.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_file_size.py b/tests/test_file_size.py index d9d124a..ce740cd 100644 --- a/tests/test_file_size.py +++ b/tests/test_file_size.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_future_math.py b/tests/test_future_math.py index c86c280..16c7e07 100644 --- a/tests/test_future_math.py +++ b/tests/test_future_math.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_init.py b/tests/test_init.py index 6869f82..8b48bac 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2015 Tim Case diff --git a/tests/test_instantiating.py b/tests/test_instantiating.py index 2063656..3de2990 100644 --- a/tests/test_instantiating.py +++ b/tests/test_instantiating.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_parse.py b/tests/test_parse.py index 5c3b01e..7087e97 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_properties.py b/tests/test_properties.py index 24818c3..7506216 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_query_device_capacity.py b/tests/test_query_device_capacity.py index 4bd4178..0ab124b 100644 --- a/tests/test_query_device_capacity.py +++ b/tests/test_query_device_capacity.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2015 Tim Case diff --git a/tests/test_representation.py b/tests/test_representation.py index 09b3390..f845ba1 100644 --- a/tests/test_representation.py +++ b/tests/test_representation.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_rich_comparison.py b/tests/test_rich_comparison.py index aaa6f4d..3eae0d2 100644 --- a/tests/test_rich_comparison.py +++ b/tests/test_rich_comparison.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_sorting.py b/tests/test_sorting.py index 72c1c64..5bcf08b 100644 --- a/tests/test_sorting.py +++ b/tests/test_sorting.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_to_Type_conversion.py b/tests/test_to_Type_conversion.py index 4759e87..818fd57 100644 --- a/tests/test_to_Type_conversion.py +++ b/tests/test_to_Type_conversion.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_to_built_in_conversion.py b/tests/test_to_built_in_conversion.py index 03e02e4..cbc206f 100644 --- a/tests/test_to_built_in_conversion.py +++ b/tests/test_to_built_in_conversion.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case diff --git a/tests/test_utils.py b/tests/test_utils.py index 6c300db..9882cb4 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT # The MIT License (MIT) # # Copyright © 2014 Tim Case From 7df4571bfb88bf419b8ae23e1bc1dc7801f68ffa Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 01:04:54 -0500 Subject: [PATCH 46/92] Remove Python 2 compatibility code - Drop __div__ and __rdiv__ (Python 2 division protocol); collapse logic into __truediv__/__rtruediv__ - Remove stale __future__.division docstring block - tests: drop from __future__ import division (no-op on Python 3) - tests: remove long/sys.version compat shim and test_to_long (long doesn't exist in Python 3) - tests: simplify mock and contextlib imports to direct Python 3 stdlib imports --- bitmath/__init__.py | 18 ++---------------- tests/test_future_math.py | 5 ++--- tests/test_query_device_capacity.py | 28 +++++++--------------------- tests/test_to_built_in_conversion.py | 12 +----------- 4 files changed, 12 insertions(+), 51 deletions(-) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 8bf6e57..93d1037 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -768,18 +768,12 @@ def __mul__(self, other): _self = self.prefix_value * self._base ** self._power return (type(self))(bytes=_other * _self) - """The division operator (/) is implemented by these methods. The -__truediv__() method is used when __future__.division is in effect, -otherwise __div__() is used. If only one of these two methods is -defined, the object will not support division in the alternate -context; TypeError will be raised instead.""" - - def __div__(self, other): + def __truediv__(self, other): """Division: Supported operations with result types: - bm1 / bm2 = num - bm / num = bm -- num / bm = num (see rdiv) +- num / bm = num (see rtruediv) """ if isinstance(other, numbers.Number): # bm / num @@ -789,10 +783,6 @@ def __div__(self, other): # bm1 / bm2 return self._byte_value / float(other.bytes) - def __truediv__(self, other): - # num / bm - return self.__div__(other) - # def __floordiv__(self, other): # return NotImplemented @@ -833,10 +823,6 @@ def __rmul__(self, other): # num * bm = bm return self * other - def __rdiv__(self, other): - # num / bm = num - return other / float(self.value) - def __rtruediv__(self, other): # num / bm = num return other / float(self.value) diff --git a/tests/test_future_math.py b/tests/test_future_math.py index 16c7e07..3ba1ba6 100644 --- a/tests/test_future_math.py +++ b/tests/test_future_math.py @@ -31,7 +31,6 @@ Reference: http://legacy.python.org/dev/peps/pep-0238/ """ -from __future__ import division from . import TestCase import bitmath @@ -62,9 +61,9 @@ def test_number_div_bitmath_is_number(self): self.assertIs(type(result), float) def test_number_truediv_bitmath_is_number(self): - """truediv: number // bitmath = number""" + """truediv: number / bitmath = number""" num1 = 2 bm1 = bitmath.KiB(1) - result = bm1.__rdiv__(num1) + result = bm1.__rtruediv__(num1) self.assertEqual(result, 2.0) self.assertIs(type(result), float) diff --git a/tests/test_query_device_capacity.py b/tests/test_query_device_capacity.py index 0ab124b..d15c0e3 100644 --- a/tests/test_query_device_capacity.py +++ b/tests/test_query_device_capacity.py @@ -31,29 +31,15 @@ from . import TestCase import bitmath -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock import struct +from contextlib import ExitStack, contextmanager -try: - # Python 3.3+ - from contextlib import ExitStack, contextmanager -except ImportError: - # Python 2.x - from contextlib import nested -else: - @contextmanager - def nested(*contexts): - """Emulation of contextlib.nested in terms of ExitStack - - Has the problems for which "nested" was removed from Python; see: - https://docs.python.org/2/library/contextlib.html#contextlib.nested - But for mock.patch, these do not matter. - """ - with ExitStack() as stack: - yield tuple(stack.enter_context(c) for c in contexts) + +@contextmanager +def nested(*contexts): + with ExitStack() as stack: + yield tuple(stack.enter_context(c) for c in contexts) device_file_no = mock.Mock(return_value=4) device = mock.MagicMock('file') diff --git a/tests/test_to_built_in_conversion.py b/tests/test_to_built_in_conversion.py index cbc206f..4450b63 100644 --- a/tests/test_to_built_in_conversion.py +++ b/tests/test_to_built_in_conversion.py @@ -26,16 +26,11 @@ """ -Test to verify the int/float/long conversions work correctly +Test to verify the int/float conversions work correctly """ from . import TestCase import bitmath -import sys - -# Python 3.x compat -if sys.version > '3': - long = int class TestToBuiltInConversion(TestCase): @@ -49,8 +44,3 @@ def test_to_float(self): """float(bitmath) returns a float""" gib = bitmath.GiB(1337.8) self.assertIs(type(float(gib)), float) - - def test_to_long(self): - """long(bitmath) returns a long""" - gib = bitmath.GiB(1337.8) - self.assertIs(type(long(gib)), long) From e386f5be76f719436038055ed433f00a2f0a3076 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 01:12:27 -0500 Subject: [PATCH 47/92] Fix built-in sum() for bitmath objects; add bitmath.sum() - __radd__ now returns self when other==0 (identity element pattern), enabling built-in sum() to work correctly with bitmath iterables - Add bitmath.sum() as an explicit alternative with start parameter - Update mixed_math.rst to document the 0+bm exception - Tests: add zero+bitmath=bitmath case, 7 tests for bitmath.sum() --- bitmath/__init__.py | 22 +++++++- docsite/source/appendices/mixed_math.rst | 20 ++++++- tests/test_basic_math.py | 7 +++ tests/test_sum.py | 71 ++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 tests/test_sum.py diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 93d1037..87e96c5 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -65,7 +65,7 @@ 'Mib', 'Gib', 'Tib', 'Pib', 'Eib', 'kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb', 'getsize', 'listdir', 'format', 'format_string', 'format_plural', 'parse_string', 'parse_string_unsafe', - 'ALL_UNIT_TYPES', 'NIST', 'NIST_PREFIXES', 'NIST_STEPS', + 'sum', 'ALL_UNIT_TYPES', 'NIST', 'NIST_PREFIXES', 'NIST_STEPS', 'SI', 'SI_PREFIXES', 'SI_STEPS'] #: A list of all the valid prefix unit types. Mostly for reference, @@ -812,6 +812,9 @@ def __truediv__(self, other): """ def __radd__(self, other): + # Special case: 0 + bm = bm (identity element, enables built-in sum()) + if other == 0: + return self # num + bm = num return other + self.value @@ -1519,6 +1522,23 @@ def parse_string_unsafe(s, system=SI): return unit_class(float(val)) +def sum(iterable, start=None): + """Sum an iterable of bitmath instances correctly. + +The built-in sum() starts from integer 0, which causes __radd__ to +return a plain float instead of a bitmath instance. This function +starts from Byte(0) so all additions go through the bitmath __add__ +path with proper unit conversion. + +- bitmath.sum([Byte(1), MiB(1), GiB(1)]) -> Byte(1074790401.0) +- bitmath.sum([KiB(1), KiB(2)], start=MiB(0)) -> MiB(0.00292...) +""" + result = Byte(0) if start is None else start + for item in iterable: + result = result + item + return result + + ###################################################################### # Contxt Managers @contextlib.contextmanager diff --git a/docsite/source/appendices/mixed_math.rst b/docsite/source/appendices/mixed_math.rst index a7e8cec..7911147 100644 --- a/docsite/source/appendices/mixed_math.rst +++ b/docsite/source/appendices/mixed_math.rst @@ -108,10 +108,28 @@ This describes the behavior of addition and subtraction operations where one operand is a bitmath type and the other is a number type. Mixed-math addition and subtraction **always** return a type from the -:py:mod:`numbers` family (integer, float, long, etc...). This rule is +:py:mod:`numbers` family (integer, float, etc...). This rule is true regardless of the placement of the operands, with respect to the operator. +.. note:: + + **Exception: zero as the left operand.** When the left operand is + exactly ``0`` (e.g. ``0 + KiB(1)``), the result is the bitmath + instance itself rather than a number. This special case exists so + that Python's built-in :py:func:`sum` function works correctly with + iterables of bitmath objects, since ``sum()`` starts accumulation + from ``0`` by default. + + .. code-block:: python + + >>> import bitmath + >>> sum([bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)]) + Byte(1074790401.0) + + For all non-zero numeric left operands the documented behaviour + (returning a number) is unchanged. + **Discussion:** Why do ``100 - KiB(90)`` and ``KiB(100) - 90`` both yield a result of ``10.0`` and not another bitmath instance, such as ``KiB(10.0)``? diff --git a/tests/test_basic_math.py b/tests/test_basic_math.py index aa6e23c..d0705c1 100644 --- a/tests/test_basic_math.py +++ b/tests/test_basic_math.py @@ -126,6 +126,13 @@ def test_number_add_bitmath_is_number(self): self.assertEqual(result, 3.0) self.assertIs(type(result), float) + def test_zero_add_bitmath_is_bitmath(self): + """0 + bitmath = bitmath (identity element enables built-in sum())""" + bm1 = bitmath.KiB(1) + result = 0 + bm1 + self.assertIsInstance(result, bitmath.Bitmath) + self.assertEqual(result, bm1) + ################################################################## # sub def test_bitmath_sub_bitmath_is_bitmath(self): diff --git a/tests/test_sum.py b/tests/test_sum.py new file mode 100644 index 0000000..0631331 --- /dev/null +++ b/tests/test_sum.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# The MIT License (MIT) +# +# Copyright © 2014 Tim Case +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +Tests for bitmath.sum() - issue #103 +""" + +from . import TestCase +import builtins +import bitmath + + +class TestSum(TestCase): + def test_sum_mixed_units_equals_manual_addition(self): + """bitmath.sum() matches manual chained addition for mixed units""" + items = [bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)] + expected = bitmath.Byte(1) + bitmath.MiB(1) + bitmath.GiB(1) + self.assertEqual(bitmath.sum(items), expected) + + def test_sum_returns_bitmath_instance(self): + """bitmath.sum() returns a bitmath instance, not a float""" + result = bitmath.sum([bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)]) + self.assertIsInstance(result, bitmath.Bitmath) + + def test_sum_builtin_now_works(self): + """built-in sum() now gives correct result via __radd__ identity fix""" + items = [bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)] + self.assertEqual(builtins.sum(items), bitmath.sum(items)) + + def test_sum_empty_iterable_returns_byte_zero(self): + """bitmath.sum([]) returns Byte(0) by default""" + result = bitmath.sum([]) + self.assertEqual(result, bitmath.Byte(0)) + + def test_sum_empty_iterable_with_custom_start(self): + """bitmath.sum([], start=MiB(0)) returns the start value""" + result = bitmath.sum([], start=bitmath.MiB(0)) + self.assertEqual(result, bitmath.MiB(0)) + + def test_sum_custom_start_unit(self): + """bitmath.sum() with a custom start unit returns that unit type""" + result = bitmath.sum([bitmath.KiB(1), bitmath.KiB(1)], start=bitmath.MiB(0)) + self.assertIsInstance(result, bitmath.MiB) + + def test_sum_same_units(self): + """bitmath.sum() works correctly for a list of same-unit instances""" + result = bitmath.sum([bitmath.KiB(1), bitmath.KiB(2), bitmath.KiB(3)]) + self.assertEqual(result, bitmath.KiB(6)) From 2a82974bf180f7476683d062763a4f68f3e006cb Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 01:16:53 -0500 Subject: [PATCH 48/92] Fix docs and internal docstring errors found during math rules audit - mixed_math.rst: correct KiB(43)/10 example (KiB(4.3) not KiB(4.2998046875)) - __init__.py: fix __mul__ docstring (num*bm=bm not num*bm=num) --- bitmath/__init__.py | 2 +- docsite/source/appendices/mixed_math.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 87e96c5..d6e219d 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -756,7 +756,7 @@ def __mul__(self, other): - bm1 * bm2 = bm1 - bm * num = bm -- num * bm = num (see rmul) +- num * bm = bm (see rmul) """ if isinstance(other, numbers.Number): # bm * num diff --git a/docsite/source/appendices/mixed_math.rst b/docsite/source/appendices/mixed_math.rst index 7911147..fc3614d 100644 --- a/docsite/source/appendices/mixed_math.rst +++ b/docsite/source/appendices/mixed_math.rst @@ -226,7 +226,7 @@ bitmath), the intention of ``MiB(100) / 10)`` is to separate .. code-block:: python In [4]: KiB(43) / 10 - Out[4]: KiB(4.2998046875) + Out[4]: KiB(4.3) The reverse operation does not maintain semantic validity. Stated differently, it does not make logical sense to divide a constant by a From a60e651a03afa09205d734e391baf9d47ae11fd8 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 07:25:55 -0500 Subject: [PATCH 49/92] Add __floor__, __ceil__, __round__ to Bitmath; document float semantics Implements math.floor(), math.ceil(), and round() support on all bitmath instances. Each returns a new instance of the same type with the prefix value rounded accordingly. Documents the float-by-default design philosophy in mixed_math.rst with a new 'Design Philosophy: Floating-Point Measurements' section, and expands instances.rst with a 'Rounding and Integer Conversion' section. Adds a cross-reference from BitMathInstance.bits to the Rules of Math appendix. Fixes #97 --- bitmath/__init__.py | 11 ++++ docsite/source/appendices/mixed_math.rst | 62 +++++++++++++++++++++ docsite/source/instances.rst | 70 +++++++++++++++++++++++- 3 files changed, 140 insertions(+), 3 deletions(-) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index d6e219d..a66f327 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -851,6 +851,17 @@ def __float__(self): """Return this instances prefix unit as a floating point number""" return float(self.prefix_value) + def __floor__(self): + return (type(self))(math.floor(self.prefix_value)) + + def __ceil__(self): + return (type(self))(math.ceil(self.prefix_value)) + + def __round__(self, ndigits=None): + if ndigits is None: + return (type(self))(round(self.prefix_value)) + return (type(self))(round(self.prefix_value, ndigits)) + ################################################################## # Bitwise operations ################################################################## diff --git a/docsite/source/appendices/mixed_math.rst b/docsite/source/appendices/mixed_math.rst index fc3614d..44e1b2d 100644 --- a/docsite/source/appendices/mixed_math.rst +++ b/docsite/source/appendices/mixed_math.rst @@ -239,6 +239,68 @@ yourself what you would expect to get if you did this: +Design Philosophy: Floating-Point Measurements +=============================================== + +bitmath represents sizes as **floating-point measurements**, not as +discrete counts of hardware bits. This is an intentional design choice. + +A file reported as ``1.7 GiB`` is a *measurement* — the same way +``2.3 miles`` or ``1.7 liters`` are measurements. Physical storage is +discrete (you cannot store half a bit), but the *measurement* of +storage is legitimately continuous. Fractional values appear naturally +in division, unit conversion chains, and proportional calculations: + +.. code-block:: python + + >>> KiB(1) / 3 + KiB(0.3333333333333333) + + >>> MiB(1).to_Bit() + Bit(8388608.0) + + >>> KiB(1/3).to_Bit() + Bit(2730.6666666666665) + +The last example is not a bug. The fractional bit count is the faithful +representation of a fractional byte input. If you need integer results, +Python's built-in :py:func:`math.floor`, :py:func:`math.ceil`, and +:py:func:`round` all work on bitmath instances and return an instance +of the same type: + +.. code-block:: python + + >>> import math + >>> math.floor(KiB(1) / 3) + KiB(0) + + >>> math.ceil(KiB(1) / 3) + KiB(1) + + >>> round(MiB(1.75)) + MiB(2) + +.. warning:: + + Rounding intermediate results is a lossy operation. + ``math.floor(GiB(10) / 3) * 3`` yields ``GiB(9)``, not + ``GiB(10)``. Only round at the **final** output step. + +**Floating-point accumulation:** Because bitmath uses IEEE 754 64-bit +floats internally, arithmetic across many operations may accumulate +small rounding errors — identical to ordinary Python float arithmetic. +For the file-size domain (values up to exabyte scale), 64-bit float +provides approximately 15 significant decimal digits of precision, +which is sufficient for all practical purposes. If exact integer +semantics are required at the byte level, use ``int(instance.bytes)`` +to work in raw integers. + +.. seealso:: + + :ref:`instances_rounding` — instance methods for rounding and + integer conversion. + + Footnotes ========= diff --git a/docsite/source/instances.rst b/docsite/source/instances.rst index f0e702e..1d4aeda 100644 --- a/docsite/source/instances.rst +++ b/docsite/source/instances.rst @@ -44,7 +44,7 @@ bitmath objects have several instance attributes: .. py:attribute:: BitMathInstance.bits - The number of bits in the object + The number of bits in the object, as a floating-point value. .. code-block:: python @@ -52,15 +52,24 @@ bitmath objects have several instance attributes: >>> print(b.bits) 10696.0 + .. note:: + + Bit values are always floating-point. A whole-number input like + ``Byte(1337)`` produces an exact float (``10696.0``), but inputs + involving division or fractional bytes will produce fractional + bit counts. Use ``int(instance.bits)`` or :py:func:`math.floor` + to obtain an integer when needed. See :ref:`appendix_math` for + the design rationale behind floating-point values. + .. py:attribute:: BitMathInstance.bytes - The number of bytes in the object + The number of bytes in the object, as a floating-point value. .. code-block:: python >>> b = bitmath.Byte(1337) >>> print(b.bytes) - 1337 + 1337.0 .. py:attribute:: BitMathInstance.power @@ -380,6 +389,61 @@ of how an attribute may be referenced multiple times. .. note:: On line **4** we print with 1 digit of precision, on line **16** we see the value has been rounded to **6.0** + +.. _instances_rounding: + +Rounding and Integer Conversion +================================ + +bitmath instances support Python's standard rounding protocol. +:py:func:`math.floor`, :py:func:`math.ceil`, and the built-in +:py:func:`round` all return a new bitmath instance of the **same +type** with the prefix value rounded accordingly. + +.. code-block:: python + + >>> import math, bitmath + + >>> math.floor(bitmath.MiB(1.75)) + MiB(1) + + >>> math.ceil(bitmath.MiB(1.25)) + MiB(2) + + >>> round(bitmath.GiB(3.7)) + GiB(4) + + >>> round(bitmath.KiB(1.555), 2) + KiB(1.56) + +These methods round the *prefix value*. To obtain the nearest whole +**byte** count, convert first: + +.. code-block:: python + + >>> int(bitmath.KiB(1/3).bytes) + 341 + +To obtain the nearest whole **bit** count: + +.. code-block:: python + + >>> int(bitmath.KiB(1/3).bits) + 2730 + +.. warning:: + + Rounding intermediate results is lossy. + ``math.floor(GiB(10) / 3) * 3`` yields ``GiB(9)``, not + ``GiB(10)``. Only round at the **final** output step, not + during calculation. + +.. seealso:: + + :ref:`appendix_math` — design rationale for floating-point values + and guidance on when rounding is appropriate. + + .. _instances_properties: Instance Properties From 9b301fe08af0c89603090d66bc6680495b430d3e Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 07:27:14 -0500 Subject: [PATCH 50/92] Makefile: use 'open' on Darwin, xdg-open elsewhere for viewdocs --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c5ccaf6..1697de0 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,11 @@ docsite/source/index.rst: docsite/source/index.rst.in README.rst VERSION $(ASCII2MAN) viewdocs: docs - xdg-open docsite/build/html/index.html + @if [ "$$(uname)" = "Darwin" ]; then \ + open docsite/build/html/index.html; \ + else \ + xdg-open docsite/build/html/index.html; \ + fi viewcover: xdg-open cover/index.html From e1c10a8185f00c97d242a158879613aacc31ac0b Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 07:33:10 -0500 Subject: [PATCH 51/92] Align make ci with GitHub workflow; add coverage XML for PR reporting - Replace 'virtualenv' command with 'python3 -m venv'; add pip upgrade step to match 'python -m pip install --upgrade pip' in the workflow - Replace ci-pyflakes (pyflakes) with ci-flake8 (flake8 --select=F) to match the workflow's linting step exactly - Add --cov-report xml:coverage.xml to pytest in both Makefile and workflow - Add MishaKav/pytest-coverage-comment step to post coverage summary as a PR comment (ubuntu-latest / Python 3.12 matrix leg only, to avoid duplicate comments) - Add pull-requests: write permission to the build job - Add coverage.xml to .gitignore --- .github/workflows/python.yml | 11 ++++++++++- .gitignore | 1 + Makefile | 18 ++++++++++-------- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 43ba22f..8701cde 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -4,6 +4,8 @@ on: [push, pull_request] jobs: build: + permissions: + pull-requests: write strategy: matrix: python-version: ["3.11", "3.12", "3.13"] @@ -35,4 +37,11 @@ jobs: - name: Run Unit Tests run: | - pytest -v --cov=bitmath --cov-report term-missing --cov-report term:skip-covered tests + pytest -v --cov=bitmath --cov-report term-missing --cov-report term:skip-covered --cov-report xml:coverage.xml tests + + - name: Coverage report on PR + if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' + uses: MishaKav/pytest-coverage-comment@main + with: + pytest-xml-coverage-path: ./coverage.xml + title: "Test Coverage Report" diff --git a/.gitignore b/.gitignore index 93bb6e3..0229c8e 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ pip-log.txt # Unit test / coverage reports .coverage +coverage.xml .tox nosetests.xml diff --git a/Makefile b/Makefile index 1697de0..4943002 100644 --- a/Makefile +++ b/Makefile @@ -170,16 +170,18 @@ virtualenv: @echo "#############################################" @echo "# Creating a virtualenv" @echo "#############################################" - virtualenv $(NAME)env3 --python=python3 - . $(NAME)env3/bin/activate && pip install -r requirements.txt + @if [ ! -d "$(NAME)env3" ]; then \ + python3 -m venv $(NAME)env3; \ + fi + . $(NAME)env3/bin/activate && python -m pip install --upgrade pip && pip install -r requirements.txt ci-unittests: @echo "" @echo "#############################################" @echo "# Running Unit Tests in virtualenv" - @echo "# Using python: $(shell ./bitmathenv3/bin/python --version 2>&1)" + @echo "# Using python: $(shell ./$(NAME)env3/bin/python --version 2>&1)" @echo "#############################################" - . $(NAME)env3/bin/activate && pytest -v --cov=bitmath --cov-report term-missing --cov-report term:skip-covered tests + . $(NAME)env3/bin/activate && pytest -v --cov=bitmath --cov-report term-missing --cov-report term:skip-covered --cov-report xml:coverage.xml tests ci-list-deps: @echo "" @@ -195,12 +197,12 @@ ci-pycodestyle: @echo "#############################################" . $(NAME)env3/bin/activate && pycodestyle -v --ignore=E501,E722 bitmath/__init__.py tests/*.py -ci-pyflakes: +ci-flake8: @echo "" @echo "#################################################" - @echo "# Running Pyflakes Compliance Tests in virtualenv" + @echo "# Running Flake8 Compliance Tests in virtualenv" @echo "#################################################" - . $(NAME)env3/bin/activate && pyflakes bitmath/__init__.py tests/*.py + . $(NAME)env3/bin/activate && flake8 --select=F bitmath/__init__.py tests/*.py -ci: clean uniquetestnames virtualenv ci-list-deps ci-pycodestyle ci-pyflakes ci-unittests +ci: clean uniquetestnames virtualenv ci-list-deps ci-pycodestyle ci-flake8 ci-unittests : From 6bdff941f32d4c8e9ddf71f06ac758ea774a18ee Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 07:34:21 -0500 Subject: [PATCH 52/92] Fix E305: add blank line after nested() helper in test_query_device_capacity --- tests/test_query_device_capacity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_query_device_capacity.py b/tests/test_query_device_capacity.py index d15c0e3..171b56c 100644 --- a/tests/test_query_device_capacity.py +++ b/tests/test_query_device_capacity.py @@ -41,6 +41,7 @@ def nested(*contexts): with ExitStack() as stack: yield tuple(stack.enter_context(c) for c in contexts) + device_file_no = mock.Mock(return_value=4) device = mock.MagicMock('file') device.fileno = device_file_no From 3e77a2136727c1176f412c7d80d7be992caa171f Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 07:35:16 -0500 Subject: [PATCH 53/92] Add HTML coverage report to ci-unittests; fix viewcover for Darwin --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4943002..23f628c 100644 --- a/Makefile +++ b/Makefile @@ -85,7 +85,11 @@ viewdocs: docs fi viewcover: - xdg-open cover/index.html + @if [ "$$(uname)" = "Darwin" ]; then \ + open htmlcov/index.html; \ + else \ + xdg-open htmlcov/index.html; \ + fi conf.py: docsite/source/conf.py.in sed "s/%VERSION%/$(VERSION)/" $< > docsite/source/conf.py @@ -181,7 +185,7 @@ ci-unittests: @echo "# Running Unit Tests in virtualenv" @echo "# Using python: $(shell ./$(NAME)env3/bin/python --version 2>&1)" @echo "#############################################" - . $(NAME)env3/bin/activate && pytest -v --cov=bitmath --cov-report term-missing --cov-report term:skip-covered --cov-report xml:coverage.xml tests + . $(NAME)env3/bin/activate && pytest -v --cov=bitmath --cov-report term-missing --cov-report term:skip-covered --cov-report xml:coverage.xml --cov-report html:htmlcov tests ci-list-deps: @echo "" From 4294af61a148bb4a0603ab34967f14c274b07f6f Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 07:36:17 -0500 Subject: [PATCH 54/92] viewcover depends on ci-unittests; ci-unittests depends on virtualenv --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 23f628c..8bb4939 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ viewdocs: docs xdg-open docsite/build/html/index.html; \ fi -viewcover: +viewcover: ci-unittests @if [ "$$(uname)" = "Darwin" ]; then \ open htmlcov/index.html; \ else \ @@ -179,7 +179,7 @@ virtualenv: fi . $(NAME)env3/bin/activate && python -m pip install --upgrade pip && pip install -r requirements.txt -ci-unittests: +ci-unittests: virtualenv @echo "" @echo "#############################################" @echo "# Running Unit Tests in virtualenv" From c486f91088433500a0dff528ec8e80342a1cd489 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 07:44:04 -0500 Subject: [PATCH 55/92] Add docstrings to __floor__/__ceil__/__round__; add test_rounding.py Each docstring follows the existing pattern (operation summary, example, design rationale note) and references the Rules for Math appendix. 19 new tests covering floor/ceil/round across fractional, whole, negative, and cross-unit-type cases. --- bitmath/__init__.py | 22 +++++++ tests/test_rounding.py | 133 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 tests/test_rounding.py diff --git a/bitmath/__init__.py b/bitmath/__init__.py index a66f327..c290c4a 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -851,13 +851,35 @@ def __float__(self): """Return this instances prefix unit as a floating point number""" return float(self.prefix_value) + """floor/ceil/round operate on the prefix value and return the same unit +type. They are explicit opt-in operations for when integer prefix values are +needed. See the Rules for Math appendix in the bitmath documentation for the +design rationale behind floating-point representation. +""" + def __floor__(self): + """Return the largest integer prefix value <= this instance as the same type. + +Rounds the prefix value down. math.floor(MiB(1.9)) -> MiB(1). +""" return (type(self))(math.floor(self.prefix_value)) def __ceil__(self): + """Return the smallest integer prefix value >= this instance as the same type. + +Rounds the prefix value up. math.ceil(MiB(1.1)) -> MiB(2). +""" return (type(self))(math.ceil(self.prefix_value)) def __round__(self, ndigits=None): + """Return this instance rounded to ndigits precision as the same type. + +round(MiB(1.75)) -> MiB(2); round(KiB(1.555), 2) -> KiB(1.56). + +Rounds the prefix value using Python's built-in round(). When ndigits +is omitted the result has an integer prefix value. Only round at the +final output step; rounding intermediate results loses precision. +""" if ndigits is None: return (type(self))(round(self.prefix_value)) return (type(self))(round(self.prefix_value, ndigits)) diff --git a/tests/test_rounding.py b/tests/test_rounding.py new file mode 100644 index 0000000..73eff23 --- /dev/null +++ b/tests/test_rounding.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# The MIT License (MIT) +# +# Copyright © 2014 Tim Case +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +Tests for math.floor(), math.ceil(), and round() on bitmath instances +""" + +import math +from . import TestCase +import bitmath + + +class TestFloor(TestCase): + def test_floor_fractional_returns_same_type(self): + """math.floor() returns the same unit type""" + result = math.floor(bitmath.MiB(1.9)) + self.assertIsInstance(result, bitmath.MiB) + + def test_floor_rounds_down(self): + """math.floor() rounds the prefix value down""" + self.assertEqual(math.floor(bitmath.MiB(1.9)), bitmath.MiB(1)) + + def test_floor_whole_number_unchanged(self): + """math.floor() on a whole prefix value returns that value""" + self.assertEqual(math.floor(bitmath.KiB(3)), bitmath.KiB(3)) + + def test_floor_negative_rounds_toward_negative_infinity(self): + """math.floor() on a negative value rounds toward negative infinity""" + self.assertEqual(math.floor(bitmath.GiB(-1.1)), bitmath.GiB(-2)) + + def test_floor_division_result(self): + """math.floor() on a division result produces integer prefix value""" + self.assertEqual(math.floor(bitmath.KiB(1) / 3), bitmath.KiB(0)) + + def test_floor_preserves_unit_across_types(self): + """math.floor() works across all unit types""" + for unit in [bitmath.Byte, bitmath.KiB, bitmath.MiB, bitmath.GiB, + bitmath.kB, bitmath.MB]: + result = math.floor(unit(1.7)) + self.assertIsInstance(result, unit) + self.assertEqual(result, unit(1)) + + +class TestCeil(TestCase): + def test_ceil_fractional_returns_same_type(self): + """math.ceil() returns the same unit type""" + result = math.ceil(bitmath.MiB(1.1)) + self.assertIsInstance(result, bitmath.MiB) + + def test_ceil_rounds_up(self): + """math.ceil() rounds the prefix value up""" + self.assertEqual(math.ceil(bitmath.MiB(1.1)), bitmath.MiB(2)) + + def test_ceil_whole_number_unchanged(self): + """math.ceil() on a whole prefix value returns that value""" + self.assertEqual(math.ceil(bitmath.KiB(3)), bitmath.KiB(3)) + + def test_ceil_negative_rounds_toward_zero(self): + """math.ceil() on a negative value rounds toward zero""" + self.assertEqual(math.ceil(bitmath.GiB(-1.9)), bitmath.GiB(-1)) + + def test_ceil_division_result(self): + """math.ceil() on a division result rounds up to next prefix unit""" + self.assertEqual(math.ceil(bitmath.KiB(1) / 3), bitmath.KiB(1)) + + def test_ceil_preserves_unit_across_types(self): + """math.ceil() works across all unit types""" + for unit in [bitmath.Byte, bitmath.KiB, bitmath.MiB, bitmath.GiB, + bitmath.kB, bitmath.MB]: + result = math.ceil(unit(1.2)) + self.assertIsInstance(result, unit) + self.assertEqual(result, unit(2)) + + +class TestRound(TestCase): + def test_round_no_ndigits_returns_same_type(self): + """round() with no ndigits returns the same unit type""" + result = round(bitmath.GiB(3.7)) + self.assertIsInstance(result, bitmath.GiB) + + def test_round_no_ndigits_rounds_to_nearest(self): + """round() with no ndigits rounds to the nearest integer prefix value""" + self.assertEqual(round(bitmath.GiB(3.7)), bitmath.GiB(4)) + self.assertEqual(round(bitmath.GiB(3.2)), bitmath.GiB(3)) + + def test_round_with_ndigits_returns_same_type(self): + """round(x, ndigits) returns the same unit type""" + result = round(bitmath.KiB(1.555), 2) + self.assertIsInstance(result, bitmath.KiB) + + def test_round_with_ndigits(self): + """round(x, ndigits) rounds to the specified decimal precision""" + self.assertEqual(round(bitmath.KiB(1.5), 0), bitmath.KiB(2)) + self.assertEqual(round(bitmath.MiB(2.567), 1), bitmath.MiB(2.6)) + + def test_round_whole_number_unchanged(self): + """round() on a whole prefix value returns that value""" + self.assertEqual(round(bitmath.MiB(5)), bitmath.MiB(5)) + + def test_round_negative_value(self): + """round() on a negative value rounds to nearest""" + self.assertEqual(round(bitmath.GiB(-3.7)), bitmath.GiB(-4)) + self.assertEqual(round(bitmath.GiB(-3.2)), bitmath.GiB(-3)) + + def test_floor_ceil_round_not_equal_for_fractional(self): + """floor, ceil, and round give distinct results for fractional values""" + val = bitmath.MiB(1.6) + self.assertEqual(math.floor(val), bitmath.MiB(1)) + self.assertEqual(math.ceil(val), bitmath.MiB(2)) + self.assertEqual(round(val), bitmath.MiB(2)) From 490b8e5bda50087a00a7e05e774f399839663169 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 07:54:32 -0500 Subject: [PATCH 56/92] Add failing tests for best_prefix() bit-family unit bug (issue #95) best_prefix() on Bit-family instances (Bit, Kib, Mib, kb, Mb, etc.) incorrectly returns Byte-family units (MiB, GiB, MB, GB, etc.) because the conversion method name is always built with a hardcoded 'B' suffix. 13 new tests document the expected behaviour across NIST and SI systems. All 13 fail against the current implementation. Existing 30 tests pass. --- tests/test_best_prefix_NIST.py | 55 ++++++++++++++++++++++++++++++++++ tests/test_best_prefix_SI.py | 49 ++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/tests/test_best_prefix_NIST.py b/tests/test_best_prefix_NIST.py index ce2fbe1..5928572 100644 --- a/tests/test_best_prefix_NIST.py +++ b/tests/test_best_prefix_NIST.py @@ -142,3 +142,58 @@ def test_bitmath_best_prefix_NIST_exbi(self): """bitmath.best_prefix return an exbibyte for a huge number of bytes""" result = bitmath.best_prefix(1152921504606846977) self.assertIs(type(result), bitmath.EiB) + + ################################################################## + # Tests for bit-family inputs (issue #95) + # + # best_prefix() on a Bit-family instance should return a Bit-family + # result. Before the fix these all incorrectly return Byte-family + # units (e.g. MiB instead of Mib). + + def test_bit_input_returns_bit_family(self): + """NIST: best_prefix on Bit() returns a Bit-family unit, not a Byte-family unit""" + # The exact value from issue #95 + result = bitmath.Bit(30950093.15655963).best_prefix() + self.assertIsInstance(result, bitmath.Bit) + + def test_bit_input_returns_mib_type(self): + """NIST: Bit(30950093) best_prefix is Mib, not MiB (issue #95)""" + result = bitmath.Bit(30950093.15655963).best_prefix() + self.assertIs(type(result), bitmath.Mib) + + def test_kib_input_returns_mib_type(self): + """NIST: Kib(8192).best_prefix() returns Mib, not MiB + +Kib(8192) = 8,388,608 bits = 1,048,576 bytes; log(1048576, 1024) = 2 -> Mib. +""" + result = bitmath.Kib(8192).best_prefix() + self.assertIs(type(result), bitmath.Mib) + + def test_mib_input_returns_gib_type(self): + """NIST: Mib(8192).best_prefix() returns Gib, not GiB + +Mib(8192) = 8,589,934,592 bits = 1,073,741,824 bytes; log(1073741824, 1024) = 3 -> Gib. +""" + result = bitmath.Mib(8192).best_prefix() + self.assertIs(type(result), bitmath.Gib) + + def test_bit_multi_oom_round_up(self): + """NIST: A very large Kib rounds up into a Pib + +Pib(8) = 2^53 bits = 2^50 bytes; log(2^50, 1024) = 5 -> Pib. +""" + large_Kib = bitmath.Kib.from_other(bitmath.Pib(8)) + self.assertIs(type(large_Kib.best_prefix()), bitmath.Pib) + + def test_bit_multi_oom_round_down(self): + """NIST: A very small Pib rounds down into a Kib + +Kib(8) = 8192 bits = 1024 bytes; log(1024, 1024) = 1 -> Kib. +""" + small_Pib = bitmath.Pib.from_other(bitmath.Kib(8)) + self.assertIs(type(small_Pib.best_prefix()), bitmath.Kib) + + def test_bit_input_prefer_nist_returns_bit_family(self): + """NIST: best_prefix(system=NIST) on a Bit() still returns a Bit-family unit""" + result = bitmath.Bit(30950093.15655963).best_prefix(system=bitmath.NIST) + self.assertIs(type(result), bitmath.Mib) diff --git a/tests/test_best_prefix_SI.py b/tests/test_best_prefix_SI.py index 7cfe15e..35c80fa 100644 --- a/tests/test_best_prefix_SI.py +++ b/tests/test_best_prefix_SI.py @@ -142,3 +142,52 @@ def test_bitmath_best_prefix_SI_yotta(self): """bitmath.best_prefix return a yottabyte for a huge number of bytes""" result = bitmath.best_prefix(1000000000000000000000001, system=bitmath.SI) self.assertIs(type(result), bitmath.YB) + + ################################################################## + # Tests for bit-family inputs (issue #95) + # + # best_prefix() on a Bit-family SI instance should return a + # Bit-family SI result. Before the fix these incorrectly return + # Byte-family units (e.g. MB instead of Mb). + + def test_bit_input_returns_bit_family_si(self): + """SI: best_prefix on Bit() returns a Bit-family unit, not a Byte-family unit""" + result = bitmath.Bit.from_other(bitmath.Mb(1)).best_prefix(system=bitmath.SI) + self.assertIsInstance(result, bitmath.Bit) + + def test_kb_input_returns_mb_type(self): + """SI: kb(8000).best_prefix() returns Mb, not MB + +kb(8000) = 8,000,000 bits = 1,000,000 bytes; log(1000000, 1000) = 2 -> Mb. +""" + result = bitmath.kb(8000).best_prefix() + self.assertIs(type(result), bitmath.Mb) + + def test_mb_input_returns_gb_type(self): + """SI: Mb(8000).best_prefix() returns Gb, not GB + +Mb(8000) = 8,000,000,000 bits = 1,000,000,000 bytes; log(1000000000, 1000) = 3 -> Gb. +""" + result = bitmath.Mb(8000).best_prefix() + self.assertIs(type(result), bitmath.Gb) + + def test_bit_multi_oom_round_up_si(self): + """SI: A very large kb rounds up into a Pb + +Pb(8) = 8*10^15 bits = 10^15 bytes; log(10^15, 1000) = 5 -> Pb. +""" + large_kb = bitmath.kb.from_other(bitmath.Pb(8)) + self.assertIs(type(large_kb.best_prefix()), bitmath.Pb) + + def test_bit_multi_oom_round_down_si(self): + """SI: A very small Pb rounds down into a kb + +kb(8) = 8000 bits = 1000 bytes; log(1000, 1000) = 1 -> kb. +""" + small_Pb = bitmath.Pb.from_other(bitmath.kb(8)) + self.assertIs(type(small_Pb.best_prefix()), bitmath.kb) + + def test_bit_input_prefer_si_returns_bit_family(self): + """SI: best_prefix(system=SI) on a kb() still returns a Bit-family unit""" + result = bitmath.kb(8000).best_prefix(system=bitmath.SI) + self.assertIs(type(result), bitmath.Mb) From 8802bcd7bb2066adaf71df0a6794c94391a02b1d Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 08:07:54 -0500 Subject: [PATCH 57/92] Fix best_prefix() to preserve unit family; closes #95 Bit-family instances (Bit, Kib, Mib, kb, Mb, etc.) now always return a Bit-family result from best_prefix(). Previously the conversion method name was built with a hardcoded 'B' suffix, so e.g. Bit(30950093).best_prefix() returned MiB instead of Mib. Changes: - Replace 'type(self) is Byte' exact-type check with isinstance(self, Byte), removing the pylint unidiomatic-typecheck suppression - At _index == 0 (below the first prefix threshold), Bit-family inputs now return Bit.from_other(self) rather than Byte, preserving family consistency down to the floor - Conversion method name now uses 'b' suffix for Bit-family and 'B' suffix for Byte-family inputs - Updated best_prefix() docstring with revised index-0 rule and a versionchanged:: 2.0.0 note - Updated test_bit_round_up to document the new family-preserving behaviour (Bit(16).best_prefix() -> Bit, not Byte) --- bitmath/__init__.py | 33 +++++++++++++++++++++++++-------- tests/test_best_prefix_BASE.py | 10 +++++++--- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index c290c4a..9f33d5a 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -379,7 +379,7 @@ def from_other(cls, item): ###################################################################### # The following implement the Python datamodel customization methods # - # Reference: http://docs.python.org/2.7/reference/datamodel.html#basic-customization + # Reference: https://docs.python.org/3/reference/datamodel.html#basic-customization def __repr__(self): """Representation of this object as you would expect to see in an @@ -430,7 +430,8 @@ def best_prefix(self, system=None): Else, begin by recording the unit system the instance is defined by. This determines which steps (NIST_STEPS/SI_STEPS) we iterate over. -If the instance is not already a ``Byte`` instance, convert it to one. +If the instance is not already a ``Byte`` instance, convert it to one +for the purpose of the log calculation. NIST units step up by powers of 1024, SI units step up by powers of 1000. @@ -444,10 +445,20 @@ def best_prefix(self, system=None): This will return a value >= 0. The following determines the 'best prefix unit' for representation: -* result == 0, best represented as a Byte +* result == 0, best represented as a Byte (or Bit for Bit-family inputs) * result >= len(SYSTEM_STEPS), best represented as an Exbi/Exabyte * 0 < result < len(SYSTEM_STEPS), best represented as SYSTEM_PREFIXES[result-1] +Unit family is preserved: Bit-family instances (Bit, Kib, Mib, kb, +Mb, etc.) always return a Bit-family result. Byte-family instances +always return a Byte-family result. + +.. versionchanged:: 2.0.0 + Bit-family instances now return Bit-family results. Previously, + ``best_prefix()`` always returned a Byte-family unit regardless of + the input type (e.g. ``Bit(30950093).best_prefix()`` returned + ``MiB`` instead of ``Mib``). See GitHub issue #95. + """ # Use absolute value so we don't return Bit's for *everything* @@ -455,7 +466,7 @@ def best_prefix(self, system=None): if abs(self) < Byte(1): return Bit.from_other(self) else: - if type(self) is Byte: # pylint: disable=unidiomatic-typecheck + if isinstance(self, Byte): _inst = self else: _inst = Byte.from_other(self) @@ -491,7 +502,10 @@ def best_prefix(self, system=None): # in the list. if _index == 0: - # Already a Byte() type, so return it. + # Below the first prefix threshold. Bit-family inputs return as + # Bit to preserve family; Byte-family inputs return as Byte. + if isinstance(self, Bit): + return Bit.from_other(self) return _inst elif _index >= len(_STEPS): # This is a really big number. Use the biggest prefix we've got @@ -500,9 +514,12 @@ def best_prefix(self, system=None): # There is an appropriate prefix unit to represent this _best_prefix = _STEPS[_index - 1] - _conversion_method = getattr( - self, - 'to_%sB' % _best_prefix) + # Preserve unit family: Bit-family -> 'to_Xib'/'to_Xb', + # Byte-family -> 'to_XiB'/'to_XB'. + if isinstance(self, Bit): + _conversion_method = getattr(self, 'to_%sb' % _best_prefix) + else: + _conversion_method = getattr(self, 'to_%sB' % _best_prefix) return _conversion_method() diff --git a/tests/test_best_prefix_BASE.py b/tests/test_best_prefix_BASE.py index 582c64d..8ccc799 100644 --- a/tests/test_best_prefix_BASE.py +++ b/tests/test_best_prefix_BASE.py @@ -42,11 +42,15 @@ def test_byte_round_down(self): self.assertIs(type(half_byte.best_prefix()), bitmath.Bit) def test_bit_round_up(self): - """best_prefix_base: 2 Bytes (as a Bit()) round up into a Byte()""" + """best_prefix_base: 2 Bytes (as a Bit()) stays as Bit() — family preserved + +Bit(16) is 2 bytes, below the Kib/kb threshold. There is no sub-kibibit +prefix unit, so best_prefix returns Bit to preserve the Bit family. +Prior to 2.0.0 this returned Byte(2). +""" # Two bytes is 16 bits two_bytes = bitmath.Bit(bytes=2) - # Bit(16) should round up into Byte(2) - self.assertIs(type(two_bytes.best_prefix()), bitmath.Byte) + self.assertIs(type(two_bytes.best_prefix()), bitmath.Bit) def test_byte_no_rounding(self): """best_prefix_base: 1 Byte (as a Byte()) best prefix is still a Byte()""" From fd3e748c1536ee429aa4adc3a75a548e89bf5c42 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 08:10:56 -0500 Subject: [PATCH 58/92] Upgrade codeql-action v3 -> v4 to clear deprecation warnings --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 93411e5..7d46f53 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -22,14 +22,14 @@ jobs: uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: python - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:python" From 75dffb5f59196a0ba5a375f1430ffa8bda041b3a Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 08:15:36 -0500 Subject: [PATCH 59/92] Upgrade actions/checkout to v6.0.2; opt into Node.js 24 now actions/checkout bumped from v4 to v6.0.2 (latest stable as of 2026-01-09). FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true added at job level in both workflows to begin testing on Node 24 ahead of the mandatory cutover on June 2nd 2026. --- .github/workflows/codeql.yml | 4 +++- .github/workflows/python.yml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7d46f53..0450a53 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,10 +16,12 @@ jobs: actions: read contents: read security-events: write + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6.0.2 - name: Initialize CodeQL uses: github/codeql-action/init@v4 diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 8701cde..88bdca8 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -6,6 +6,8 @@ jobs: build: permissions: pull-requests: write + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true strategy: matrix: python-version: ["3.11", "3.12", "3.13"] @@ -13,7 +15,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: "GitHub Checks it out :sunglasses-face:" - uses: actions/checkout@v4 + uses: actions/checkout@v6.0.2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 From 57d4af64e733cce181097b97aa5cbd15c81785a0 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 08:24:34 -0500 Subject: [PATCH 60/92] Clarify mixed-type add/sub docs; add explicit-operand pattern Closes #94 --- docsite/source/appendices/mixed_math.rst | 51 +++++++++++++++--------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/docsite/source/appendices/mixed_math.rst b/docsite/source/appendices/mixed_math.rst index 44e1b2d..1a47032 100644 --- a/docsite/source/appendices/mixed_math.rst +++ b/docsite/source/appendices/mixed_math.rst @@ -107,28 +107,23 @@ Mixed Types: Addition and Subtraction This describes the behavior of addition and subtraction operations where one operand is a bitmath type and the other is a number type. -Mixed-math addition and subtraction **always** return a type from the -:py:mod:`numbers` family (integer, float, etc...). This rule is -true regardless of the placement of the operands, with respect to the -operator. +Mixed-math addition and subtraction return a type from the +:py:mod:`numbers` family (integer, float, etc...) regardless of the +placement of the operands, with one exception: when the left operand +is exactly ``0``, the result is the bitmath instance itself. -.. note:: +This exception exists so that Python's built-in :py:func:`sum` +function works correctly with iterables of bitmath objects, since +``sum()`` starts accumulation from ``0`` by default: - **Exception: zero as the left operand.** When the left operand is - exactly ``0`` (e.g. ``0 + KiB(1)``), the result is the bitmath - instance itself rather than a number. This special case exists so - that Python's built-in :py:func:`sum` function works correctly with - iterables of bitmath objects, since ``sum()`` starts accumulation - from ``0`` by default. - - .. code-block:: python +.. code-block:: python - >>> import bitmath - >>> sum([bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)]) - Byte(1074790401.0) + >>> import bitmath + >>> sum([bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)]) + Byte(1074790401.0) - For all non-zero numeric left operands the documented behaviour - (returning a number) is unchanged. +For all non-zero numeric operands the behaviour (returning a number) +applies. **Discussion:** Why do ``100 - KiB(90)`` and ``KiB(100) - 90`` both yield a result of ``10.0`` and not another bitmath instance, such as @@ -197,6 +192,26 @@ what unit the operand was *intended* to carry. Therefore, the behavior of bitmath is **conservative**. It will meet us half way and do the math, but it will not return a unit in the result. +**Keeping the result as a bitmath type** + +If the intent is to add or subtract a quantity of the *same unit* — +for example, incrementing ``Byte(1)`` by one more byte — use an +explicit bitmath operand on both sides: + +.. code-block:: python + + >>> Byte(1) + Byte(1) + Byte(2.0) + + >>> KiB(10) - KiB(3) + KiB(7.0) + +This makes the unit explicit rather than relying on implicit +conversion, which eliminates ambiguity — ``KiB(10) - 3`` could mean +"subtract 3 KiB" or "subtract the number 3 from the prefix value." +bitmath does not guess; using a bitmath operand on both sides states +the intent clearly. + Mixed Types: Multiplication and Division ======================================== From 76c1ff1016f8eaa53abfb2d887572ee152b29438 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 09:08:45 -0500 Subject: [PATCH 61/92] Add a SECURITY policy for GH --- SECURITY.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ab329b5 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,32 @@ +# Security Policy + +Generally speaking, users will be encouraged to update to newer versions. Since the launch of this project in 2014 I have been committed to introducing non-breaking changes and strong forward/backward compatibility. The 2.0.0 release series is the first time in over a decade that breaking changes have been introduced. Most likely, the response will always be "update to the latest 2.x" for the latest patches. + +Security related bugs should be reserved for situations with actual real-life consequences. Inappropriate use of the library, "user-error", generally won't fall under this umbrella. + +## Supported Versions + +As of the 2.0.0 re-factor only versions ≥ 2.0.0 will receive support. Versions prior to 2.0.0 are legacy and not recommended for general use. The last "update" to the pre 2.0.0 series was 1.4.0 in April of 2026, nearly 8 years after the 1.3.3 release made in 2018. + +| Version | Supported | +| ------- | ------------------ | +| `≥ 2.0.0` | :white_check_mark: | +| `< 2.x.y` | :x: | + +This list will be updated when future releases are made in the 2-version series that require specific callouts for supportability. + +## Reporting a Vulnerability + +If you have discovered what you think is a harmful bug with the potential for exploitation that will lead to loss of life or data, then reach out to the maintainer at this email address: + +* `bitmath@lnx.cx` + +Please include a tag in the subject indicating the sensitivity of the issue. I will respond and we will triage the issue, a disclosure statement will be made as soon as we understand the potential impact and have prepared a mitigation. + +As an emergency backup you can find me on bsky or instagram and direct message me there: + +* [bsky - @lnx.cx](https://bsky.app/profile/lnx.cx) +* [insta - @tim.lnx](https://www.instagram.com/tim.lnx/) + +For less serious security issues with lower potential for exploitation or damage, please open a bug on the project and apply the `security` label to it. + From 61ddf32620b0ada4abab17430598a40102ad3a77 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 09:16:11 -0500 Subject: [PATCH 62/92] SECURITY POLICY BETTER --- SECURITY.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index ab329b5..3502ed9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -15,9 +15,22 @@ As of the 2.0.0 re-factor only versions ≥ 2.0.0 will receive support. Versions This list will be updated when future releases are made in the 2-version series that require specific callouts for supportability. -## Reporting a Vulnerability +If you have discovered what you think is a harmful bug with the potential for exploitation in a supported version series, and this bug may lead to loss of life or data, then you have two options for reporting available to you: -If you have discovered what you think is a harmful bug with the potential for exploitation that will lead to loss of life or data, then reach out to the maintainer at this email address: +## [1] Self-Reporting (GitHub) + +Consider using the new [Private Vulnerability Reporting](https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/privately-reporting-a-security-vulnerability) function if you want to get involved that way. + +* On GitHub, navigate to the main page of the repository. +* Under the repository name, click the Security and quality tab. If you cannot see the " Security and quality" tab, select the dropdown menu, and then click Security and quality. +* Click Report a vulnerability to open the advisory form. +* Fill in the advisory details form. + +...as described in the linked GitHub documentation. + +## [2] Reporting a Vulnerability (Non-GitHub) + +You may also reach out to me at this email address: * `bitmath@lnx.cx` From 52f232249c7ba4e14d91c7395c67b4c22c3c1902 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 09:27:59 -0500 Subject: [PATCH 63/92] Naming, copyright, emails, housekeeping stuff --- CODE_OF_CONDUCT.md | 2 +- LICENSE | 2 +- bitmath.1 | 10 +++++----- bitmath.1.asciidoc.in | 6 +++--- pyproject.toml | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5c24c81..7b5c27a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at timbielawa@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at bitmath@lnx.cx. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. diff --git a/LICENSE b/LICENSE index 69fe7b7..39eb0c5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright © 2014 Tim Bielawa +Copyright © 2014-2026 Tim Case (fmr. tbielawa) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/bitmath.1 b/bitmath.1 index c380692..8fb8fe8 100644 --- a/bitmath.1 +++ b/bitmath.1 @@ -2,12 +2,12 @@ .\" Title: bitmath .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 02/04/2023 +.\" Date: 04/17/2026 .\" Manual: python-bitmath .\" Source: bitmath 2.0.0 .\" Language: English .\" -.TH "BITMATH" "1" "02/04/2023" "bitmath 2\&.0\&.0" "python\-bitmath" +.TH "BITMATH" "1" "04/17/2026" "bitmath 2\&.0\&.0" "python\-bitmath" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -56,18 +56,18 @@ for best human\-readability\&. .RE .SH "AUTHOR" .sp -Tim Bielawa +Tim Case (fmr\&. tbielawa) .sp For a complete list of contributors, please visit the GitHub charts page\&. .SH "COPYRIGHT" .sp -Copyright \(co 2014\-2016, Tim Bielawa\&. +Copyright \(co 2014\-2026, Tim Case\&. .sp bitmath is released under the terms of the "MIT" License\&. .SH "SEE ALSO" .sp \fBunits\fR(7) .sp -\fBThe bitmath GitHub Project\fR \(em https://github\&.com/tbielawa/bitmath +\fBThe bitmath GitHub Project\fR \(em https://github\&.com/timlnx/bitmath .sp \fBThe bitmath Documentation\fR \(em https://bitmath\&.readthedocs\&.org diff --git a/bitmath.1.asciidoc.in b/bitmath.1.asciidoc.in index 39afbb7..cc901a4 100644 --- a/bitmath.1.asciidoc.in +++ b/bitmath.1.asciidoc.in @@ -44,7 +44,7 @@ best human-readability. AUTHOR ------ -Tim Bielawa +Tim Case (fmr. tbielawa) For a complete list of contributors, please visit the GitHub charts page. @@ -52,7 +52,7 @@ page. COPYRIGHT --------- -Copyright © 2014-2016, Tim Bielawa. +Copyright © 2014-2026, Tim Case. bitmath is released under the terms of the "MIT" License. @@ -62,6 +62,6 @@ SEE ALSO -------- *units*(7) -*The bitmath GitHub Project* -- +*The bitmath GitHub Project* -- *The bitmath Documentation* -- diff --git a/pyproject.toml b/pyproject.toml index 931c37d..0809175 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ description = "Pythonic module for representing and manipulating file sizes with readme = "README.rst" requires-python = ">=3.11" authors = [ - { name = "Tim Case", email = "timbielawa@gmail.com" }, + { name = "Tim Case", email = "bitmath@lnx.cx" }, ] classifiers = [ "Development Status :: 5 - Production/Stable", From 18bc5026f9fcbd4c1a225f97eca1686cd96fbd38 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 12:48:50 -0500 Subject: [PATCH 64/92] Merge parse_string_unsafe into parse_string; deprecate parse_string_unsafe - Add strict=True and system=NIST kwargs to parse_string. When strict=True (default) behaviour is identical to the original function. When strict=False the loose parsing logic (formerly parse_string_unsafe) is used. - Default system is now NIST for the strict=False path. Callers that previously relied on the SI default of parse_string_unsafe must now pass system=bitmath.SI explicitly. - parse_string_unsafe is now a deprecated wrapper that emits DeprecationWarning with migration instructions. Deprecated in 2.0.0. - Fix latent KeyError in non-strict NIST path when given an invalid single-letter unit (e.g. "100J"). - Migrate all test_parse_unsafe_* tests to the new API; add explicit system= where SI behaviour is expected. Add test for DeprecationWarning. Closes #101 --- bitmath/__init__.py | 274 ++++++++++++++++++++++---------------------- tests/test_parse.py | 182 +++++++++++++++-------------- 2 files changed, 228 insertions(+), 228 deletions(-) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 9f33d5a..3a4e26f 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -1404,172 +1404,166 @@ def listdir(search_base, followlinks=False, filter='*', yield (_return_path, getsize(_path, bestprefix=bestprefix, system=system)) -def parse_string(s): - """Parse a string with units and try to make a bitmath object out of -it. +def parse_string(s, system=NIST, strict=True): + """Parse a string with units and return a bitmath instance. String inputs may include whitespace characters between the value and the unit. - """ - # Strings only please - if not isinstance(s, (str)): - raise ValueError("parse_string only accepts string inputs but a %s was given" % - type(s)) - - # get the index of the first alphabetic character - try: - index = list([i.isalpha() for i in s]).index(True) - except ValueError: - # If there's no alphabetic characters we won't be able to .index(True) - raise ValueError("No unit detected, can not parse string '%s' into a bitmath object" % s) - - # split the string into the value and the unit - val, unit = s[:index], s[index:] - - # see if the unit exists as a type in our namespace - - if unit == "b": - unit_class = Bit - elif unit == "B": - unit_class = Byte - else: - if not (hasattr(sys.modules[__name__], unit) and isinstance(getattr(sys.modules[__name__], unit), type)): - raise ValueError("The unit %s is not a valid bitmath unit" % unit) - unit_class = globals()[unit] - - try: - val = float(val) - except ValueError: - raise - try: - return unit_class(val) - except: # pragma: no cover - raise ValueError("Can't parse string %s into a bitmath object" % s) - - -def parse_string_unsafe(s, system=SI): - """Attempt to parse a string with ambiguous units and try to make a -bitmath object out of it. -This may produce inaccurate results if parsing shell output. For -example `ls` may say a 2730 Byte file is '2.7K'. 2730 Bytes == 2.73 kB -~= 2.666 KiB. See the documentation for all of the important details. +:param s: The string to parse. +:param system: Unit system to use when ``strict=False``. Ignored when + ``strict=True`` (the default). Set to ``bitmath.NIST`` (default) + or ``bitmath.SI``. +:param strict: When ``True`` (default), the unit must be an exact + bitmath type name (e.g. ``"KiB"``, ``"MB"``). When ``False``, + accepts ambiguous input such as plain numbers, numeric strings, + and case-insensitive single-letter units (e.g. ``"4k"``, + ``"2.7M"``); see caveats below. -Note the following caveats: +When ``strict=False`` the following rules apply: -* All inputs are assumed to be byte-based (as opposed to bit based) +* All inputs are assumed to be byte-based (not bit-based) +* Plain numbers and numeric strings are assumed to be bytes +* Single-letter units (``k``, ``M``, ``G``, etc.) are assumed NIST + unless ``system=bitmath.SI`` +* Inputs with an ``i`` after the leading letter (``Ki``, ``Mi``) + are treated as NIST units +* Capitalisation does not matter -* Numerical inputs (those without any units) are assumed to be a - number of bytes +The result is returned in the parsed unit system. To coerce the result +into a preferred unit system call ``.best_prefix(system=system)`` on +the return value:: -* Inputs with single letter units (k, M, G, etc) are assumed to be SI - units (base-10). Set the `system` parameter to `bitmath.NIST` to - change this behavior. - -* Inputs with an `i` character following the leading letter (Ki, Mi, - Gi) are assumed to be NIST units (base 2) - -* Capitalization does not matter + parse_string("4k", strict=False).best_prefix(system=bitmath.SI) +.. versionchanged:: 2.0.0 + Added ``strict`` and ``system`` parameters. When ``strict=True`` + (default) behaviour is identical to the original function. + When ``strict=False`` the behaviour of the former + ``parse_string_unsafe`` is applied. The ``system`` parameter + defaults to ``bitmath.NIST`` and is ignored when ``strict=True``. """ - if not isinstance(s, (str)) and \ - not isinstance(s, numbers.Number): - raise ValueError("parse_string_unsafe only accepts string/number inputs but a %s was given" % - type(s)) + if strict: + # Strings only please + if not isinstance(s, (str)): + raise ValueError("parse_string only accepts string inputs but a %s was given" % + type(s)) - ###################################################################### - # Is the input simple to parse? Just a number, or a number - # masquerading as a string perhaps? + # get the index of the first alphabetic character + try: + index = list([i.isalpha() for i in s]).index(True) + except ValueError: + # If there's no alphabetic characters we won't be able to .index(True) + raise ValueError("No unit detected, can not parse string '%s' into a bitmath object" % s) - # Test case: raw number input (easy!) - if isinstance(s, numbers.Number): - # It's just a number. Assume bytes - return Byte(s) + # split the string into the value and the unit + val, unit = s[:index], s[index:] + + # see if the unit exists as a type in our namespace + if unit == "b": + unit_class = Bit + elif unit == "B": + unit_class = Byte + else: + if not (hasattr(sys.modules[__name__], unit) and isinstance(getattr(sys.modules[__name__], unit), type)): + raise ValueError("The unit %s is not a valid bitmath unit" % unit) + unit_class = globals()[unit] - # Test case: a number pretending to be a string - if isinstance(s, (str)): try: - # Can we turn it directly into a number? - return Byte(float(s)) + val = float(val) except ValueError: - # Nope, this is not a plain number - pass + raise + try: + return unit_class(val) + except: # pragma: no cover + raise ValueError("Can't parse string %s into a bitmath object" % s) - ###################################################################### - # At this point: - # - the input is also not just a number wrapped in a string - # - nor is is just a plain number type - # - # We need to do some more digging around now to figure out exactly - # what we were given and possibly normalize the input into a - # format we can recognize. + else: + # loose / non-strict path (formerly parse_string_unsafe) + if not isinstance(s, (str)) and \ + not isinstance(s, numbers.Number): + raise ValueError("parse_string only accepts string/number inputs but a %s was given" % + type(s)) + + # Test case: raw number input (easy!) + if isinstance(s, numbers.Number): + return Byte(s) + + # Test case: a number pretending to be a string + if isinstance(s, (str)): + try: + return Byte(float(s)) + except ValueError: + pass - # First we'll separate the number and the unit. - # - # Get the index of the first alphabetic character - try: - index = list([i.isalpha() for i in s]).index(True) - except ValueError: # pragma: no cover - # If there's no alphabetic characters we won't be able to .index(True) - raise ValueError("No unit detected, can not parse string '%s' into a bitmath object" % s) + # At this point the input is a string with a unit component. + # Separate the number and the unit. + try: + index = list([i.isalpha() for i in s]).index(True) + except ValueError: # pragma: no cover + raise ValueError("No unit detected, can not parse string '%s' into a bitmath object" % s) - # Split the string into the value and the unit - val, unit = s[:index], s[index:] + val, unit = s[:index], s[index:] - # Don't trust anything. We'll make sure the correct 'b' is in place. - unit = unit.rstrip('Bb') - unit += 'B' + # Normalise: strip trailing b/B and append 'B' so we always + # work with byte-family units regardless of what was supplied. + unit = unit.rstrip('Bb') + unit += 'B' - # At this point we can expect `unit` to be either: - # - # - 2 Characters (for SI, ex: kB or GB) - # - 3 Caracters (so NIST, ex: KiB, or GiB) - # - # A unit with any other number of chars is not a valid unit - - # SI - if len(unit) == 2: - # Has NIST parsing been requested? - if system == NIST: - # NIST units requested. Ensure the unit begins with a - # capital letter and is followed by an 'i' character. + if len(unit) == 2: + if system == NIST: + unit = capitalize_first(unit) + _unit = list(unit) + _unit.insert(1, 'i') + unit = ''.join(_unit) + if unit in globals(): + unit_class = globals()[unit] + else: + if unit.startswith('K'): + unit = unit.replace('K', 'k') + elif not unit.startswith('k'): + unit = capitalize_first(unit) + if unit[0] in SI_PREFIXES: + unit_class = globals()[unit] + elif len(unit) == 3: unit = capitalize_first(unit) - # Insert an 'i' char after the first letter - _unit = list(unit) - _unit.insert(1, 'i') - # Collapse the list back into a 3 letter string - unit = ''.join(_unit) - unit_class = globals()[unit] + if unit[:2] in NIST_PREFIXES: + unit_class = globals()[unit] else: - # Default parsing (SI format) - # - # Edge-case checking: SI 'thousand' is a lower-case K - if unit.startswith('K'): - unit = unit.replace('K', 'k') - elif not unit.startswith('k'): - # Otherwise, ensure the first char is capitalized - unit = capitalize_first(unit) + raise ValueError("The unit %s is not a valid bitmath unit" % unit) - # This is an SI-type unit - if unit[0] in SI_PREFIXES: - unit_class = globals()[unit] - # NIST - elif len(unit) == 3: - unit = capitalize_first(unit) + try: + unit_class + except UnboundLocalError: + raise ValueError("The unit %s is not a valid bitmath unit" % unit) - # This is a NIST-type unit - if unit[:2] in NIST_PREFIXES: - unit_class = globals()[unit] - else: - # This is not a unit we recognize - raise ValueError("The unit %s is not a valid bitmath unit" % unit) + return unit_class(float(val)) + + +def parse_string_unsafe(s, system=NIST): + """Deprecated wrapper for ``parse_string(s, strict=False, system=system)``. - try: - unit_class - except UnboundLocalError: - raise ValueError("The unit %s is not a valid bitmath unit" % unit) +.. deprecated:: 2.0.0 + ``parse_string_unsafe`` is deprecated and will be removed in a + future release. Use ``parse_string(s, strict=False, + system=system)`` instead. - return unit_class(float(val)) + To suppress this warning:: + + import warnings + warnings.filterwarnings('ignore', category=DeprecationWarning, + module='bitmath') + """ + import warnings + warnings.warn( + "parse_string_unsafe is deprecated as of 2.0.0 and will be removed " + "in a future release. Use parse_string(s, strict=False, system=system) " + "instead. To suppress: " + "warnings.filterwarnings('ignore', category=DeprecationWarning, module='bitmath')", + DeprecationWarning, + stacklevel=2, + ) + return parse_string(s, system=system, strict=False) def sum(iterable, start=None): diff --git a/tests/test_parse.py b/tests/test_parse.py index 7087e97..1b042e2 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -120,63 +120,59 @@ def test_parse_string_unicode(self): ###################################################################### - def test_parse_unsafe_bad_input_type(self): - """parse_string_unsafe can identify invalid input types""" + def test_parse_loose_bad_input_type(self): + """parse_string strict=False can identify invalid input types""" with self.assertRaises(ValueError): - invalid_input = {'keyvalue': 'store'} - bitmath.parse_string_unsafe(invalid_input) + bitmath.parse_string({'keyvalue': 'store'}, strict=False) - def test_parse_unsafe_invalid_input(self): - """parse_string_unsafe explodes when given invalid units""" - invalid_input_str = "kitties!" + def test_parse_loose_invalid_input(self): + """parse_string strict=False raises ValueError for invalid units""" with self.assertRaises(ValueError): - bitmath.parse_string_unsafe(invalid_input_str) + bitmath.parse_string("kitties!", strict=False) with self.assertRaises(ValueError): - bitmath.parse_string_unsafe('100 CiB') + bitmath.parse_string('100 CiB', strict=False) with self.assertRaises(ValueError): - bitmath.parse_string_unsafe('100 J') + bitmath.parse_string('100 J', strict=False) - def test_parse_unsafe_good_number_input(self): - """parse_string_unsafe can parse unitless number inputs""" + def test_parse_loose_good_number_input(self): + """parse_string strict=False can parse unitless number inputs""" number_input = 100 string_input = "100" expected_result = bitmath.Byte(100) self.assertEqual( - bitmath.parse_string_unsafe(number_input), + bitmath.parse_string(number_input, strict=False), expected_result) self.assertEqual( - bitmath.parse_string_unsafe(string_input), + bitmath.parse_string(string_input, strict=False), expected_result) - def test_parse_unsafe_handles_SI_K_unit(self): - """parse_string_unsafe can parse the upper/lowercase SI 'thousand' (k)""" + def test_parse_loose_handles_SI_K_unit(self): + """parse_string strict=False can parse the upper/lowercase SI 'thousand' (k)""" thousand_lower = "100k" thousand_upper = "100K" expected_result = bitmath.kB(100) self.assertEqual( - bitmath.parse_string_unsafe(thousand_lower), + bitmath.parse_string(thousand_lower, strict=False, system=bitmath.SI), expected_result) self.assertEqual( - bitmath.parse_string_unsafe(thousand_upper), + bitmath.parse_string(thousand_upper, strict=False, system=bitmath.SI), expected_result) - def test_parse_unsafe_NIST_units(self): - """parse_string_unsafe can parse abbreviated NIST units (Gi, Ki, ...)""" + def test_parse_loose_NIST_units(self): + """parse_string strict=False can parse abbreviated NIST units (Gi, Ki, ...)""" nist_input = "100 Gi" expected_result = bitmath.GiB(100) self.assertEqual( - bitmath.parse_string_unsafe(nist_input), + bitmath.parse_string(nist_input, strict=False), expected_result) - def test_parse_unsafe_SI(self): - """parse_string_unsafe can parse all accepted SI inputs""" - # Begin with the kilo unit because it's the most tricky (SI - # defines the unit as a lower-case 'k') + def test_parse_loose_SI(self): + """parse_string strict=False can parse all accepted SI inputs""" kilo_inputs = [ '100k', '100K', @@ -187,11 +183,10 @@ def test_parse_unsafe_SI(self): expected_kilo_result = bitmath.kB(100) for ki in kilo_inputs: - _parsed = bitmath.parse_string_unsafe(ki) + _parsed = bitmath.parse_string(ki, strict=False, system=bitmath.SI) self.assertEqual(_parsed, expected_kilo_result) self.assertIs(type(_parsed), type(expected_kilo_result)) - # Now check for other easier to parse prefixes other_inputs = [ '100g', '100G', @@ -199,18 +194,15 @@ def test_parse_unsafe_SI(self): '100gB', '100GB' ] - expected_gig_result = bitmath.GB(100) for gi in other_inputs: - _parsed = bitmath.parse_string_unsafe(gi) + _parsed = bitmath.parse_string(gi, strict=False, system=bitmath.SI) self.assertEqual(_parsed, expected_gig_result) self.assertIs(type(_parsed), type(expected_gig_result)) - def test_parse_unsafe_NIST(self): - """parse_string_unsafe can parse all accepted NIST inputs""" - # Begin with the kilo unit because it's the most tricky (SI - # defines the unit as a lower-case 'k') + def test_parse_loose_NIST(self): + """parse_string strict=False can parse all accepted NIST inputs""" kilo_inputs = [ '100ki', '100Ki', @@ -221,11 +213,10 @@ def test_parse_unsafe_NIST(self): expected_kilo_result = bitmath.KiB(100) for ki in kilo_inputs: - _parsed = bitmath.parse_string_unsafe(ki) + _parsed = bitmath.parse_string(ki, strict=False) self.assertEqual(_parsed, expected_kilo_result) self.assertIs(type(_parsed), type(expected_kilo_result)) - # Now check for other easier to parse prefixes other_inputs = [ '100gi', '100Gi', @@ -233,71 +224,86 @@ def test_parse_unsafe_NIST(self): '100giB', '100GiB' ] - expected_gig_result = bitmath.GiB(100) for gi in other_inputs: - _parsed = bitmath.parse_string_unsafe(gi) + _parsed = bitmath.parse_string(gi, strict=False) self.assertEqual(_parsed, expected_gig_result) self.assertIs(type(_parsed), type(expected_gig_result)) - def test_parse_string_unsafe_request_NIST(self): - """parse_string_unsafe can convert to NIST on request""" - unsafe_input = "100M" - _parsed = bitmath.parse_string_unsafe(unsafe_input, system=bitmath.NIST) - expected = bitmath.MiB(100) - - self.assertEqual(_parsed, expected) - self.assertIs(type(_parsed), type(expected)) - - unsafe_input2 = "100k" - _parsed2 = bitmath.parse_string_unsafe(unsafe_input2, system=bitmath.NIST) - expected2 = bitmath.KiB(100) - - self.assertEqual(_parsed2, expected2) - self.assertIs(type(_parsed2), type(expected2)) - - unsafe_input3 = "100" - _parsed3 = bitmath.parse_string_unsafe(unsafe_input3, system=bitmath.NIST) - expected3 = bitmath.Byte(100) - - self.assertEqual(_parsed3, expected3) - self.assertIs(type(_parsed3), type(expected3)) - - unsafe_input4 = "100kb" - _parsed4 = bitmath.parse_string_unsafe(unsafe_input4, system=bitmath.NIST) - expected4 = bitmath.KiB(100) - - self.assertEqual(_parsed4, expected4) - self.assertIs(type(_parsed4), type(expected4)) - - ###################################################################### + def test_parse_loose_default_system_is_NIST(self): + """parse_string strict=False defaults to NIST for ambiguous single-letter units""" + self.assertEqual( + bitmath.parse_string("100M", strict=False), + bitmath.MiB(100)) + self.assertIs( + type(bitmath.parse_string("100k", strict=False)), + bitmath.KiB) + + def test_parse_loose_explicit_SI(self): + """parse_string strict=False uses SI when system=bitmath.SI""" + self.assertEqual( + bitmath.parse_string("100M", strict=False, system=bitmath.SI), + bitmath.MB(100)) + self.assertIs( + type(bitmath.parse_string("100k", strict=False, system=bitmath.SI)), + bitmath.kB) + + def test_parse_loose_number_inputs_unaffected_by_system(self): + """parse_string strict=False returns Byte() for plain numbers regardless of system""" + self.assertEqual( + bitmath.parse_string("100", strict=False, system=bitmath.NIST), + bitmath.Byte(100)) + self.assertEqual( + bitmath.parse_string(100, strict=False, system=bitmath.SI), + bitmath.Byte(100)) - def test_parse_string_unsafe_github_issue_60(self): - """parse_string_unsafe can parse the examples reported in issue #60 + def test_parse_loose_github_issue_60(self): + """parse_string strict=False can parse the examples reported in issue #60 https://github.com/timlnx/bitmath/issues/60 """ - issue_input1 = '7.5KB' - _parsed1 = bitmath.parse_string_unsafe(issue_input1) - expected_result1 = bitmath.kB(7.5) - self.assertEqual( - _parsed1, - expected_result1) - - issue_input2 = '4.7MB' - _parsed2 = bitmath.parse_string_unsafe(issue_input2) - expected_result2 = bitmath.MB(4.7) + bitmath.parse_string('7.5KB', strict=False, system=bitmath.SI), + bitmath.kB(7.5)) self.assertEqual( - _parsed2, - expected_result2) - - issue_input3 = '4.7M' - _parsed3 = bitmath.parse_string_unsafe(issue_input3) - expected_result3 = bitmath.MB(4.7) + bitmath.parse_string('4.7MB', strict=False, system=bitmath.SI), + bitmath.MB(4.7)) self.assertEqual( - _parsed3, - expected_result3) + bitmath.parse_string('4.7M', strict=False, system=bitmath.SI), + bitmath.MB(4.7)) + + def test_parse_string_unsafe_deprecation_warning(self): + """parse_string_unsafe emits DeprecationWarning as of 2.0.0""" + import warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + bitmath.parse_string_unsafe("100 GiB") + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[0].category, DeprecationWarning)) + self.assertIn("2.0.0", str(w[0].message)) + self.assertIn("parse_string", str(w[0].message)) + + def test_parse_string_unsafe_request_NIST(self): + """parse_string_unsafe still delegates correctly with explicit system""" + import warnings + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + + _parsed = bitmath.parse_string_unsafe("100M", system=bitmath.NIST) + self.assertEqual(_parsed, bitmath.MiB(100)) + self.assertIs(type(_parsed), bitmath.MiB) + + _parsed2 = bitmath.parse_string_unsafe("100k", system=bitmath.NIST) + self.assertEqual(_parsed2, bitmath.KiB(100)) + self.assertIs(type(_parsed2), bitmath.KiB) + + _parsed3 = bitmath.parse_string_unsafe("100", system=bitmath.NIST) + self.assertEqual(_parsed3, bitmath.Byte(100)) + self.assertIs(type(_parsed3), bitmath.Byte) + + _parsed4 = bitmath.parse_string_unsafe("100kb", system=bitmath.NIST) + self.assertEqual(_parsed4, bitmath.KiB(100)) + self.assertIs(type(_parsed4), bitmath.KiB) From 0db6fa88332f1c4d8b6b283ae06afce4dfd6e7ae Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 13:05:31 -0500 Subject: [PATCH 65/92] Add ZiB, YiB, Zib, Yib NIST units; fix int/float comparison for large units - Add ZiB (2^70), YiB (2^80) as Byte subclasses with Zio/Yio aliases - Add Zib (2^70), Yib (2^80) as Bit subclasses - Add Zi/Yi to NIST_PREFIXES and NIST_STEPS - Add to_ZiB, to_YiB, to_Zib, to_Yib conversion methods and properties to the Bitmath base class alongside their SI counterparts - Add ZiB/YiB/Zib/Yib to __all__ and ALL_UNIT_TYPES - Fix _norm: always use float(value) * _unit_value so that int and float inputs produce the same _byte_value. Without this, ZB(654) stored an exact Python int while ZB(654.0) stored a float, causing Python 3's exact int/float comparison to return False even for logically equal instances (the root cause in issue #54). - Add parse tests for Zio/Yio/Zo/Yo aliases - Add instantiation, equality, conversion, and property tests for all four new units in their respective existing test files Closes #54 --- bitmath/__init__.py | 61 +++++++++++++++++++++++++++++------ tests/test_instantiating.py | 35 ++++++++++++++++++++ tests/test_parse.py | 24 ++++++++++++++ tests/test_properties.py | 16 +++++++++ tests/test_rich_comparison.py | 45 ++++++++++++++++++++++++++ 5 files changed, 171 insertions(+), 10 deletions(-) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 3a4e26f..dec017a 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -60,9 +60,9 @@ import struct -__all__ = ['Bit', 'Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', +__all__ = ['Bit', 'Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'Kib', - 'Mib', 'Gib', 'Tib', 'Pib', 'Eib', 'kb', 'Mb', 'Gb', 'Tb', + 'Mib', 'Gib', 'Tib', 'Pib', 'Eib', 'Zib', 'Yib', 'kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb', 'getsize', 'listdir', 'format', 'format_string', 'format_plural', 'parse_string', 'parse_string_unsafe', 'sum', 'ALL_UNIT_TYPES', 'NIST', 'NIST_PREFIXES', 'NIST_STEPS', @@ -73,7 +73,8 @@ ALL_UNIT_TYPES = ['Bit', 'Byte', 'kb', 'kB', 'Mb', 'MB', 'Gb', 'GB', 'Tb', 'TB', 'Pb', 'PB', 'Eb', 'EB', 'Zb', 'ZB', 'Yb', 'YB', 'Kib', 'KiB', 'Mib', 'MiB', 'Gib', 'GiB', - 'Tib', 'TiB', 'Pib', 'PiB', 'Eib', 'EiB'] + 'Tib', 'TiB', 'Pib', 'PiB', 'Eib', 'EiB', 'Zib', 'ZiB', + 'Yib', 'YiB'] # ##################################################################### # Set up our module variables/constants @@ -114,7 +115,7 @@ #: All of the NIST prefixes -NIST_PREFIXES = ['Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei'] +NIST_PREFIXES = ['Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'] #: Byte values represented by each NIST prefix unit NIST_STEPS = { @@ -125,7 +126,9 @@ 'Gi': 1073741824, 'Ti': 1099511627776, 'Pi': 1125899906842624, - 'Ei': 1152921504606846976 + 'Ei': 1152921504606846976, + 'Zi': 1180591620717411303424, + 'Yi': 1208925819614629174706176 } #: String representation, ex: ``13.37 MiB``, or ``42.0 kB`` @@ -239,7 +242,7 @@ def _norm(self, value): :raises ValueError: if the input value is not a type of real number """ if isinstance(value, self.valid_types): - self._byte_value = value * self._unit_value + self._byte_value = float(value) * self._unit_value self._bit_value = self._byte_value * 8.0 else: raise ValueError("Initialization value '%s' is of an invalid type: %s. " @@ -656,8 +659,12 @@ def to_Eb(self): Eb = property(lambda s: s.to_Eb()) ################################################################## - # The SI units go beyond the NIST units. They also have the Zetta - # and Yotta prefixes. + + def to_ZiB(self): + return ZiB(bits=self._bit_value) + + def to_Zib(self): + return Zib(bits=self._bit_value) def to_ZB(self): return ZB(bits=self._bit_value) @@ -665,19 +672,27 @@ def to_ZB(self): def to_Zb(self): return Zb(bits=self._bit_value) - # Properties + ZiB = property(lambda s: s.to_ZiB()) + Zib = property(lambda s: s.to_Zib()) ZB = property(lambda s: s.to_ZB()) Zb = property(lambda s: s.to_Zb()) ################################################################## + def to_YiB(self): + return YiB(bits=self._bit_value) + + def to_Yib(self): + return Yib(bits=self._bit_value) + def to_YB(self): return YB(bits=self._bit_value) def to_Yb(self): return Yb(bits=self._bit_value) - #: A new object representing this instance as a Yottabyte + YiB = property(lambda s: s.to_YiB()) + Yib = property(lambda s: s.to_Yib()) YB = property(lambda s: s.to_YB()) Yb = property(lambda s: s.to_Yb()) @@ -1024,6 +1039,22 @@ def _setup(self): Eio = EiB +class ZiB(Byte): + def _setup(self): + return (2, 70, 'ZiB', 'ZiBs') + + +Zio = ZiB + + +class YiB(Byte): + def _setup(self): + return (2, 80, 'YiB', 'YiBs') + + +Yio = YiB + + ###################################################################### # SI Prefixes for Byte based types class kB(Byte): @@ -1140,6 +1171,16 @@ def _setup(self): return (2, 60, 'Eib', 'Eibs') +class Zib(Bit): + def _setup(self): + return (2, 70, 'Zib', 'Zibs') + + +class Yib(Bit): + def _setup(self): + return (2, 80, 'Yib', 'Yibs') + + ###################################################################### # SI Prefixes for Bit based types class kb(Bit): diff --git a/tests/test_instantiating.py b/tests/test_instantiating.py index 3de2990..6269760 100644 --- a/tests/test_instantiating.py +++ b/tests/test_instantiating.py @@ -103,3 +103,38 @@ def test_bitmath_Bitmath_cannot_be_instantiated(self): """Instantiation fails if we try to instantiate bitmath.Bitmath""" with self.assertRaises(NotImplementedError): bitmath.Bitmath(1337) + + ################################################################## + # ZiB, YiB, Zib, Yib — NIST large units added in 2.0.0 + + def test_ZiB_instantiation(self): + """ZiB can be instantiated""" + self.assertIsInstance(bitmath.ZiB(1), bitmath.ZiB) + + def test_YiB_instantiation(self): + """YiB can be instantiated""" + self.assertIsInstance(bitmath.YiB(1), bitmath.YiB) + + def test_Zib_instantiation(self): + """Zib can be instantiated""" + self.assertIsInstance(bitmath.Zib(1), bitmath.Zib) + + def test_Yib_instantiation(self): + """Yib can be instantiated""" + self.assertIsInstance(bitmath.Yib(1), bitmath.Yib) + + def test_ZiB_in_all_unit_types(self): + """ZiB is listed in ALL_UNIT_TYPES""" + self.assertIn('ZiB', bitmath.ALL_UNIT_TYPES) + + def test_YiB_in_all_unit_types(self): + """YiB is listed in ALL_UNIT_TYPES""" + self.assertIn('YiB', bitmath.ALL_UNIT_TYPES) + + def test_Zib_in_all_unit_types(self): + """Zib is listed in ALL_UNIT_TYPES""" + self.assertIn('Zib', bitmath.ALL_UNIT_TYPES) + + def test_Yib_in_all_unit_types(self): + """Yib is listed in ALL_UNIT_TYPES""" + self.assertIn('Yib', bitmath.ALL_UNIT_TYPES) diff --git a/tests/test_parse.py b/tests/test_parse.py index 1b042e2..3da1545 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -72,6 +72,18 @@ def test_parse_Eio(self): bitmath.parse_string("654 Eio"), bitmath.EiB(654)) + def test_parse_Zio(self): + """parse_string works on zebioctet strings""" + self.assertEqual( + bitmath.parse_string("654 Zio"), + bitmath.ZiB(654)) + + def test_parse_Yio(self): + """parse_string works on yobioctet strings""" + self.assertEqual( + bitmath.parse_string("654 Yio"), + bitmath.YiB(654)) + # SI 'octet' based units def test_parse_Mo(self): """parse_string works on megaoctet strings""" @@ -85,6 +97,18 @@ def test_parse_Eo(self): bitmath.parse_string("654 Eo"), bitmath.EB(654)) + def test_parse_Zo(self): + """parse_string works on zettaoctet strings""" + self.assertEqual( + bitmath.parse_string("654 Zo"), + bitmath.ZB(654)) + + def test_parse_Yo(self): + """parse_string works on yottaoctet strings""" + self.assertEqual( + bitmath.parse_string("654 Yo"), + bitmath.YB(654)) + ###################################################################### def test_parse_bad_float(self): diff --git a/tests/test_properties.py b/tests/test_properties.py index 7506216..ece0e21 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -57,3 +57,19 @@ def test_write_property_fails(self): """bitmath type's properties are read-only""" with self.assertRaises(AttributeError): self.kib.value += 42 + + def test_ZiB_property(self): + """ZiB property returns a ZiB instance""" + self.assertIsInstance(self.kib.ZiB, bitmath.ZiB) + + def test_YiB_property(self): + """YiB property returns a YiB instance""" + self.assertIsInstance(self.kib.YiB, bitmath.YiB) + + def test_Zib_property(self): + """Zib property returns a Zib instance""" + self.assertIsInstance(self.kib.Zib, bitmath.Zib) + + def test_Yib_property(self): + """Yib property returns a Yib instance""" + self.assertIsInstance(self.kib.Yib, bitmath.Yib) diff --git a/tests/test_rich_comparison.py b/tests/test_rich_comparison.py index 3eae0d2..c52818d 100644 --- a/tests/test_rich_comparison.py +++ b/tests/test_rich_comparison.py @@ -106,3 +106,48 @@ def test_equal_num(self): def test_equal_false_num(self): """Unequal objects aren't equal with numbers""" self.assertNotEqual(self.kib, 42) + + ################################################################## + # Equality for large NIST units (regression for issue #54) + # Python 2 had a float vs long comparison bug for these sizes. + # Python 3 unifies integers, so these must compare equal. + + def test_ZiB_equal_direct(self): + """ZiB(654) == ZiB(654)""" + self.assertEqual(bitmath.ZiB(654), bitmath.ZiB(654)) + + def test_ZiB_equal_parsed(self): + """parse_string('654 ZiB') == ZiB(654)""" + self.assertEqual(bitmath.parse_string("654 ZiB"), bitmath.ZiB(654)) + + def test_YiB_equal_direct(self): + """YiB(654) == YiB(654)""" + self.assertEqual(bitmath.YiB(654), bitmath.YiB(654)) + + def test_YiB_equal_parsed(self): + """parse_string('654 YiB') == YiB(654)""" + self.assertEqual(bitmath.parse_string("654 YiB"), bitmath.YiB(654)) + + def test_Zib_equal_direct(self): + """Zib(654) == Zib(654)""" + self.assertEqual(bitmath.Zib(654), bitmath.Zib(654)) + + def test_Zib_equal_parsed(self): + """parse_string('654 Zib') == Zib(654)""" + self.assertEqual(bitmath.parse_string("654 Zib"), bitmath.Zib(654)) + + def test_Yib_equal_direct(self): + """Yib(654) == Yib(654)""" + self.assertEqual(bitmath.Yib(654), bitmath.Yib(654)) + + def test_Yib_equal_parsed(self): + """parse_string('654 Yib') == Yib(654)""" + self.assertEqual(bitmath.parse_string("654 Yib"), bitmath.Yib(654)) + + def test_ZiB_to_YiB_conversion(self): + """1024 ZiB == 1 YiB""" + self.assertEqual(bitmath.ZiB(1024).YiB, bitmath.YiB(1)) + + def test_Zib_to_Yib_conversion(self): + """1024 Zib == 1 Yib""" + self.assertEqual(bitmath.Zib(1024).Yib, bitmath.Yib(1)) From 4e37435ddc6ec3fc5e61df43d4585c47048b64ea Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 13:07:18 -0500 Subject: [PATCH 66/92] Update module.rst: reflect parse_string changes and deprecate parse_string_unsafe - parse_string() signature updated to show strict and system params - Add parse-string-loose section documenting strict=False behaviour, replacing the old parse_string_unsafe description and examples - parse_string_unsafe() section marked deprecated as of 2.0.0; shows migration instructions and how to suppress the warning - Default system updated to NIST throughout (was SI in old docs) --- docsite/source/module.rst | 307 ++++++++++++++++++-------------------- 1 file changed, 146 insertions(+), 161 deletions(-) diff --git a/docsite/source/module.rst b/docsite/source/module.rst index affcf2a..bfa802c 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -222,17 +222,24 @@ bitmath.listdir() bitmath.parse_string() ====================== -.. function:: parse_string(str_repr) - - Parse a string representing a unit into a proper bitmath - object. All non-string inputs are rejected and will raise a - :py:exc:`ValueError`. Strings without units are also rejected. See - the examples below for additional clarity. - - :param string str_repr: The string to parse. May contain whitespace - between the value and the unit. - :return: A bitmath object representing ``str_repr`` - :raises ValueError: if ``str_repr`` can not be parsed +.. function:: parse_string(str_repr, system=bitmath.NIST, strict=True) + + Parse a string (or, when ``strict=False``, a string or number) into + a bitmath object. + + :param str_repr: The value to parse. String inputs may include + whitespace between the value and the unit. + :param system: Unit system used when ``strict=False``. Ignored when + ``strict=True``. One of :py:data:`bitmath.NIST` + (default) or :py:data:`bitmath.SI`. + :param strict: When ``True`` (default) the unit must be an exact + bitmath type name such as ``"KiB"`` or ``"MB"``. + When ``False`` the parser accepts ambiguous input + such as plain numbers, numeric strings, and + case-insensitive single-letter units. See + :ref:`parse-string-loose` below. + :return: A bitmath object representing the input. + :raises ValueError: if the input cannot be parsed. A simple usage example: @@ -247,17 +254,15 @@ bitmath.parse_string() .. caution:: - Caution is advised if you are reading values from an unverified - external source, such as output from a shell command or a - generated file. Many applications (even ``/usr/bin/ls``) still - do not produce file size strings with valid (or even correct) - prefix units unless `specially configured to do so - `_. See - :py:func:`bitmath.parse_string_unsafe` as an alternative. + Caution is advised when reading values from an unverified + external source such as shell command output or a generated file. + Many applications (even ``/usr/bin/ls``) do not produce file + size strings with valid prefix units unless `specially configured + `_. + Use ``strict=False`` for those cases — see :ref:`parse-string-loose`. - To protect your application from unexpected runtime errors it is - recommended that calls to :py:func:`bitmath.parse_string` are - wrapped in a ``try`` statement: + To protect your application from unexpected runtime errors, wrap + calls in a ``try`` statement: .. code-block:: python @@ -270,8 +275,7 @@ bitmath.parse_string() Error while parsing string into bitmath object - Here we can see some more examples of invalid input, as well as two - acceptable inputs: + Here are some more examples of valid and invalid input: .. code-block:: python @@ -318,157 +322,138 @@ bitmath.parse_string() >>> print(bitmath.parse_string("1337 Eio")) 1337.0 EiB - Notice how on lines **4** and **5** that the variable - ``a_mebibyte`` from the input ``1 MiB`` is exactly equivalent to - ``a_mebioctet`` from the different input ``1 Mio``. This is because - after :py:mod:`bitmath` parses the octet units the results are - normalized into their **standard** NIST/SI equivalents - automatically. + Notice how on lines **4** and **5** the variable ``a_mebibyte`` + from the input ``"1 MiB"`` is exactly equivalent to ``a_mebioctet`` + from ``"1 Mio"``. After parsing, octet units are normalised into + their standard NIST/SI equivalents automatically. + .. versionchanged:: 2.0.0 + Added ``strict`` and ``system`` parameters. The default + ``strict=True`` behaviour is identical to earlier versions. + ``system`` defaults to :py:data:`bitmath.NIST` and is only + consulted when ``strict=False``. - .. note:: + .. versionadded:: 1.1.0 - If your input isn't compatible with - :py:func:`bitmath.parse_string` you can try using - :py:func:`bitmath.parse_string_unsafe` - instead. :py:func:`bitmath.parse_string_unsafe` is more - forgiving with input. Please read the documentation carefully so - you understand the risks you assume using the ``unsafe`` parser. - .. versionadded:: 1.1.0 +.. _parse-string-loose: + +parse_string with ``strict=False`` +----------------------------------- + +When ``strict=False`` the parser accepts ambiguous input that does not +conform to exact bitmath type names. This is the behaviour that was +previously provided by the now-deprecated +:py:func:`bitmath.parse_string_unsafe`. + +All inputs are assumed to be **byte-based** (not bit-based). The +following additional rules apply: + +* Plain numbers and numeric strings are interpreted as a number of bytes. +* Single-letter units (``k``, ``M``, ``G``, etc.) default to NIST + (base-2) unless ``system=bitmath.SI`` is passed. +* Inputs with an ``i`` after the leading letter (``Ki``, ``Mi``, …) + are always treated as NIST units regardless of ``system``. +* Capitalisation does not matter. + +The result is returned in the parsed unit system. To coerce it to a +preferred system call ``.best_prefix(system=system)`` on the return +value. + +In this example we parse the output of ``df -H / /boot /home``, +whose ``Used`` column contains SI units:: + + Filesystem Size Used Avail Use% Mounted on + /dev/mapper/luks-ca8d5493-72bb-4691-afe1 107G 64G 38G 63% / + /dev/sda1 500M 391M 78M 84% /boot + /dev/mapper/vg_deepfryer-lv_home 129G 118G 4.7G 97% /home + +.. code-block:: python + :linenos: + :emphasize-lines: 7 + + >>> with open('/tmp/df-output.txt', 'r') as fp: + ... _ = fp.readline() # skip header + ... for line in fp.readlines(): + ... cols = line.split()[0:4] + ... print("""Filesystem: %s + ... - Used: %s""" % (cols[0], + ... bitmath.parse_string(cols[1], strict=False, system=bitmath.SI))) + Filesystem: /dev/mapper/luks-ca8d5493-72bb-4691-afe1 + - Used: 107.0 GB + Filesystem: /dev/sda1 + - Used: 500.0 MB + Filesystem: /dev/mapper/vg_deepfryer-lv_home + - Used: 129.0 GB + +If the ``df`` command had been run with ``-h`` (NIST output) instead +of ``-H`` the values differ slightly but still use the same single +letter unit ``G``. Pass ``system=bitmath.NIST`` (or omit ``system`` +entirely, as NIST is the default) to interpret them correctly: + +.. code-block:: python + :linenos: + :emphasize-lines: 7 + + >>> with open('/tmp/df-output.txt', 'r') as fp: + ... _ = fp.readline() # skip header + ... for line in fp.readlines(): + ... cols = line.split()[0:4] + ... print("""Filesystem: %s + ... - Used: %s""" % (cols[0], + ... bitmath.parse_string(cols[1], strict=False, system=bitmath.NIST))) + Filesystem: /dev/mapper/luks-ca8d5493-72bb-4691-afe1 + - Used: 100.0 GiB + Filesystem: /dev/sda1 + - Used: 477.0 MiB + Filesystem: /dev/mapper/vg_deepfryer-lv_home + - Used: 120.0 GiB + +The results now use the proper NIST prefix syntax: ``GiB``. bitmath.parse_string_unsafe() ============================= -.. function:: parse_string_unsafe(repr[, system=bitmath.SI]) - - Parse a string or number into a proper bitmath object. This is the - less strict version of the :py:func:`bitmath.parse_string` - function. While :py:func:`bitmath.parse_string` only accepts SI and - NIST defined unit prefixes, :py:func:`bitmath.parse_string_unsafe` - accepts *non-standard* units such as those often displayed in - command-line output. Examples following the description. - - :param repr: The value to parse. May contain whitespace between the - value and the unit. - - :param system: :py:func:`bitmath.parse_string_unsafe` defaults to - parsing units as ``SI`` (base-10) units. Set the - ``system`` parameter to :py:data:`bitmath.NIST` if - you know your input is in ``NIST`` (base-2) format. - - :return: A bitmath object representing ``repr`` - :raises ValueError: if ``repr`` can not be parsed - - Use of this function comes with several caveats: - - * All inputs are assumed to be byte-based (as opposed to bit based) - * Numerical inputs (those without any units) are assumed to be a number of bytes - * Inputs with single letter units (``k``, ``M``, ``G``, etc) are - assumed to be SI units (base-10). See the ``system`` parameter - description **above** to change this behavior - * Inputs with an ``i`` character following the leading letter (``Ki``, - ``Mi``, ``Gi``) are assumed to be NIST units (base-2) - * Capitalization does not matter - - What exactly are these *non-standard* units? Generally speaking - non-standard units will not include enough information to be able - to identify exactly which unit system is being used. This is caused - by mis-capitalized characters (capital ``k``'s for SI *kilo* units - when they should be lower case), or omitted Byte or Bit - suffixes. You can find examples of non-standard units in many - common command line functions or parameters. For example: - - * The ``ls`` command will print out single-letter units when given - the ``-h`` option flag - * Running ``qemu-img info virtualdisk.img`` will also report with - single letter units - * The ``df`` command also uses single-letter units - * `Kubernetes - `_ will - display items like *memory limits* using two letter NIST units - (ex: ``memory: 2370Mi``) - - Given those considerations, understanding exactly what values you - are feeding into this function is crucial to getting accurate - results. You can control the output of some commands with various - option flags. For example, you could ensure the GNU ``ls`` and - ``df`` commands print with SI values by providing the ``--si`` - option flag. By default those commands will print out using NIST - (base-2) values. - - In this example let's pretend we're parsing the output of running - ``df -H / /boot /home`` on our filesystems. Assume the output is - saved into a file called ``/tmp/df-output.txt`` and looks like - this:: - - Filesystem Size Used Avail Use% Mounted on - /dev/mapper/luks-ca8d5493-72bb-4691-afe1 107G 64G 38G 63% / - /dev/sda1 500M 391M 78M 84% /boot - /dev/mapper/vg_deepfryer-lv_home 129G 118G 4.7G 97% /home - - Now let's read this file, parse the ``Used`` column, and then print - out the space used (line **7**): +.. deprecated:: 2.0.0 + + ``parse_string_unsafe`` is deprecated and will be removed in a + future release. Use :py:func:`bitmath.parse_string` with + ``strict=False`` instead: .. code-block:: python - :linenos: - :emphasize-lines: 7 - - >>> with open('/tmp/df-output.txt', 'r') as fp: - ... # Skip parsing the 'df' header column - ... _ = fp.readline() - ... for line in fp.readlines(): - ... cols = line.split()[0:4] - ... print("""Filesystem: %s) - ... - Used: %s""" % (cols[0], bitmath.parse_string_unsafe(cols[1])) - Filesystem: /dev/mapper/luks-ca8d5493-72bb-4691-afe1 - - Used: 107.0 GB - Filesystem: /dev/sda1 - - Used: 500.0 MB - Filesystem: /dev/mapper/vg_deepfryer-lv_home - - Used: 129.0 GB - - - If we had ran the ``df`` command with the ``-h`` option (instead of - ``-H``) we will get base-2 (NIST) output. That would look like - this:: - - Filesystem Size Used Avail Use% Mounted on - /dev/mapper/luks-ca8d5493-72bb-4691-afe1 100G 59G 36G 63% / - /dev/sda1 477M 373M 75M 84% /boot - /dev/mapper/vg_deepfryer-lv_home 120G 110G 4.4G 97% /home - - Because we switch from ``SI`` output to ``NIST`` output the values - displayed are slightly different. **However** they still print - using the same prefix unit, ``G``. We can tell - :py:func:`bitmath.parse_string_unsafe` that the input is ``NIST`` - (base-2) by giving ``bitmath.NIST`` to the ``system`` parameter - like this (line **8**): + + # old + bitmath.parse_string_unsafe(value, system=bitmath.NIST) + + # new + bitmath.parse_string(value, strict=False, system=bitmath.NIST) + + To suppress the deprecation warning in the interim: .. code-block:: python - :linenos: - :emphasize-lines: 8 - - >>> with open('/tmp/df-output.txt', 'r') as fp: - ... # Skip parsing the 'df' header column - ... _ = fp.readline() - ... for line in fp.readlines(): - ... cols = line.split()[0:4] - ... print("""Filesystem: %s - ... - Used: %s""" % (cols[0], - ... bitmath.parse_string_unsafe(cols[1], \ - ... system=bitmath.NIST))) - Filesystem: /dev/mapper/luks-ca8d5493-72bb-4691-afe1 - - Used: 100.0 GiB - Filesystem: /dev/sda1 - - Used: 477.0 MiB - Filesystem: /dev/mapper/vg_deepfryer-lv_home - - Used: 120.0 GiB - - The results printed use the proper NIST prefix unit syntax now: - Capital **G** followed by a lower-case **i** ending with a capital - **B**, ``GiB``. + + import warnings + warnings.filterwarnings('ignore', category=DeprecationWarning, + module='bitmath') + +.. function:: parse_string_unsafe(repr[, system=bitmath.NIST]) + + A deprecated thin wrapper around + ``parse_string(repr, strict=False, system=system)``. All behaviour, + parameters, and caveats are identical to + :ref:`parse_string with strict=False `. + + :param repr: The value to parse. + :param system: :py:data:`bitmath.NIST` (default) or + :py:data:`bitmath.SI`. + :return: A bitmath object representing ``repr``. + :raises ValueError: if ``repr`` cannot be parsed. + + .. versionchanged:: 2.0.0 + Deprecated. Default ``system`` changed from ``bitmath.SI`` to + ``bitmath.NIST`` for consistency with + :py:func:`bitmath.parse_string`. .. versionadded:: 1.3.1 From 16e2fc301db0b3d68de60fea9a15c9cf35bd9c6a Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 14:55:52 -0500 Subject: [PATCH 67/92] Rewrite parse_string strict=False docs: system is a tiebreaker, not an override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace vague bullet list with a three-tier precedence model that accurately describes when system is and is not consulted: 1. No unit → always Byte, system irrelevant 2. Self-describing unit (i-marker present) → always NIST, system irrelevant 3. Ambiguous single-letter unit → system decides (NIST default) Lead with an .. important:: block scoped to strict=False so readers understand the constraint before seeing the examples. --- docsite/source/module.rst | 58 +++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/docsite/source/module.rst b/docsite/source/module.rst index bfa802c..74befc7 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -342,26 +342,49 @@ parse_string with ``strict=False`` ----------------------------------- When ``strict=False`` the parser accepts ambiguous input that does not -conform to exact bitmath type names. This is the behaviour that was -previously provided by the now-deprecated +conform to exact bitmath type names — for example, the single-letter +units produced by tools like ``ls -h``, ``df``, and ``qemu-img``. This +is the behaviour previously provided by the now-deprecated :py:func:`bitmath.parse_string_unsafe`. -All inputs are assumed to be **byte-based** (not bit-based). The -following additional rules apply: +All inputs are treated as **byte-based**. Bit-based units are not +supported in loose parsing mode. Capitalisation does not matter. -* Plain numbers and numeric strings are interpreted as a number of bytes. -* Single-letter units (``k``, ``M``, ``G``, etc.) default to NIST - (base-2) unless ``system=bitmath.SI`` is passed. -* Inputs with an ``i`` after the leading letter (``Ki``, ``Mi``, …) - are always treated as NIST units regardless of ``system``. -* Capitalisation does not matter. +.. _parse-string-system-hint: -The result is returned in the parsed unit system. To coerce it to a -preferred system call ``.best_prefix(system=system)`` on the return -value. +Understanding the ``system`` parameter in loose mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. important:: + + In ``strict=False`` mode, ``system`` is a **tiebreaker**, not a + guarantee. It is only consulted when the parser cannot determine the + unit system from the input itself. Passing ``system=bitmath.SI`` + does **not** force all results to be SI units. + +The parser resolves the unit system in the following order of +precedence: + +1. **No unit present** — plain numbers and numeric strings (e.g. + ``100``, ``"2048"``) are always returned as :class:`bitmath.Byte` + regardless of ``system``. + +2. **Unit is self-describing** — inputs whose unit already contains an + ``i`` marker (e.g. ``"100 KiB"``, ``"4Gi"``) unambiguously identify + a NIST unit. ``system`` is ignored and the result is always NIST. + +3. **Unit is ambiguous** — single-letter units such as ``k``, ``M``, + ``G`` carry no inherent system information. Only here does + ``system`` act as the deciding hint: ``system=bitmath.NIST`` + (the default) interprets ``"4G"`` as ``GiB(4)``; passing + ``system=bitmath.SI`` interprets it as ``GB(4)``. + +In summary: ``system`` resolves ambiguity — it does not override +evidence already present in the input string. In this example we parse the output of ``df -H / /boot /home``, -whose ``Used`` column contains SI units:: +whose ``Used`` column contains single-letter SI units. Because the +units are ambiguous we pass ``system=bitmath.SI`` as a hint:: Filesystem Size Used Avail Use% Mounted on /dev/mapper/luks-ca8d5493-72bb-4691-afe1 107G 64G 38G 63% / @@ -386,10 +409,9 @@ whose ``Used`` column contains SI units:: Filesystem: /dev/mapper/vg_deepfryer-lv_home - Used: 129.0 GB -If the ``df`` command had been run with ``-h`` (NIST output) instead -of ``-H`` the values differ slightly but still use the same single -letter unit ``G``. Pass ``system=bitmath.NIST`` (or omit ``system`` -entirely, as NIST is the default) to interpret them correctly: +If ``df`` is run with ``-h`` instead of ``-H`` it produces NIST-sized +values but still prints the same single-letter units. Omit ``system`` +(NIST is the default) or pass ``system=bitmath.NIST`` explicitly: .. code-block:: python :linenos: From f030c64f29adf4a436cd8de4a6eaeb4380ca1358 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 14:59:20 -0500 Subject: [PATCH 68/92] Unify parse_string terminology: replace 'loose' with 'non-strict' throughout --- bitmath/__init__.py | 2 +- docsite/source/module.rst | 14 +++++++------- tests/test_parse.py | 22 +++++++++++----------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index dec017a..44c7780 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -1520,7 +1520,7 @@ def parse_string(s, system=NIST, strict=True): raise ValueError("Can't parse string %s into a bitmath object" % s) else: - # loose / non-strict path (formerly parse_string_unsafe) + # strict=False path (formerly parse_string_unsafe) if not isinstance(s, (str)) and \ not isinstance(s, numbers.Number): raise ValueError("parse_string only accepts string/number inputs but a %s was given" % diff --git a/docsite/source/module.rst b/docsite/source/module.rst index 74befc7..2af4436 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -237,7 +237,7 @@ bitmath.parse_string() When ``False`` the parser accepts ambiguous input such as plain numbers, numeric strings, and case-insensitive single-letter units. See - :ref:`parse-string-loose` below. + :ref:`parse-string-non-strict` below. :return: A bitmath object representing the input. :raises ValueError: if the input cannot be parsed. @@ -259,7 +259,7 @@ bitmath.parse_string() Many applications (even ``/usr/bin/ls``) do not produce file size strings with valid prefix units unless `specially configured `_. - Use ``strict=False`` for those cases — see :ref:`parse-string-loose`. + Use ``strict=False`` for those cases — see :ref:`parse-string-non-strict`. To protect your application from unexpected runtime errors, wrap calls in a ``try`` statement: @@ -336,7 +336,7 @@ bitmath.parse_string() .. versionadded:: 1.1.0 -.. _parse-string-loose: +.. _parse-string-non-strict: parse_string with ``strict=False`` ----------------------------------- @@ -348,12 +348,12 @@ is the behaviour previously provided by the now-deprecated :py:func:`bitmath.parse_string_unsafe`. All inputs are treated as **byte-based**. Bit-based units are not -supported in loose parsing mode. Capitalisation does not matter. +supported in non-strict parsing mode. Capitalisation does not matter. .. _parse-string-system-hint: -Understanding the ``system`` parameter in loose mode -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Understanding the ``system`` parameter in non-strict mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. important:: @@ -464,7 +464,7 @@ bitmath.parse_string_unsafe() A deprecated thin wrapper around ``parse_string(repr, strict=False, system=system)``. All behaviour, parameters, and caveats are identical to - :ref:`parse_string with strict=False `. + :ref:`parse_string with strict=False `. :param repr: The value to parse. :param system: :py:data:`bitmath.NIST` (default) or diff --git a/tests/test_parse.py b/tests/test_parse.py index 3da1545..ddce52c 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -144,12 +144,12 @@ def test_parse_string_unicode(self): ###################################################################### - def test_parse_loose_bad_input_type(self): + def test_parse_non_strict_bad_input_type(self): """parse_string strict=False can identify invalid input types""" with self.assertRaises(ValueError): bitmath.parse_string({'keyvalue': 'store'}, strict=False) - def test_parse_loose_invalid_input(self): + def test_parse_non_strict_invalid_input(self): """parse_string strict=False raises ValueError for invalid units""" with self.assertRaises(ValueError): bitmath.parse_string("kitties!", strict=False) @@ -160,7 +160,7 @@ def test_parse_loose_invalid_input(self): with self.assertRaises(ValueError): bitmath.parse_string('100 J', strict=False) - def test_parse_loose_good_number_input(self): + def test_parse_non_strict_good_number_input(self): """parse_string strict=False can parse unitless number inputs""" number_input = 100 string_input = "100" @@ -173,7 +173,7 @@ def test_parse_loose_good_number_input(self): bitmath.parse_string(string_input, strict=False), expected_result) - def test_parse_loose_handles_SI_K_unit(self): + def test_parse_non_strict_handles_SI_K_unit(self): """parse_string strict=False can parse the upper/lowercase SI 'thousand' (k)""" thousand_lower = "100k" thousand_upper = "100K" @@ -186,7 +186,7 @@ def test_parse_loose_handles_SI_K_unit(self): bitmath.parse_string(thousand_upper, strict=False, system=bitmath.SI), expected_result) - def test_parse_loose_NIST_units(self): + def test_parse_non_strict_NIST_units(self): """parse_string strict=False can parse abbreviated NIST units (Gi, Ki, ...)""" nist_input = "100 Gi" expected_result = bitmath.GiB(100) @@ -195,7 +195,7 @@ def test_parse_loose_NIST_units(self): bitmath.parse_string(nist_input, strict=False), expected_result) - def test_parse_loose_SI(self): + def test_parse_non_strict_SI(self): """parse_string strict=False can parse all accepted SI inputs""" kilo_inputs = [ '100k', @@ -225,7 +225,7 @@ def test_parse_loose_SI(self): self.assertEqual(_parsed, expected_gig_result) self.assertIs(type(_parsed), type(expected_gig_result)) - def test_parse_loose_NIST(self): + def test_parse_non_strict_NIST(self): """parse_string strict=False can parse all accepted NIST inputs""" kilo_inputs = [ '100ki', @@ -255,7 +255,7 @@ def test_parse_loose_NIST(self): self.assertEqual(_parsed, expected_gig_result) self.assertIs(type(_parsed), type(expected_gig_result)) - def test_parse_loose_default_system_is_NIST(self): + def test_parse_non_strict_default_system_is_NIST(self): """parse_string strict=False defaults to NIST for ambiguous single-letter units""" self.assertEqual( bitmath.parse_string("100M", strict=False), @@ -264,7 +264,7 @@ def test_parse_loose_default_system_is_NIST(self): type(bitmath.parse_string("100k", strict=False)), bitmath.KiB) - def test_parse_loose_explicit_SI(self): + def test_parse_non_strict_explicit_SI(self): """parse_string strict=False uses SI when system=bitmath.SI""" self.assertEqual( bitmath.parse_string("100M", strict=False, system=bitmath.SI), @@ -273,7 +273,7 @@ def test_parse_loose_explicit_SI(self): type(bitmath.parse_string("100k", strict=False, system=bitmath.SI)), bitmath.kB) - def test_parse_loose_number_inputs_unaffected_by_system(self): + def test_parse_non_strict_number_inputs_unaffected_by_system(self): """parse_string strict=False returns Byte() for plain numbers regardless of system""" self.assertEqual( bitmath.parse_string("100", strict=False, system=bitmath.NIST), @@ -282,7 +282,7 @@ def test_parse_loose_number_inputs_unaffected_by_system(self): bitmath.parse_string(100, strict=False, system=bitmath.SI), bitmath.Byte(100)) - def test_parse_loose_github_issue_60(self): + def test_parse_non_strict_github_issue_60(self): """parse_string strict=False can parse the examples reported in issue #60 https://github.com/timlnx/bitmath/issues/60 From f74a05a186ee505007a17581ec7630267baba5fb Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 15:03:04 -0500 Subject: [PATCH 69/92] Clarify system param: only consulted when unit cannot be reliably determined --- docsite/source/module.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docsite/source/module.rst b/docsite/source/module.rst index 2af4436..35083a7 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -229,9 +229,11 @@ bitmath.parse_string() :param str_repr: The value to parse. String inputs may include whitespace between the value and the unit. - :param system: Unit system used when ``strict=False``. Ignored when - ``strict=True``. One of :py:data:`bitmath.NIST` - (default) or :py:data:`bitmath.SI`. + :param system: Unit system used when ``strict=False`` and the + intended unit cannot be reliably determined from the + input. Ignored when ``strict=True``. One of + :py:data:`bitmath.NIST` (default) or + :py:data:`bitmath.SI`. :param strict: When ``True`` (default) the unit must be an exact bitmath type name such as ``"KiB"`` or ``"MB"``. When ``False`` the parser accepts ambiguous input From df388163f13f695195a0e10699ea3c4e650f3acd Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 15:12:48 -0500 Subject: [PATCH 70/92] Update simple_examples.rst: fix stale examples, add new feature sections Fixes: - KiB(1)/3 result was 0.3330078125 (Python 2 integer division); replaced with KiB(6)/4 = KiB(1.5) which is exact and correct under Python 3 - reverse=True sort output showed raw KiB values for human_sizes instead of the MiB results that best_prefix() produces; corrected to match the forward-sorted output above it - Rich Comparison link updated from Python 2.7 to Python 3 docs New sections: - Parsing Strings: parse_string strict and non-strict mode with examples covering exact units, octet aliases, ambiguous single-letter units, plain numbers, and the i-marker override of system - Summing an Iterable: bitmath.sum() vs built-in sum() - Rounding: math.floor/ceil/round on bitmath instances, with warning about lossy intermediate rounding --- docsite/source/simple_examples.rst | 91 +++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/docsite/source/simple_examples.rst b/docsite/source/simple_examples.rst index 3bb6bcc..4804dfa 100644 --- a/docsite/source/simple_examples.rst +++ b/docsite/source/simple_examples.rst @@ -72,7 +72,7 @@ Math works mostly like you expect it to, except for a few edge-cases: +----------------+-------------------+---------------------+---------------------------------------+ | Division | ``bm1`` / ``bm2`` | ``type(num)`` | ``KiB(1) / KiB(2)`` = ``0.5`` | +----------------+-------------------+---------------------+---------------------------------------+ -| Division | ``bm`` / ``num`` | ``type(bm)`` | ``KiB(1) / 3`` = ``0.3330078125KiB`` | +| Division | ``bm`` / ``num`` | ``type(bm)`` | ``KiB(6) / 4`` = ``KiB(1.5)`` | +----------------+-------------------+---------------------+---------------------------------------+ | Division | ``num`` / ``bm`` | ``type(num)`` | ``3 / KiB(2)`` = ``1.5`` | +----------------+-------------------+---------------------+---------------------------------------+ @@ -145,7 +145,7 @@ Rich Comparison *************** Rich Comparison (as per the `Python Basic Customization -`_ +`_ magic methods) ``<``, ``<=``, ``==``, ``!=``, ``>``, ``>=`` is fully supported: @@ -207,4 +207,89 @@ Now print them out in descending magnitude .. code-block:: python >>> print(sorted(human_sizes, reverse=True)) - [KiB(7892.0), KiB(7337.0), KiB(4190.0), KiB(4003.0), KiB(2326.0), KiB(2178.0), KiB(2126.0), KiB(1770.0), KiB(1441.0), KiB(48.0)] + [MiB(7.70703125), MiB(7.1650390625), MiB(4.091796875), MiB(3.9091796875), MiB(2.271484375), MiB(2.126953125), MiB(2.076171875), MiB(1.728515625), MiB(1.4072265625), KiB(48.0)] + + +Parsing Strings +*************** + +:py:func:`bitmath.parse_string` converts a human-readable string into a +bitmath instance. By default (``strict=True``) the unit must be an exact +bitmath type name: + +.. code-block:: python + + >>> import bitmath + >>> bitmath.parse_string("4.7 GiB") + GiB(4.7) + >>> bitmath.parse_string("1337 MB") + MB(1337.0) + >>> bitmath.parse_string("1 Mio") # octet alias + MiB(1.0) + +When the input comes from a tool that produces ambiguous single-letter +units, use ``strict=False``. Pass ``system=bitmath.SI`` or +``system=bitmath.NIST`` to tell the parser which system to assume for +those ambiguous units: + +.. code-block:: python + + >>> bitmath.parse_string("4G", strict=False) # NIST default + GiB(4.0) + >>> bitmath.parse_string("4G", strict=False, system=bitmath.SI) + GB(4.0) + >>> bitmath.parse_string("100", strict=False) # plain number → bytes + Byte(100.0) + >>> bitmath.parse_string("100 GiB", strict=False, system=bitmath.SI) # i-marker wins + GiB(100.0) + +.. seealso:: + + :py:func:`bitmath.parse_string` — full parameter reference and caveats. + + +Summing an Iterable +******************* + +Python's built-in :py:func:`sum` starts accumulation from the integer +``0``, which causes mixed-type addition to return a plain float rather +than a bitmath instance. Use :py:func:`bitmath.sum` instead: + +.. code-block:: python + + >>> import bitmath + >>> bitmath.sum([bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)]) + Byte(1074790401.0) + >>> bitmath.sum([bitmath.KiB(1), bitmath.KiB(2)], start=bitmath.MiB(0)) + MiB(0.0029296875) + + +Rounding +******** + +bitmath represents sizes as floating-point measurements. When an integer +result is needed, Python's :py:func:`math.floor`, :py:func:`math.ceil`, +and :py:func:`round` all work directly on bitmath instances and return +an instance of the same type: + +.. code-block:: python + + >>> import math, bitmath + >>> math.floor(bitmath.KiB(1) / 3) + KiB(0) + >>> math.ceil(bitmath.KiB(1) / 3) + KiB(1) + >>> round(bitmath.MiB(1.75)) + MiB(2) + >>> round(bitmath.GiB(1.23456), 2) + GiB(1.23) + +.. warning:: + + Rounding intermediate results is lossy. ``math.floor(GiB(10) / 3) * 3`` + yields ``GiB(9)``, not ``GiB(10)``. Only round at the final output step. + +.. seealso:: + + :ref:`Appendix: Rules for Math ` — discussion of + floating-point representation and when rounding is appropriate. From 580e8cb0580c10b74a9370c08260e2951966084e Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 15:26:17 -0500 Subject: [PATCH 71/92] docs: update sum() docs to distinguish built-in sum() from bitmath.sum() Built-in sum() now works with bitmath objects via the __radd__ identity (0 + bm = bm), preserving the type of the first element. Document this alongside bitmath.sum() so users know when to reach for each: built-in for type-preserving accumulation, bitmath.sum() for normalised-to-Byte (or custom start) output regardless of input types. Update the bitmath.sum() docstring to match, replacing the stale claim that built-in sum() returns a plain float. --- bitmath/__init__.py | 15 +++++++------ docsite/source/simple_examples.rst | 36 +++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 44c7780..1d93358 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -1608,15 +1608,16 @@ def parse_string_unsafe(s, system=NIST): def sum(iterable, start=None): - """Sum an iterable of bitmath instances correctly. + """Sum an iterable of bitmath instances, returning a Byte by default. -The built-in sum() starts from integer 0, which causes __radd__ to -return a plain float instead of a bitmath instance. This function -starts from Byte(0) so all additions go through the bitmath __add__ -path with proper unit conversion. +The built-in sum() also works with bitmath objects: the __radd__ +identity (0 + bm = bm) means sum() preserves the type of the first +element. Use bitmath.sum() instead when you need the result normalised +to a specific unit regardless of input types — it accumulates into +Byte(0) by default, or into the provided start instance. -- bitmath.sum([Byte(1), MiB(1), GiB(1)]) -> Byte(1074790401.0) -- bitmath.sum([KiB(1), KiB(2)], start=MiB(0)) -> MiB(0.00292...) +- bitmath.sum([MiB(1), GiB(1)]) -> Byte(1074790400.0) +- bitmath.sum([KiB(1), KiB(2)], start=MiB(0)) -> MiB(0.0029296875) """ result = Byte(0) if start is None else start for item in iterable: diff --git a/docsite/source/simple_examples.rst b/docsite/source/simple_examples.rst index 4804dfa..ee537b5 100644 --- a/docsite/source/simple_examples.rst +++ b/docsite/source/simple_examples.rst @@ -213,9 +213,9 @@ Now print them out in descending magnitude Parsing Strings *************** -:py:func:`bitmath.parse_string` converts a human-readable string into a -bitmath instance. By default (``strict=True``) the unit must be an exact -bitmath type name: +:py:func:`bitmath.parse_string` converts a human-readable string into +a bitmath instance. By default the unit must be an exact bitmath type +name: .. code-block:: python @@ -227,10 +227,11 @@ bitmath type name: >>> bitmath.parse_string("1 Mio") # octet alias MiB(1.0) -When the input comes from a tool that produces ambiguous single-letter -units, use ``strict=False``. Pass ``system=bitmath.SI`` or -``system=bitmath.NIST`` to tell the parser which system to assume for -those ambiguous units: +When the input comes from a tool that produces ambiguous output +(often-times single-letter units) use ``strict=False``. Pass +``system=bitmath.SI`` or ``system=bitmath.NIST`` to tell the parser +which system to use if the unit can not reliably be determined +automatically: .. code-block:: python @@ -251,15 +252,28 @@ those ambiguous units: Summing an Iterable ******************* -Python's built-in :py:func:`sum` starts accumulation from the integer -``0``, which causes mixed-type addition to return a plain float rather -than a bitmath instance. Use :py:func:`bitmath.sum` instead: +The built-in :py:func:`sum` works with bitmath objects. Because +``0 + bm`` returns ``bm`` itself (the identity element), accumulation +starts correctly and the result type matches the **first element** in +the iterable: .. code-block:: python >>> import bitmath - >>> bitmath.sum([bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)]) + >>> sum([bitmath.KiB(1), bitmath.KiB(2)]) + KiB(3.0) + >>> sum([bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)]) Byte(1074790401.0) + +Use :py:func:`bitmath.sum` when you need the result **normalised to a +specific unit** regardless of the input types. Without a ``start`` +argument it accumulates into :class:`bitmath.Byte`; pass ``start`` to +choose a different accumulator: + +.. code-block:: python + + >>> bitmath.sum([bitmath.MiB(1), bitmath.GiB(1)]) + Byte(1074790400.0) >>> bitmath.sum([bitmath.KiB(1), bitmath.KiB(2)], start=bitmath.MiB(0)) MiB(0.0029296875) From d2c5331bd4c99653e4b9115f57f1f33f7efad749 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 15:49:39 -0500 Subject: [PATCH 72/92] docs: add bitmath.sum() reference entry to module.rst Documents the bitmath.sum() function added in 2.0.0 in the module API reference, positioned after bitmath.listdir(). Includes parameter table, note contrasting it with built-in sum(), and a seealso cross-reference to the Getting Started page. Also adds the simple_examples_summing anchor needed by that cross-reference. --- docsite/source/module.rst | 60 ++++++++++++++++++++++++++++++ docsite/source/simple_examples.rst | 25 ++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/docsite/source/module.rst b/docsite/source/module.rst index 35083a7..78f1ed8 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -218,6 +218,66 @@ bitmath.listdir() .. versionadded:: 1.0.7 +.. _bitmath_sum: + +bitmath.sum() +============= + +.. function:: sum(iterable[, start=None]) + + Sum an iterable of bitmath instances into a single bitmath instance. + + :param iterable: Any iterable of bitmath objects to sum. + :param start: **Default:** ``None`` (accumulates into + :class:`bitmath.Byte`). Pass a bitmath instance to + set both the starting value and the result type. + :type start: A bitmath instance, or ``None`` + :returns: A bitmath instance whose type is determined by ``start`` + (or :class:`bitmath.Byte` when ``start`` is ``None``). + + .. note:: + + Python's built-in :py:func:`sum` also works with bitmath objects. + Because ``0 + bm`` returns ``bm`` itself (the ``__radd__`` identity + element), the built-in accumulates into the type of the **first + element** in the iterable. Use :py:func:`bitmath.sum` instead when + you need the result normalised to a **specific unit** regardless of + the input types. + + Sum a homogeneous list — result type matches ``start`` (``Byte`` by + default): + + .. code-block:: python + + >>> import bitmath + >>> bitmath.sum([bitmath.MiB(1), bitmath.GiB(1)]) + Byte(1074790400.0) + + Pass ``start`` to choose a different accumulator unit: + + .. code-block:: python + + >>> bitmath.sum([bitmath.KiB(1), bitmath.KiB(2)], start=bitmath.MiB(0)) + MiB(0.0029296875) + + Contrast with the built-in :py:func:`sum`, whose result type tracks the + first element: + + .. code-block:: python + + >>> sum([bitmath.KiB(1), bitmath.KiB(2)]) + KiB(3.0) + >>> sum([bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)]) + Byte(1074790401.0) + + .. seealso:: + + :ref:`Summing an Iterable ` in *Getting Started* + Side-by-side examples of built-in :py:func:`sum` vs + :py:func:`bitmath.sum`. + + .. versionadded:: 2.0.0 + bitmath.parse_string() ====================== diff --git a/docsite/source/simple_examples.rst b/docsite/source/simple_examples.rst index ee537b5..8ae55b4 100644 --- a/docsite/source/simple_examples.rst +++ b/docsite/source/simple_examples.rst @@ -249,6 +249,8 @@ automatically: :py:func:`bitmath.parse_string` — full parameter reference and caveats. +.. _simple_examples_summing: + Summing an Iterable ******************* @@ -265,10 +267,28 @@ the iterable: >>> sum([bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)]) Byte(1074790401.0) +Results from mixing plain numbers and numbers with units yields a +result with no units. + +.. code-block:: python + + >>> sum([bitmath.Byte(1), 0]) + 1.0 + >>> sum([1, bitmath.KiB(2)]) + 3.0 + +.. seealso:: + + :ref:`Appendix: Rules for Math ` — for a + thrilling discussion about the minute details when doing mixed-type + math math. What it all boils down to is this: if we don’t provide a + unit then bitmath won’t give us one back. + + Use :py:func:`bitmath.sum` when you need the result **normalised to a specific unit** regardless of the input types. Without a ``start`` argument it accumulates into :class:`bitmath.Byte`; pass ``start`` to -choose a different accumulator: +choose a different accumulator (resultant unit): .. code-block:: python @@ -276,7 +296,8 @@ choose a different accumulator: Byte(1074790400.0) >>> bitmath.sum([bitmath.KiB(1), bitmath.KiB(2)], start=bitmath.MiB(0)) MiB(0.0029296875) - + >>> bitmath.sum([bitmath.MiB(100), bitmath.KiB(2000)], start=bitmath.GiB(0)) + GiB(0.0995635986328125) Rounding ******** From 0b0303d5671a8a2b85b4783134b74a1a6e3a1da7 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 15:55:13 -0500 Subject: [PATCH 73/92] docs: sync SI_STEPS, NIST_STEPS, ALL_UNIT_TYPES constants with code SI_STEPS was missing Z and Y entries; NIST_STEPS was missing Zi and Yi (added with the ZiB/YiB/Zib/Yib units in 2.0.0); ALL_UNIT_TYPES used the old 'b'/'B' spellings instead of 'Bit'/'Byte' and was missing the Zb/ZB/Yb/YB/Zib/ZiB/Yib/YiB entries added this release. --- docsite/source/appendices/mixed_math.rst | 1 + docsite/source/module.rst | 28 ++++++++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/docsite/source/appendices/mixed_math.rst b/docsite/source/appendices/mixed_math.rst index 1a47032..6cf9eb2 100644 --- a/docsite/source/appendices/mixed_math.rst +++ b/docsite/source/appendices/mixed_math.rst @@ -100,6 +100,7 @@ Internally, this is implemented as: *Division* The result will be a number type due to unit cancellation. +.. _appendix_math_mixed_types: Mixed Types: Addition and Subtraction ===================================== diff --git a/docsite/source/module.rst b/docsite/source/module.rst index 78f1ed8..0225892 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -237,12 +237,12 @@ bitmath.sum() .. note:: - Python's built-in :py:func:`sum` also works with bitmath objects. - Because ``0 + bm`` returns ``bm`` itself (the ``__radd__`` identity - element), the built-in accumulates into the type of the **first - element** in the iterable. Use :py:func:`bitmath.sum` instead when - you need the result normalised to a **specific unit** regardless of - the input types. + Python's built-in :py:func:`sum` also works with bitmath + objects. Because ``0 + bm`` returns ``bm`` itself, the built-in + accumulates into the type of the **first element** in the + iterable. Use :py:func:`bitmath.sum` instead when you need the + result normalised to a **specific unit** regardless of the input + types. Sum a homogeneous list — result type matches ``start`` (``Byte`` by default): @@ -878,7 +878,9 @@ behavior. 'G': 1000000000, 'T': 1000000000000, 'P': 1000000000000000, - 'E': 1000000000000000000 + 'E': 1000000000000000000, + 'Z': 1000000000000000000000, + 'Y': 1000000000000000000000000 } @@ -900,7 +902,9 @@ behavior. 'Gi': 1073741824, 'Ti': 1099511627776, 'Pi': 1125899906842624, - 'Ei': 1152921504606846976 + 'Ei': 1152921504606846976, + 'Zi': 1180591620717411303424, + 'Yi': 1208925819614629174706176 } @@ -911,10 +915,10 @@ behavior. .. code-block:: python - ALL_UNIT_TYPES = ['b', 'B', 'kb', 'kB', 'Mb', 'MB', 'Gb', 'GB', - 'Tb', 'TB', 'Pb', 'PB', 'Eb', 'EB', 'Kib', 'KiB', 'Mib', - 'MiB', 'Gib', 'GiB', 'Tib', 'TiB', 'Pib', 'PiB', 'Eib', - 'EiB'] + ALL_UNIT_TYPES = ['Bit', 'Byte', 'kb', 'kB', 'Mb', 'MB', 'Gb', 'GB', + 'Tb', 'TB', 'Pb', 'PB', 'Eb', 'EB', 'Zb', 'ZB', 'Yb', 'YB', + 'Kib', 'KiB', 'Mib', 'MiB', 'Gib', 'GiB', 'Tib', 'TiB', + 'Pib', 'PiB', 'Eib', 'EiB', 'Zib', 'ZiB', 'Yib', 'YiB'] .. py:module:: bitmath.integrations From 53a0b285af5d5b857d1e3c7047a747e28d56d671 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 17:04:41 -0500 Subject: [PATCH 74/92] Fix the issue template locations --- .../bug_report.md} | 0 .github/ISSUE_TEMPLATE/feature_request.md | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+) rename .github/{ISSUE_TEMPLATE.md => ISSUE_TEMPLATE/bug_report.md} (100%) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug_report.md similarity index 100% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..f3a9fe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. + From 9e2f6b6831496d9ebc0c317e1ef23e153e503d60 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 17:11:41 -0500 Subject: [PATCH 75/92] fix: make bitmath.format() context manager thread-safe; implement bestprefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The format() context manager mutated module-level globals (format_string, format_plural) without a try/finally, causing two bugs: 1. In concurrent code, threads clobbered each other's saved/restored values — the race condition reported in issue #83. 2. An exception raised inside the with block left the globals permanently mutated for the remainder of the process. Fix by switching to threading.local storage. Each thread entering a context manager writes to its own _thread_local namespace; __str__ and the unit property read from that namespace (falling back to the module globals when not inside a context). Module globals are never written by the context manager, so threads not inside any context are unaffected. Nested contexts within a thread correctly save and restore the enclosing context's thread-local values via a sentinel-based save/restore pattern. Also implements the bestprefix parameter that was previously accepted but silently ignored: when bestprefix=True, __str__ calls self.best_prefix() before formatting, so the rendered string uses the best human-readable prefix unit automatically. Additional changes: - Fixed test_print_GiB_plural_fmt_in_mgr which was a copy-paste of the Byte test; it now tests GiB(3.0) as its name and docstring imply. - Added bestprefix, exception-safety, and nesting tests to test_context_manager.py. - Added tests/test_context_manager_thread_safe.py with six thread-safety tests including a direct reproduction of the issue #83 race condition. closes #83 --- bitmath/__init__.py | 87 ++++++--- tests/test_context_manager.py | 46 ++++- tests/test_context_manager_thread_safe.py | 211 ++++++++++++++++++++++ 3 files changed, 310 insertions(+), 34 deletions(-) create mode 100644 tests/test_context_manager_thread_safe.py diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 1d93358..2d6990c 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -51,6 +51,7 @@ import os.path import platform import sys +import threading # For device capacity reading in query_device_capacity(). Only supported # on posix systems for now. Will be addressed in issue #52 on GitHub. @@ -137,6 +138,24 @@ #: Pluralization behavior format_plural = False +# Thread-local storage for context manager overrides. When a thread is inside +# a bitmath.format() context, these shadow the module globals above for that +# thread only — other threads are unaffected. +_thread_local = threading.local() +_FMT_SENTINEL = object() # distinguishes "not set" from any real value + + +def _get_format_string(): + return getattr(_thread_local, 'format_string', format_string) + + +def _get_format_plural(): + return getattr(_thread_local, 'format_plural', format_plural) + + +def _get_bestprefix(): + return getattr(_thread_local, 'bestprefix', False) + def os_name(): # makes unittesting platform specific code easier @@ -308,12 +327,10 @@ def unit(self): >>> Byte(1.1).unit == 'Bytes' >>> Gb(2).unit == 'Gbs' """ - global format_plural # noqa: F824 - if self.prefix_value == 1: # If it's a '1', return it singular, no matter what return self._name_singular - elif format_plural: + elif _get_format_plural(): # Pluralization requested return self._name_plural else: @@ -392,8 +409,9 @@ def __repr__(self): def __str__(self): """String representation of this object""" - global format_string # noqa: F824 - return self.format(format_string) + if _get_bestprefix(): + return self.best_prefix().format(_get_format_string()) + return self.format(_get_format_string()) def format(self, fmt): """Return a representation of this instance formatted with user @@ -1626,39 +1644,48 @@ def sum(iterable, start=None): ###################################################################### -# Contxt Managers +# Context Managers @contextlib.contextmanager def format(fmt_str=None, plural=False, bestprefix=False): - """Context manager for printing bitmath instances. + """Thread-safe context manager for printing bitmath instances. -``fmt_str`` - a formatting mini-language compat formatting string. See +``fmt_str`` - a formatting mini-language compatible string. See the @properties (above) for a list of available items. -``plural`` - True enables printing instances with 's's if they're +``plural`` - True enables printing instances with 's' if they're plural. False (default) prints them as singular (no trailing 's'). -``bestprefix`` - True enables printing instances in their best -human-readable representation. False, the default, prints instances -using their current prefix unit. - """ - if 'bitmath' not in globals(): - import bitmath +``bestprefix`` - True converts instances to their best human-readable +prefix unit before formatting. False (default) formats the instance +as its current prefix unit. - if plural: - orig_fmt_plural = bitmath.format_plural - bitmath.format_plural = True - - if fmt_str: - orig_fmt_str = bitmath.format_string - bitmath.format_string = fmt_str - - yield - - if plural: - bitmath.format_plural = orig_fmt_plural - - if fmt_str: - bitmath.format_string = orig_fmt_str +All settings are thread-local: concurrent contexts in different threads +are fully isolated from one another. Nested contexts within the same +thread correctly save and restore the enclosing context's settings. + """ + prev_fmt = getattr(_thread_local, 'format_string', _FMT_SENTINEL) + prev_plural = getattr(_thread_local, 'format_plural', _FMT_SENTINEL) + prev_bestprefix = getattr(_thread_local, 'bestprefix', _FMT_SENTINEL) + + _thread_local.format_string = fmt_str if fmt_str is not None else format_string + _thread_local.format_plural = plural + _thread_local.bestprefix = bestprefix + + try: + yield + finally: + if prev_fmt is _FMT_SENTINEL: + del _thread_local.format_string + else: + _thread_local.format_string = prev_fmt + if prev_plural is _FMT_SENTINEL: + del _thread_local.format_plural + else: + _thread_local.format_plural = prev_plural + if prev_bestprefix is _FMT_SENTINEL: + del _thread_local.bestprefix + else: + _thread_local.bestprefix = prev_bestprefix def cli_script_main(cli_args): diff --git a/tests/test_context_manager.py b/tests/test_context_manager.py index a758060..c59bcb0 100644 --- a/tests/test_context_manager.py +++ b/tests/test_context_manager.py @@ -86,12 +86,12 @@ def test_print_byte_plural_fmt_in_mgr(self): self.assertEqual(expected_result, actual_result) def test_print_GiB_plural_fmt_in_mgr(self): - """TiB(1/3.0) prints out units in plural form, setting the fmt str in the mgr""" - expected_result = "3Bytes" + """GiB(3.0) prints out units in plural form, setting the fmt str in the mgr""" + expected_result = "3GiBs" with bitmath.format(fmt_str="{value:.1g}{unit}", plural=True): - three_Bytes = bitmath.Byte(3.0) - actual_result = str(three_Bytes) + three_GiB = bitmath.GiB(3.0) + actual_result = str(three_GiB) self.assertEqual(expected_result, actual_result) def test_print_GiB_singular_fmt_in_mgr(self): @@ -102,3 +102,41 @@ def test_print_GiB_singular_fmt_in_mgr(self): third_tibibyte = bitmath.TiB(1 / 3.0).best_prefix() actual_result = str(third_tibibyte) self.assertEqual(expected_result, actual_result) + + def test_bestprefix_in_context_manager(self): + """bestprefix=True causes str() to render the best human-readable prefix""" + with bitmath.format(bestprefix=True): + result = str(bitmath.MiB(1024)) + self.assertEqual(result, "1.0 GiB") + + def test_bestprefix_restores_after_context(self): + """bestprefix is not active outside the context manager""" + with bitmath.format(bestprefix=True): + pass + self.assertEqual(str(bitmath.MiB(1024)), "1024.0 MiB") + + def test_bestprefix_with_fmt_str(self): + """bestprefix=True combined with fmt_str applies the format to the converted unit""" + with bitmath.format(fmt_str="{value:.2f} {unit}", bestprefix=True): + result = str(bitmath.KiB(2048)) + self.assertEqual(result, "2.00 MiB") + + def test_format_restored_after_exception(self): + """format_string is restored to default even when an exception is raised""" + original = bitmath.format_string + try: + with bitmath.format(fmt_str="{value:.2f} {unit}"): + raise ValueError("boom") + except ValueError: + pass + self.assertEqual(bitmath.format_string, original) + self.assertEqual(str(bitmath.KiB(1)), "1.0 KiB") + + def test_nested_context_managers(self): + """Nested format contexts correctly save and restore enclosing context state""" + with bitmath.format(fmt_str="{value:.1f} {unit}"): + self.assertEqual(str(bitmath.KiB(1)), "1.0 KiB") + with bitmath.format(fmt_str="{value:.3f} {unit}"): + self.assertEqual(str(bitmath.KiB(1)), "1.000 KiB") + self.assertEqual(str(bitmath.KiB(1)), "1.0 KiB") + self.assertEqual(str(bitmath.KiB(1)), "1.0 KiB") diff --git a/tests/test_context_manager_thread_safe.py b/tests/test_context_manager_thread_safe.py new file mode 100644 index 0000000..cfad417 --- /dev/null +++ b/tests/test_context_manager_thread_safe.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# The MIT License (MIT) +# +# Copyright © 2026 Tim Case +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +""" +Thread-safety tests for the bitmath.format() context manager. + +These tests verify that concurrent context managers in different threads +are fully isolated: one thread's format_string, plural, and bestprefix +settings cannot bleed into another thread's context. + +Regression coverage for GitHub issue #83. +""" + +import queue +import threading + +from . import TestCase +import bitmath + + +THREAD_COUNT = 8 + + +class TestContextManagerThreadSafety(TestCase): + + def test_format_string_isolation(self): + """Concurrent format contexts expose only their own format_string (issue #83) + +Each of THREAD_COUNT threads enters a context with a unique format string, +then all wait at a barrier so they are guaranteed to be inside their +respective contexts simultaneously before formatting any strings. + """ + errors = queue.Queue() + barrier = threading.Barrier(THREAD_COUNT) + + def worker(thread_id): + fmt = "{value}-T" + str(thread_id) + expected = "1.0-T" + str(thread_id) + try: + with bitmath.format(fmt_str=fmt): + barrier.wait() + result = str(bitmath.KiB(1)) + if result != expected: + errors.put(AssertionError( + "Thread %d: expected %r, got %r" % ( + thread_id, expected, result))) + except Exception as exc: + errors.put(exc) + + threads = [ + threading.Thread(target=worker, args=(i,)) + for i in range(THREAD_COUNT) + ] + for t in threads: + t.start() + for t in threads: + t.join() + + if not errors.empty(): + raise errors.get() + + def test_plural_isolation(self): + """Concurrent format contexts expose only their own plural setting""" + errors = queue.Queue() + barrier = threading.Barrier(THREAD_COUNT) + + def plural_worker(expect_plural): + try: + with bitmath.format(plural=expect_plural): + barrier.wait() + result = str(bitmath.Byte(3.0)) + if expect_plural and result != "3.0 Bytes": + errors.put(AssertionError( + "plural thread: expected '3.0 Bytes', got %r" % result)) + elif not expect_plural and result != "3.0 Byte": + errors.put(AssertionError( + "singular thread: expected '3.0 Byte', got %r" % result)) + except Exception as exc: + errors.put(exc) + + threads = [ + threading.Thread(target=plural_worker, args=(i % 2 == 0,)) + for i in range(THREAD_COUNT) + ] + for t in threads: + t.start() + for t in threads: + t.join() + + if not errors.empty(): + raise errors.get() + + def test_bestprefix_isolation(self): + """Concurrent format contexts expose only their own bestprefix setting""" + errors = queue.Queue() + barrier = threading.Barrier(THREAD_COUNT) + + def bestprefix_worker(use_bestprefix): + try: + with bitmath.format(bestprefix=use_bestprefix): + barrier.wait() + result = str(bitmath.MiB(1024)) + if use_bestprefix and result != "1.0 GiB": + errors.put(AssertionError( + "bestprefix thread: expected '1.0 GiB', got %r" % result)) + elif not use_bestprefix and result != "1024.0 MiB": + errors.put(AssertionError( + "no-bestprefix thread: expected '1024.0 MiB', got %r" % result)) + except Exception as exc: + errors.put(exc) + + threads = [ + threading.Thread(target=bestprefix_worker, args=(i % 2 == 0,)) + for i in range(THREAD_COUNT) + ] + for t in threads: + t.start() + for t in threads: + t.join() + + if not errors.empty(): + raise errors.get() + + def test_issue_83_format_string_race(self): + """Reproduces the exact race condition reported in GitHub issue #83 + +The module-level format_string is set to an invalid key. Each thread +enters a context that supplies a valid format string. Without thread- +local storage all threads would see the invalid format_string after any +one of them exits its context, producing a KeyError. + """ + saved = bitmath.format_string + bitmath.format_string = "{not_a_valid_key}" + errors = queue.Queue() + barrier = threading.Barrier(THREAD_COUNT) + + def worker(): + try: + with bitmath.format(fmt_str=saved): + barrier.wait() + str(bitmath.KiB(1)) + except Exception as exc: + errors.put(exc) + + threads = [threading.Thread(target=worker) for _ in range(THREAD_COUNT)] + try: + for t in threads: + t.start() + for t in threads: + t.join() + finally: + bitmath.format_string = saved + + if not errors.empty(): + raise errors.get() + + def test_module_global_unchanged_by_context(self): + """The module-level format_string is never mutated by the context manager""" + original = bitmath.format_string + with bitmath.format(fmt_str="{value:.4f} {unit}"): + self.assertEqual(bitmath.format_string, original) + self.assertEqual(bitmath.format_string, original) + + def test_format_string_restored_after_exception_in_thread(self): + """Thread-local state is cleaned up even when an exception escapes the with block""" + errors = queue.Queue() + + def worker(): + try: + try: + with bitmath.format(fmt_str="{value:.2f} {unit}"): + raise ValueError("intentional") + except ValueError: + pass + result = str(bitmath.KiB(1)) + if result != "1.0 KiB": + errors.put(AssertionError( + "after exception: expected '1.0 KiB', got %r" % result)) + except Exception as exc: + errors.put(exc) + + t = threading.Thread(target=worker) + t.start() + t.join() + + if not errors.empty(): + raise errors.get() From 815a77c0389c517f05bc2dfca3440b027726319e Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 17:48:50 -0500 Subject: [PATCH 76/92] docs: rewrite contributing guide; add Windows viewdocs support Rewrote contributing.rst to reflect the current 2.0.0 toolchain: GitHub Actions CI, pytest, pycodestyle/flake8 checks, and accurate make targets. Added Windows (MINGW/Cygwin/MSYS) branch to viewdocs. --- Makefile | 2 + docsite/source/contributing.rst | 280 +++++++++++++++----------------- 2 files changed, 130 insertions(+), 152 deletions(-) diff --git a/Makefile b/Makefile index 8bb4939..2c85c56 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,8 @@ docsite/source/index.rst: docsite/source/index.rst.in README.rst VERSION viewdocs: docs @if [ "$$(uname)" = "Darwin" ]; then \ open docsite/build/html/index.html; \ + elif echo "$$(uname)" | grep -qi "mingw\|cygwin\|msys"; then \ + start docsite/build/html/index.html; \ else \ xdg-open docsite/build/html/index.html; \ fi diff --git a/docsite/source/contributing.rst b/docsite/source/contributing.rst index 074828e..4afe0c9 100644 --- a/docsite/source/contributing.rst +++ b/docsite/source/contributing.rst @@ -27,29 +27,32 @@ Conduct Issue Reporting *************** -If you are encounter an issue with the bitmath library, please use the +If you encounter an issue with the bitmath library, please use the provided template. * `Open a new issue `_ * `View open issues `_ -Code Style/Formatting -********************* +.. _contributing_code_style: -Please conform to :pep:`0008` for code formatting. This specification -outlines the style that is required for patches. +Code Style and Formatting +************************* -Your code must follow this (or note why it can't) before patches will -be accepted. There is one consistent exception to this rule: +Two static analysis checks run on every pull request as part of the +GitHub Actions CI workflow, and locally via ``make ci``: -**E501** - Line too long +* ``pycodestyle`` — checks code style, with **E501** (line too long) + and **E722** (bare ``except``) ignored. +* ``flake8 --select=F`` — runs pyflakes error checks only (undefined + names, unused imports, etc.). Style checks are disabled. -The ``pycodestyle`` tests for bitmath include a ``--ignore`` option to -automatically exclude **E501** errors from the tests. +A PR cannot be merged until both pass. Run ``make ci`` locally to +check before submitting. +.. _contributing_commit_messages: + Commit Messages *************** @@ -58,60 +61,34 @@ Please write `intelligent commit messages For example:: - Capitalized, short (50 chars or less) summary + Short summary (50 chars or less) - More detailed explanatory text, if necessary. Wrap it to about 72 - characters or so. In some contexts, the first line is treated as - the subject of an email and the rest of the text as the body. The - blank line separating the summary from the body is critical (unless - you omit the body entirely); tools like rebase can get confused if - you run the two together. + More detailed explanatory text, if necessary. Wrap it to about 72 + characters or so. Write your commit message in the imperative: "Fix bug" and not - "Fixed bug" or "Fixes bug." This convention matches up with commit - messages generated by commands like git merge and git revert. - - Further paragraphs come after blank lines. + "Fixed bug" or "Fixes bug." - Bullet points are okay, too - - Typically a hyphen or asterisk is used for the bullet, followed - by a single space, with blank lines in between, but conventions - vary here - - - Use a hanging indent +.. _contributing_pull_requests: Pull Requests ************* -After a `pull request `_ is -submitted on GitHub two automatic processes are started: - -#. `Travis-CI `_ clones the - new pull request and runs the :ref:`automated test suite - `. -#. `Coveralls `_ clones - the new pull request and determines if the request would increase - or decrease the overall code test coverage. - -Please check back shortly after submitting a pull request to verify -that the Travis-CI process passes. +When you open a pull request, GitHub Actions automatically runs the +full test suite across all supported Python versions. The repository is +configured to block merges until all checks pass — you don't need to +trigger anything manually. +If a check fails, GitHub will report the failure directly on the pull +request. Review the output, push a fix, and the checks will re-run +automatically. -What Happens If The Build Breaks -================================ - -Pull requests which break the build will be looked at closely and you -may be asked to fix the tests. - -The bitmath project **welcomes all contributors** so **it's OK** if -you're unable to fix the tests yourself. Just leave a comment in the -pull request explaining so if that is the case. - -Likewise, if Coveralls indicates the pull request would decrease the -overall test-coverage, and you aren't able to fix it yourself, just -leave a comment in the pull request. +The bitmath project **welcomes all contributors**. If you're unable to +fix a failing check yourself, leave a comment on the pull request +explaining the situation and we'll help. .. _contributing_automated_tests: @@ -120,136 +97,135 @@ Automated Tests *************** Write `unittests `_ -for any new functionality, `if you are up to the task`. This is not a -requirement, but it does get you a lot of karma. +for any new functionality if you are up to the task. It is not a hard +requirement, but it greatly helps. + +All bitmath code includes unit tests to verify expected functionality. -All bitmath code includes unit tests to verify expected -functionality. In the rest of this section we'll learn how the unit -tests are put together and how to interact with them. + +.. _contributing_components: Components ========== -bitmath unit tests are integrated with/depend on the following items: +The bitmath test suite depends on the following tools: + +* `GitHub Actions `_ — + Runs the full test suite automatically on every pull request across + all supported Python versions. + +* `unittest `_ — + Python's standard unit testing framework. All bitmath tests are + written using this framework. + +* `pytest `_ — Test runner used + to execute the unittest-based test suite, collect results, and report + coverage. -* `GitHub Actions `_ - - GitHub Actions provideFree online `continuous integration` - functionality for projects. Tests are ran automatically on every git - commit. In the past we used Travis for this functionality. +* `pytest-cov `_ — Coverage plugin + for pytest. The project aims for high coverage; reasonable exceptions + can always be discussed in the pull request. -* `unittest `_ - - Python unit testing framework. All bitmath tests are written using - this framework. +* `pycodestyle `_ — Checks + Python code style. -* `PyTest `_ - Per the **pytest** - website: "`The pytest framework makes it easy to write small, - readable tests, and can scale to support complex functional testing - for applications and libraries`". **pytest** is used to run our unit - tests. +* `pyflakes `_ — Checks Python + source files for errors. -* `pycodestyle `_ - A tool to check Python - code against some of the style conventions in :pep:`0008`. +* `virtualenv `_ — Creates an + isolated Python environment. The ``make ci`` target manages this + automatically. -* `pyflakes `_ - A simple - program which checks Python source files for errors. +* `Makefile `_ — Orchestrates + all build and test tasks. See :ref:`contributing_makefile_targets` + below. -* `virtualenv `_ - A tool to - create isolated Python environments. Allows us to install additional - package dependencies without requiring access to the system - site-packages directory. -* `Makefiles `_ - Utility scripts - used for project building and testing. How bitmath uses - **Makefiles** is described later in this section. +.. _contributing_makefile_targets: +Makefile Targets +================ -Targets -======= +All development tasks are driven through ``make``. The targets most +relevant to contributors are: -In the scope of this document, we use the term `target` in the context -of `makefile targets`. For the purpose of this documentation, we can -think of these `targets` as pre-defined commands coded in a -makefile. bitmath testing targets include: +.. note:: -* ``ci`` - Run all of the tests -* ``ci-pycodestyle`` - Run :pep:`0008` syntax checks -* ``ci-pyflakes`` - Run `pyflakes` error checks -* ``ci-unittests`` - Run the tests in a virtual env -* ``clean`` - Remove temporary files and build artifacts from the - checked-out repository. -* ``uniquetestnames`` - Ensures no unit tests have the same name. + These targets are how you test your changes locally and clean up + afterwards before opening a pull request. -To ensure the highest degree of confidence in test results you should -**always use** the ``ci`` target, or make a pull request on GitHub and -let it run the tests on all of the supported platforms. +``make ci`` + The primary target. Creates a Python virtualenv, installs all + dependencies from ``requirements.txt``, runs the unique test name + check, executes the full pytest suite with coverage, and runs + ``pycodestyle`` and ``pyflakes``. Run this before opening a pull + request. **This is the same check GitHub Actions runs.** +``make clean`` + Removes the virtualenv, compiled ``*.pyc`` files, ``__pycache__`` + directories, and build artifacts. Run ``make clean; make ci`` for a + guaranteed fresh test run. + +``make docs`` + Builds the HTML documentation locally using Sphinx. Output is + written to ``docsite/build/html/``. Run ``make viewdocs`` to open + the result automatically in your default browser, or open + ``docsite/build/html/index.html`` directly. + + +.. _contributing_running_tests: Running the Tests ================= -The bitmath test suite is invoked via the Makefile. The following is -an example of how to run the ``ci`` test target manually: +The simplest way to run the full test suite locally is: .. code-block:: console - :linenos: - :emphasize-lines: 2 - [~/Projects/bitmath] 17:22:21 (master) $ make ci + +For a guaranteed clean run (recommended before opening a PR): + +.. code-block:: console + + $ make clean; make ci + +The output will look something like this (dependency installation +output omitted for brevity): + +.. code-block:: console + ############################################# # Running Unique TestCase checker ############################################# ./tests/test_unique_testcase_names.sh + ############################################# # Creating a virtualenv ############################################# - virtualenv bitmathenv - New python executable in bitmathenv/bin/python - Installing setuptools, pip...done. - . bitmathenv/bin/activate && pip install -r requirements.txt - Downloading/unpacking python-coveralls (from -r requirements.txt (line 1)) - Downloading python_coveralls-2.4.3-py2.py3-none-any.whl - Downloading/unpacking nose (from -r requirements.txt (line 2)) - - ... snip ... - - Convert a bitmath GiB into a Tb ... ok - Convert a bitmath PiB into a TiB ... ok - Convert a bitmath GiB into a Tib ... ok - Convert to kb ... ok - Convert a bitmath Bit into a MiB ... ok - bitmath type converted to the same unit is properly converted ... ok - float(bitmath) returns a float ... ok - int(bitmath) returns an int ... ok - long(bitmath) returns a long ... ok - - Name Stmts Miss Cover Missing - --------------------------------------- - bitmath 440 1 99% 1152 - ---------------------------------------------------------------------- - Ran 163 tests in 0.035s - - OK - : - -On line **2** we see how to call a makefile target. In this case it's -quite straightforward: ``make ci``. Other targets are called in the -same way. For example, to run the ``clean`` target, you run the -command ``make clean``. To run the Python 3.x test suite, you would -run the command ``make ci3``. - - -Troubleshooting -=============== - -If you find yourself unable to run the unit tests: - -#. `Search `_ for relevant error messages - -#. **Read** the error message closely. The solution could be hidden in - the error message output. The problem could be as simple as a - missing dependency - -#. If you are unable to figure out all the necessary dependencies to - run the tests, file an issue on that specific projects GitHub issue - tracker. Include the full error message. + ... (dependency installation) ... + + ############################################# + # Running Unit Tests + ############################################# + ============================= test session starts ============================== + tests/test_argparse_type.py::TestArgparseType::test_BitmathType_bad_spaces_in_value PASSED [ 0%] + tests/test_argparse_type.py::TestArgparseType::test_BitmathType_bad_wtfareyoudoing PASSED [ 0%] + tests/test_argparse_type.py::TestArgparseType::test_BitmathType_good_one_arg PASSED [ 1%] + + ... (285 tests total) ... + + ================================ tests coverage ================================ + Name Stmts Miss Cover Missing + ------------------------------------- + TOTAL 623 0 100% + + ======================== 285 passed, 1 warning in 0.23s ======================== + +A passing run shows **285 passed** and **100% coverage**. Any +deviation from this is a failure. + +The definitive pass/fail verdict comes from the GitHub Actions workflow +on your pull request, which runs the suite across all supported Python +versions. A clean local ``make ci`` is a strong signal, but the PR +checks are the final authority. From c46de85d46c1dd279b948851967bd0084fc8bb84 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 18:00:12 -0500 Subject: [PATCH 77/92] feat: implement __format__ for f-string and format() support Adds __format__ to Bitmath so instances work with Python's standard string formatting protocol (PEP 3101). An empty spec returns str(self); any numeric format spec (e.g. ':.2f', '>10.1f') is applied to self.value only, letting the caller control the surrounding string. size = bitmath.MiB(2.847598437) f'{size:.1f} {size.unit}' # -> '2.8 MiB' f'{size}' # -> '2.847598437 MiB' Note: uses self.value.__format__(fmt_spec) rather than the built-in format() to avoid the name collision with the module-level bitmath.format() context manager. Eight tests added to test_representation.py. Documentation added to instances.rst with credit to the original author. closes #76 --- bitmath/__init__.py | 19 +++++++++++ docsite/source/instances.rst | 64 ++++++++++++++++++++++++++++++++++++ tests/test_representation.py | 49 +++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) diff --git a/bitmath/__init__.py b/bitmath/__init__.py index 2d6990c..e6c5431 100644 --- a/bitmath/__init__.py +++ b/bitmath/__init__.py @@ -413,6 +413,25 @@ def __str__(self): return self.best_prefix().format(_get_format_string()) return self.format(_get_format_string()) + def __format__(self, fmt_spec): + """Support Python's string formatting protocol. + +When *fmt_spec* is empty, returns ``str(self)`` — the same as the +default string representation (e.g. ``"1.0 KiB"``). + +When *fmt_spec* is a standard numeric format spec (e.g. ``".2f"``, +``">10.1f"``), it is applied to ``self.value`` only, returning the +formatted number without a unit suffix. The caller controls the +surrounding string:: + + size = bitmath.MiB(2.847598437) + f'size: {size:.1f} {size.unit}' # -> 'size: 2.8 MiB' + f'size: {size}' # -> 'size: 2.847598437 MiB' + """ + if fmt_spec == '': + return str(self) + return self.value.__format__(fmt_spec) + def format(self, fmt): """Return a representation of this instance formatted with user supplied syntax""" diff --git a/docsite/source/instances.rst b/docsite/source/instances.rst index 1d4aeda..ce97f2c 100644 --- a/docsite/source/instances.rst +++ b/docsite/source/instances.rst @@ -390,6 +390,70 @@ of how an attribute may be referenced multiple times. **16** we see the value has been rounded to **6.0** +.. _instances_dunder_format: + +Python Format Protocol (f-strings and format()) +================================================ + +.. py:method:: BitMathInstance.__format__(fmt_spec) + + Support Python's standard string formatting protocol (:pep:`3101`), + enabling bitmath instances to be used directly in f-strings and + :py:func:`format` calls. + + When *fmt_spec* is **empty**, returns ``str(self)`` — the same as + the default string representation: + + .. code-block:: python + + >>> size = bitmath.MiB(2.847598437) + >>> f'{size}' + '2.847598437 MiB' + + When *fmt_spec* is a **numeric format spec**, it is applied to + ``self.value`` only, returning the formatted number without a unit + suffix. The caller controls the surrounding string: + + .. code-block:: python + + >>> size = bitmath.MiB(2.847598437) + >>> f'{size:.1f} {size.unit}' + '2.8 MiB' + + This makes it straightforward to build columnar output with + consistent alignment across mixed unit types: + + .. code-block:: python + + >>> disk_usage = [ + ... ("home", bitmath.GiB(127.3)), + ... ("tmp", bitmath.MiB(843.7)), + ... ("var", bitmath.GiB(2.1)), + ... ] + >>> for mount, size in disk_usage: + ... print(f"{mount:<8} {size:>10.2f} {size.unit}") + home 127.30 GiB + tmp 843.70 MiB + var 2.10 GiB + + Any standard Python numeric format spec works: ``:.2f``, + ``:.3e``, ``:.0f``, ``>10.2f``, and so on. + + .. note:: + + The format spec applies to ``self.value`` — the numeric quantity + in the instance's current prefix unit. To render a different unit, + convert first: ``size.to_GiB()``, then format. + + .. versionadded:: 2.0.0 + + .. rubric:: Credit + + The original concept and implementation for this feature was + contributed by `Jonathan Eunice `_ + in `pull request #76 `_. + + .. _instances_rounding: Rounding and Integer Conversion diff --git a/tests/test_representation.py b/tests/test_representation.py index f845ba1..d4ea7b1 100644 --- a/tests/test_representation.py +++ b/tests/test_representation.py @@ -147,3 +147,52 @@ def test_print_byte_singular(self): one_Byte = bitmath.Byte(1.0) actual_result = one_Byte.format(fmt_str) self.assertEqual(expected_result, actual_result) + + ################################################################## + # Test __format__ (PEP 3101 / format() / f-string support) + # Original concept from PR #76 by Jonathan Eunice + def test_dunder_format_empty_spec_returns_str(self): + """__format__ with no spec returns str(instance)""" + size = bitmath.MiB(2.847598437) + self.assertEqual(format(size, ''), str(size)) + + def test_dunder_format_fstring_empty_spec(self): + """f'{size}' with no spec returns the default string representation""" + size = bitmath.KiB(1) + self.assertEqual(f'{size}', '1.0 KiB') + + def test_dunder_format_precision(self): + """__format__ with a precision spec formats self.value only""" + size = bitmath.MiB(2.847598437) + self.assertEqual(format(size, '.1f'), '2.8') + + def test_dunder_format_fstring_with_unit(self): + """f-string with precision spec and explicit unit gives full representation""" + size = bitmath.MiB(2.847598437) + self.assertEqual(f'{size:.1f} {size.unit}', '2.8 MiB') + + def test_dunder_format_width_and_precision(self): + """__format__ respects width and alignment specs""" + size = bitmath.GiB(127.3) + self.assertEqual(format(size, '>10.2f'), ' 127.30') + + def test_dunder_format_columnar_table(self): + """__format__ produces correct columnar output across mixed units""" + rows = [ + (bitmath.GiB(127.3), 'GiB'), + (bitmath.MiB(843.7), 'MiB'), + ] + results = [f'{size:>10.2f} {unit}' for size, unit in rows] + self.assertEqual(results[0], ' 127.30 GiB') + self.assertEqual(results[1], ' 843.70 MiB') + + def test_dunder_format_zero_precision(self): + """__format__ with .0f formats as integer-looking value""" + size = bitmath.KiB(1.9) + self.assertEqual(format(size, '.0f'), '2') + + def test_dunder_format_scientific(self): + """__format__ with 'e' spec formats in scientific notation""" + size = bitmath.GiB(1) + result = format(size, '.2e') + self.assertEqual(result, '1.00e+00') From 560cca70760a47f20308c91320fab9fe24a99302 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 18:04:45 -0500 Subject: [PATCH 78/92] docs: add Choosing a Formatting Approach guide to Getting Started --- docsite/source/simple_examples.rst | 148 +++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/docsite/source/simple_examples.rst b/docsite/source/simple_examples.rst index 8ae55b4..14e8d15 100644 --- a/docsite/source/simple_examples.rst +++ b/docsite/source/simple_examples.rst @@ -328,3 +328,151 @@ an instance of the same type: :ref:`Appendix: Rules for Math ` — discussion of floating-point representation and when rounding is appropriate. + + +Choosing a Formatting Approach +****************************** + +bitmath offers several ways to control how instances are rendered as +strings. They overlap deliberately — each suits a different situation. +This section helps you pick the right one. + +**Quick reference** + +.. list-table:: + :header-rows: 1 + :widths: 30 35 35 + + * - Approach + - Best for + - Avoid when + * - Default ``str()`` + - Printing, debugging, logging + - You need custom precision or layout + * - ``instance.format()`` + - Full control over layout using any instance attribute + - You only need to format the number + * - f-strings / ``format()`` + - Inline formatting in modern Python; columnar output + - You need unit-aware attributes beyond ``value`` + * - ``bitmath.format()`` context manager + - Consistent formatting across a block of code; threaded code + - A one-off format on a single value + * - ``bitmath.format_string`` global + - Changing the default for an entire script or session + - Anything other than a top-level script (mutates global state) + +Default ``str()`` +================= + +The simplest option. Just print or convert to string — no imports, no +setup. Output follows the module-level ``format_string`` (default: +``"{value} {unit}"``): + +.. code-block:: python + + >>> import bitmath + >>> print(bitmath.MiB(1.5)) + 1.5 MiB + >>> str(bitmath.GiB(10)) + '10.0 GiB' + +**Use this when** you just need a readable value and don't care about +precision or alignment. + +``instance.format()`` +===================== + +The most expressive option. The format string has access to every +instance attribute — ``{value}``, ``{unit}``, ``{bits}``, ``{bytes}``, +``{system}``, ``{base}``, ``{power}``, and more: + +.. code-block:: python + + >>> size = bitmath.MiB(1 / 3.0) + >>> size.format("{value:.2f} {unit} ({bits:.0f} bits)") + '0.33 MiB (2796202 bits)' + +**Use this when** you need the unit label, bit/byte counts, or any +other instance attribute woven into the output string. + +.. seealso:: + + :ref:`Instance Formatting ` — full attribute reference. + +f-strings and ``format()`` +=========================== + +Standard Python formatting. The format spec applies to ``self.value`` +only; the unit is omitted unless you add it explicitly with +``{size.unit}``: + +.. code-block:: python + + >>> size = bitmath.GiB(127.3) + >>> f'{size:.2f} {size.unit}' + '127.30 GiB' + >>> f'{size}' # no spec → same as str(size) + '127.3 GiB' + +This shines for columnar output where alignment matters: + +.. code-block:: python + + >>> rows = [("home", bitmath.GiB(127.3)), ("tmp", bitmath.MiB(843.7))] + >>> for mount, size in rows: + ... print(f"{mount:<8} {size:>10.2f} {size.unit}") + home 127.30 GiB + tmp 843.70 MiB + +**Use this when** you're building formatted strings inline and only +need the numeric value with a precision or alignment spec. + +``bitmath.format()`` context manager +===================================== + +Sets ``fmt_str``, ``plural``, and ``bestprefix`` for every bitmath +``str()`` call within the block, then restores the previous state +automatically — even if an exception is raised. Safe to use in +threaded code: + +.. code-block:: python + + >>> sizes = [bitmath.KiB(1024), bitmath.MiB(512)] + >>> with bitmath.format(fmt_str="{value:.1f} {unit}", bestprefix=True): + ... for s in sizes: + ... print(s) + 1.0 MiB + 512.0 MiB + +**Use this when** you want a consistent format across multiple +``print()`` or ``str()`` calls without touching each one individually, +or when you're in a threaded environment. + +.. seealso:: + + :ref:`bitmath.format() ` — full parameter reference. + +``bitmath.format_string`` global +================================= + +Sets the default representation for *all* bitmath instances for the +remainder of the process. Useful at the top of a script; a poor choice +inside a library or threaded code: + +.. code-block:: python + + >>> import bitmath + >>> bitmath.format_string = "{value:.2f} {unit}" + >>> print(bitmath.MiB(1.5)) + 1.50 MiB + +**Use this when** you control the entire script and want a single +format everywhere without wrapping everything in a context manager. +Prefer the context manager for anything more targeted. + +.. warning:: + + Mutating ``bitmath.format_string`` directly affects all threads. + Use the :py:func:`bitmath.format` context manager instead in + concurrent code. From 86f172daeffc91c533ad4358bd6c2cb3b09076fc Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 20:49:24 -0500 Subject: [PATCH 79/92] docs: update feature list and test count in README and index for 2.0.0 Add ZiB/YiB/Zib/Yib, non-strict parse_string, bitmath.sum(), built-in sum() support, f-string/__format__, and rounding to the feature list. Fix 'OS X' -> 'macOS' and 'almost 200' -> 'nearly 300' unit tests. --- README.rst | 12 ++++++++---- docsite/source/index.rst.in | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 5878054..5f16497 100644 --- a/README.rst +++ b/README.rst @@ -43,13 +43,17 @@ focusing on file size unit conversion, functionality now includes: * Converting between **SI** and **NIST** prefix units (``kB`` to ``GiB``) * Converting between units of the same type (SI to SI, or NIST to NIST) +* Full NIST unit coverage including **ZiB**, **YiB**, **Zib**, and **Yib** * Automatic human-readable prefix selection (like in `hurry.filesize `_) * Basic arithmetic operations (subtracting 42KiB from 50GiB) * Rich comparison operations (``1024 Bytes == 1KiB``) -* bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) -* Reading a device's storage capacity (Linux/OS X support only) -* String parsing +* Bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) +* Rounding via :py:func:`math.floor`, :py:func:`math.ceil`, and :py:func:`round` +* Reading a device's storage capacity (Linux/macOS support only) +* String parsing, including flexible non-strict parsing of ambiguous input (``parse_string(s, strict=False)``) * Sorting +* Summing iterables via built-in :py:func:`sum` or :py:func:`bitmath.sum` for unit-normalised results +* f-string and :py:func:`format` support via the standard Python formatting protocol * `argparse `_ integration as a custom type @@ -70,7 +74,7 @@ most of the time what you're really seeing are the base-2 sizes/rates. **Don't Forget!** The source for bitmath `is available on GitHub `_. -And did we mention there's almost 200 unittests? `Check them out for +And did we mention there are nearly 300 unit tests? `Check them out for yourself `_. Running the tests should be as simple as calling the ``ci`` target in diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index 332e0e3..c181147 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -37,13 +37,17 @@ focusing on file size unit conversion, functionality now includes: * Converting between **SI** and **NIST** prefix units (``kB`` to ``GiB``) * Converting between units of the same type (SI to SI, or NIST to NIST) +* Full NIST unit coverage including **ZiB**, **YiB**, **Zib**, and **Yib** * Automatic human-readable prefix selection (like in `hurry.filesize `_) * Basic arithmetic operations (subtracting 42KiB from 50GiB) * Rich comparison operations (``1024 Bytes == 1KiB``) -* bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) -* Reading a device's storage capacity (Linux/OS X support only) -* String parsing +* Bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) +* Rounding via :py:func:`math.floor`, :py:func:`math.ceil`, and :py:func:`round` +* Reading a device's storage capacity (Linux/macOS support only) +* String parsing, including flexible non-strict parsing of ambiguous input (``parse_string(s, strict=False)``) * Sorting +* Summing iterables via built-in :py:func:`sum` or :py:func:`bitmath.sum` for unit-normalised results +* f-string and :py:func:`format` support via the standard Python formatting protocol * `argparse `_ integration as a custom type @@ -63,7 +67,7 @@ most of the time what you're really seeing are the base-2 sizes/rates. **Don't Forget!** The source for bitmath `is available on GitHub `_. -And did we mention there's almost 200 unittests? `Check them out for +And did we mention there are nearly 300 unit tests? `Check them out for yourself `_. * :ref:`Examples ` after the TOC. From bad68a79a3beda04dea32f7a4110c4ca8e7c3e2e Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 21:13:26 -0500 Subject: [PATCH 80/92] docs: remove parenthetical from string parsing bullet --- README.rst | 2 +- docsite/source/index.rst.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 5f16497..2e1573b 100644 --- a/README.rst +++ b/README.rst @@ -50,7 +50,7 @@ focusing on file size unit conversion, functionality now includes: * Bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) * Rounding via :py:func:`math.floor`, :py:func:`math.ceil`, and :py:func:`round` * Reading a device's storage capacity (Linux/macOS support only) -* String parsing, including flexible non-strict parsing of ambiguous input (``parse_string(s, strict=False)``) +* String parsing, including flexible non-strict parsing of ambiguous input * Sorting * Summing iterables via built-in :py:func:`sum` or :py:func:`bitmath.sum` for unit-normalised results * f-string and :py:func:`format` support via the standard Python formatting protocol diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index c181147..37ab94b 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -44,7 +44,7 @@ focusing on file size unit conversion, functionality now includes: * Bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) * Rounding via :py:func:`math.floor`, :py:func:`math.ceil`, and :py:func:`round` * Reading a device's storage capacity (Linux/macOS support only) -* String parsing, including flexible non-strict parsing of ambiguous input (``parse_string(s, strict=False)``) +* String parsing, including flexible non-strict parsing of ambiguous input * Sorting * Summing iterables via built-in :py:func:`sum` or :py:func:`bitmath.sum` for unit-normalised results * f-string and :py:func:`format` support via the standard Python formatting protocol From 21c682145912a8ae08b58813dc2f56056dd80165 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 21:51:54 -0500 Subject: [PATCH 81/92] docs: fix GitHub URL, update copyright year and email address --- docsite/source/contact.rst | 6 +----- docsite/source/copyright.rst | 2 +- docsite/source/index.rst.in | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/docsite/source/contact.rst b/docsite/source/contact.rst index ce6e5af..b68e2b3 100644 --- a/docsite/source/contact.rst +++ b/docsite/source/contact.rst @@ -29,8 +29,4 @@ ahead: * :ref:`Contributing ` **E-Mail** - If you want to contact me directly, `clone the project - `_ and look at any of `my - bitmath commits - `_ - to find my email address. + You can reach me directly at `bitmath@lnx.cx `_. diff --git a/docsite/source/copyright.rst b/docsite/source/copyright.rst index bff7190..c428388 100644 --- a/docsite/source/copyright.rst +++ b/docsite/source/copyright.rst @@ -2,7 +2,7 @@ Copyright ######### The MIT License (MIT) -Copyright © 2014-2016 Tim Case +Copyright © 2014-2026 Tim Case Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index 37ab94b..f3000d8 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -65,7 +65,7 @@ bytes. When you see file sizes or transfer rates in your web browser, most of the time what you're really seeing are the base-2 sizes/rates. **Don't Forget!** The source for bitmath `is available on GitHub -`_. +`_. And did we mention there are nearly 300 unit tests? `Check them out for yourself `_. From 7631d39d7d0cab3e20af07e5f06b2a3942542122 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 21:53:11 -0500 Subject: [PATCH 82/92] Fix names and such --- docsite/source/conf.py | 2 +- docsite/source/conf.py.in | 2 +- docsite/source/index.rst | 28 ++++++++++++++++------------ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/docsite/source/conf.py b/docsite/source/conf.py index e51e97e..29683c7 100644 --- a/docsite/source/conf.py +++ b/docsite/source/conf.py @@ -48,7 +48,7 @@ # General information about the project. project = u'bitmath' -copyright = u'2014-2023, Tim Case' +copyright = u'2014-2026, Tim Case' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docsite/source/conf.py.in b/docsite/source/conf.py.in index 18c11c8..0d50a27 100644 --- a/docsite/source/conf.py.in +++ b/docsite/source/conf.py.in @@ -48,7 +48,7 @@ master_doc = 'index' # General information about the project. project = u'bitmath' -copyright = u'2014-2023, Tim Case' +copyright = u'2014-2026, Tim Case' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docsite/source/index.rst b/docsite/source/index.rst index 3ff2f06..effb705 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -1,20 +1,20 @@ -.. image:: https://github.com/timlnx/bitmath/actions/workflows/python.yml/badge.svg - :target: https://github.com/timlnx/bitmath/actions/workflows/python.yml +.. image:: https://github.com/tbielawa/bitmath/actions/workflows/python.yml/badge.svg + :target: https://github.com/tbielawa/bitmath/actions/workflows/python.yml :alt: Build Status on GitHub -.. image:: https://img.shields.io/github/issues/timlnx/bitmath?style=flat-square - :target: https://github.com/timlnx/bitmath/issues +.. image:: https://img.shields.io/github/issues/tbielawa/bitmath?style=flat-square + :target: https://github.com/tbielawa/bitmath/issues :alt: Open Issues -.. image:: https://img.shields.io/github/issues-pr/timlnx/bitmath?style=flat-square - :target: https://github.com/timlnx/bitmath/pulls +.. image:: https://img.shields.io/github/issues-pr/tbielawa/bitmath?style=flat-square + :target: https://github.com/tbielawa/bitmath/pulls :alt: Open Pull Requests .. image:: https://img.shields.io/pypi/dm/bitmath?style=flat-square :target: https://pypistats.org/packages/bitmath :alt: PyPI - Package Popularity -.. image:: https://img.shields.io/github/stars/timlnx/bitmath?style=flat-square +.. image:: https://img.shields.io/github/stars/tbielawa/bitmath?style=flat-square :target: https://pypistats.org/packages/bitmath :alt: GitHub Project Popularity @@ -37,13 +37,17 @@ focusing on file size unit conversion, functionality now includes: * Converting between **SI** and **NIST** prefix units (``kB`` to ``GiB``) * Converting between units of the same type (SI to SI, or NIST to NIST) +* Full NIST unit coverage including **ZiB**, **YiB**, **Zib**, and **Yib** * Automatic human-readable prefix selection (like in `hurry.filesize `_) * Basic arithmetic operations (subtracting 42KiB from 50GiB) * Rich comparison operations (``1024 Bytes == 1KiB``) -* bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) -* Reading a device's storage capacity (Linux/OS X support only) -* String parsing +* Bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) +* Rounding via :py:func:`math.floor`, :py:func:`math.ceil`, and :py:func:`round` +* Reading a device's storage capacity (Linux/macOS support only) +* String parsing, including flexible non-strict parsing of ambiguous input * Sorting +* Summing iterables via built-in :py:func:`sum` or :py:func:`bitmath.sum` for unit-normalised results +* f-string and :py:func:`format` support via the standard Python formatting protocol * `argparse `_ integration as a custom type @@ -63,8 +67,8 @@ most of the time what you're really seeing are the base-2 sizes/rates. **Don't Forget!** The source for bitmath `is available on GitHub `_. -And did we mention there's almost 200 unittests? `Check them out for -yourself `_. +And did we mention there are nearly 300 unit tests? `Check them out for +yourself `_. * :ref:`Examples ` after the TOC. From a030fb496b1875e5950b0e9bf15198c33bcfa6ba Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 22:10:24 -0500 Subject: [PATCH 83/92] Remove bitmath.integrations package; add Integration Examples docs chapter The integrations subpackage (bmargparse, bmclick, bmprogressbar) required users to install optional dependencies that were not declared in the package metadata, and the bundled implementations became stale. Per issue #85, the approach of shipping copy-paste examples in documentation is simpler and easier to maintain. Changes: - Remove bitmath/integrations/ package and tests/test_argparse_type.py - Remove full_demo.py (broken legacy demo script) - Add docsite/source/integration_examples.rst with standalone argparse, click, and progressbar2 examples - Replace 3rd Party Module Integrations section in module.rst with a pointer to the new chapter - Update index.rst / index.rst.in toctree to include the new chapter - Update real_life_examples.rst progress bar section with chapter pointer - Update README.rst argparse example to use inline BitmathType function - Drop bitmath.integrations from pyproject.toml packages list --- README.rst | 38 ++--- docsite/source/contributing.rst | 14 +- docsite/source/index.rst | 38 ++--- docsite/source/index.rst.in | 1 + docsite/source/integration_examples.rst | 216 ++++++++++++++++++++++++ docsite/source/module.rst | 90 +--------- docsite/source/real_life_examples.rst | 10 +- pyproject.toml | 2 +- 8 files changed, 268 insertions(+), 141 deletions(-) create mode 100644 docsite/source/integration_examples.rst diff --git a/README.rst b/README.rst index 2e1573b..d0d1e4b 100644 --- a/README.rst +++ b/README.rst @@ -424,27 +424,25 @@ Formatting ``argparse`` Integration ------------------------ -Example script using ``bitmath.integrations.bmargparse.BitmathType`` as an -argparser argument type: +A self-contained example showing how to use bitmath as an argparse +argument type is available in the `Integration Examples +`_ +chapter of the documentation. .. code-block:: python import argparse - from bitmath.integrations.bmargparse import BitmathType - parser = argparse.ArgumentParser( - description="Arg parser with a bitmath type argument") - parser.add_argument('--block-size', - type=BitmathType, - required=True) - - results = parser.parse_args() - print("Parsed in: {PARSED}; Which looks like {TOKIB} as a Kibibit".format( - PARSED=results.block_size, - TOKIB=results.block_size.Kib)) - -If ran as a script the results would be similar to this: - -.. code-block:: bash - - $ python ./bmargparse.py --block-size 100MiB - Parsed in: 100.0 MiB; Which looks like 819200.0 Kib as a Kibibit + import bitmath + + def BitmathType(value): + try: + return bitmath.parse_string(value) + except ValueError: + raise argparse.ArgumentTypeError( + f"{value!r} is not a recognised bitmath unit string" + ) + + parser = argparse.ArgumentParser() + parser.add_argument('--block-size', type=BitmathType, required=True) + args = parser.parse_args(['--block-size', '10MiB']) + print(args.block_size) # 10.0 MiB diff --git a/docsite/source/contributing.rst b/docsite/source/contributing.rst index 4afe0c9..178033f 100644 --- a/docsite/source/contributing.rst +++ b/docsite/source/contributing.rst @@ -209,21 +209,19 @@ output omitted for brevity): # Running Unit Tests ############################################# ============================= test session starts ============================== - tests/test_argparse_type.py::TestArgparseType::test_BitmathType_bad_spaces_in_value PASSED [ 0%] - tests/test_argparse_type.py::TestArgparseType::test_BitmathType_bad_wtfareyoudoing PASSED [ 0%] - tests/test_argparse_type.py::TestArgparseType::test_BitmathType_good_one_arg PASSED [ 1%] - - ... (285 tests total) ... + tests/test_arithmetic.py::TestArithmetic::test_add_bitmath_to_bitmath PASSED [ 0%] + tests/test_arithmetic.py::TestArithmetic::test_sub_bitmath_from_bitmath PASSED [ 0%] + ... (hundreds more) ... ================================ tests coverage ================================ Name Stmts Miss Cover Missing ------------------------------------- TOTAL 623 0 100% - ======================== 285 passed, 1 warning in 0.23s ======================== + ======================== NNN passed in Xs ======================== -A passing run shows **285 passed** and **100% coverage**. Any -deviation from this is a failure. +A passing run shows 100% coverage. The exact test count grows as new +tests are added. Any regression in coverage is a failure. The definitive pass/fail verdict comes from the GitHub Actions workflow on your pull request, which runs the suite across all supported Python diff --git a/docsite/source/index.rst b/docsite/source/index.rst index effb705..628efed 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -117,6 +117,7 @@ Contents instances.rst simple_examples.rst real_life_examples.rst + integration_examples.rst contributing.rst appendices.rst NEWS.rst @@ -359,27 +360,24 @@ Formatting ``argparse`` Integration ------------------------ -Example script using ``bitmath.integrations.bmargparse.BitmathType`` as an -argparser argument type: +A self-contained example showing how to use bitmath as an argparse +argument type is available in the :ref:`integration_examples_argparse` +section of the *Integration Examples* chapter. .. code-block:: python import argparse - from bitmath.integrations.bmargparse import BitmathType - parser = argparse.ArgumentParser( - description="Arg parser with a bitmath type argument") - parser.add_argument('--block-size', - type=BitmathType, - required=True) - - results = parser.parse_args() - print("Parsed in: {PARSED}; Which looks like {TOKIB} as a Kibibit".format( - PARSED=results.block_size, - TOKIB=results.block_size.Kib)) - -If ran as a script the results would be similar to this: - -.. code-block:: bash - - $ python ./bmargparse.py --block-size 100MiB - Parsed in: 100.0 MiB; Which looks like 819200.0 Kib as a Kibibit + import bitmath + + def BitmathType(value): + try: + return bitmath.parse_string(value) + except ValueError: + raise argparse.ArgumentTypeError( + f"{value!r} is not a recognised bitmath unit string" + ) + + parser = argparse.ArgumentParser() + parser.add_argument('--block-size', type=BitmathType, required=True) + args = parser.parse_args(['--block-size', '10MiB']) + print(args.block_size) # 10.0 MiB diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index f3000d8..6d9970d 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -117,6 +117,7 @@ Contents instances.rst simple_examples.rst real_life_examples.rst + integration_examples.rst contributing.rst appendices.rst NEWS.rst diff --git a/docsite/source/integration_examples.rst b/docsite/source/integration_examples.rst new file mode 100644 index 0000000..6dcaa0a --- /dev/null +++ b/docsite/source/integration_examples.rst @@ -0,0 +1,216 @@ +.. _integration_examples: + +Integration Examples +#################### + +The following are self-contained, copy-paste examples showing how to use +:mod:`bitmath` with popular third-party libraries. These libraries are +**not** installed by bitmath — install them separately before use. + +.. contents:: + :local: + :depth: 1 + + +.. _integration_examples_argparse: + +argparse +******** + +The :mod:`argparse` module (part of the Python standard library) accepts +command-line arguments as strings by default. The ``type`` parameter of +:py:meth:`~argparse.ArgumentParser.add_argument` lets you supply a +callable that converts a raw string into whatever type your application +needs. + +The snippet below defines a ``BitmathType`` callable and registers it as +the type for a ``--block-size`` option so that users can write values +like ``--block-size 10MiB`` and receive a :class:`bitmath.MiB` object +directly. + +.. code-block:: python + + import argparse + import bitmath + + + def BitmathType(value): + """Convert a command-line string such as '10MiB' into a bitmath object.""" + try: + return bitmath.parse_string(value) + except ValueError: + raise argparse.ArgumentTypeError( + f"{value!r} is not a recognised bitmath unit string " + "(examples: 10MiB, 1.5GiB, 500kB)" + ) + + + def main(): + parser = argparse.ArgumentParser( + description="Example script using a bitmath argument type" + ) + parser.add_argument( + "--block-size", + type=BitmathType, + required=True, + help="Block size with unit, e.g. 10MiB", + ) + args = parser.parse_args() + print(f"Block size: {args.block_size}") + print(f"In KiB: {args.block_size.to_KiB():.2f}") + + + if __name__ == "__main__": + main() + +Example run: + +.. code-block:: bash + + $ python script.py --block-size 10MiB + Block size: 10.0 MiB + In KiB: 10240.00 KiB + + $ python script.py --block-size bad + error: argument --block-size: 'bad' is not a recognised bitmath unit string (examples: 10MiB, 1.5GiB, 500kB) + + +.. _integration_examples_click: + +click +***** + +`click `_ is a popular command-line +interface toolkit. Custom parameter types are implemented by subclassing +:class:`click.ParamType` and overriding :py:meth:`~click.ParamType.convert`. + +Install click before use: + +.. code-block:: bash + + pip install click + +.. code-block:: python + + import click + import bitmath + + + class BitmathParamType(click.ParamType): + """A click parameter type that accepts bitmath unit strings.""" + + name = "SIZE" + + def convert(self, value, param, ctx): + if isinstance(value, bitmath.Bitmath): + return value + try: + return bitmath.parse_string(value) + except ValueError: + self.fail( + f"{value!r} is not a recognised bitmath unit string " + "(examples: 10MiB, 1.5GiB, 500kB)", + param, + ctx, + ) + + + BITMATH = BitmathParamType() + + + @click.command() + @click.option( + "--block-size", + type=BITMATH, + required=True, + help="Block size with unit, e.g. 10MiB", + ) + def main(block_size): + """Example command using a bitmath click parameter type.""" + click.echo(f"Block size: {block_size}") + click.echo(f"In KiB: {block_size.to_KiB():.2f}") + + + if __name__ == "__main__": + main() + +Example run: + +.. code-block:: bash + + $ python script.py --block-size 10MiB + Block size: 10.0 MiB + In KiB: 10240.00 KiB + + $ python script.py --block-size bad + Error: Invalid value for '--block-size': 'bad' is not a recognised bitmath unit string (examples: 10MiB, 1.5GiB, 500kB) + + +.. _integration_examples_progressbar2: + +progressbar2 +************ + +`progressbar2 `_ is a flexible +terminal progress-bar library. The example below defines a custom widget +that displays a data-transfer speed (bytes per second) in a +human-readable bitmath unit, and demonstrates it with a simulated file +download. + +Install progressbar2 before use: + +.. code-block:: bash + + pip install progressbar2 + +.. code-block:: python + + import time + import progressbar + import bitmath + + + class DataTransferSpeed(progressbar.widgets.FormatWidgetMixin, + progressbar.widgets.TimeSensitiveMixin): + """Display transfer speed as a human-readable bitmath value per second.""" + + def __call__(self, progress, data, **kwargs): + elapsed = data.get("seconds_elapsed") or 0 + if elapsed <= 0 or data.get("value") is None: + return "?? B/s" + bytes_done = data["value"] + speed = bitmath.Byte(bytes_done / elapsed).best_prefix() + return f"{speed:.2f}/s" + + + def simulate_download(total_bytes): + widgets = [ + "Downloading: ", + progressbar.Bar(), + " ", + progressbar.Percentage(), + " ", + DataTransferSpeed(), + " ", + progressbar.ETA(), + ] + with progressbar.ProgressBar( + max_value=total_bytes, widgets=widgets + ) as bar: + received = 0 + chunk = total_bytes // 50 + while received < total_bytes: + time.sleep(0.05) + received = min(received + chunk, total_bytes) + bar.update(received) + + + if __name__ == "__main__": + # Simulate a 100 MiB download + simulate_download(int(bitmath.MiB(100).to_Byte())) + +Example run: + +.. code-block:: text + + Downloading: |####################| 100% 18.32 MiB/s ETA: 0:00:00 diff --git a/docsite/source/module.rst b/docsite/source/module.rst index 0225892..d4062fd 100644 --- a/docsite/source/module.rst +++ b/docsite/source/module.rst @@ -920,94 +920,12 @@ behavior. 'Kib', 'KiB', 'Mib', 'MiB', 'Gib', 'GiB', 'Tib', 'TiB', 'Pib', 'PiB', 'Eib', 'EiB', 'Zib', 'ZiB', 'Yib', 'YiB'] -.. py:module:: bitmath.integrations - .. _bitmath_3rd_party_module_integrations: 3rd Party Module Integrations ***************************** -This section describes the various ways in which :py:mod:`bitmath` can -be integrated with other 3rd pary modules. - -To see a full demo of the :mod:`argparse` and :mod:`progressbar` -integrations, as well as a comprehensive demonstrations of the full -capabilities of the bitmath library, see :ref:`Creating Download -Progress Bars ` in the -*Real Life Examples* section. - -.. _bitmath_BitmathType: - -argparse -======== - -.. versionadded:: 1.2.0 - -The `argparse module -`_ (part of stdlib) -is used to parse command line arguments. By default, parsed options -and arguments are turned into strings. However, one useful feature -:py:mod:`argparse` provides is the ability to `specify what datatype -`_ any given -argument or option should be interpreted as. - -.. function:: BitmathType(bmstring) - - The :func:`BitmathType` factory creates objects that can be passed - to the type argument of `ArgumentParser.add_argument() - `_. Arguments - that have :func:`BitmathType` objects as their type will - automatically parse the command line argument into a matching - :ref:`bitmath object `. - - :param str bmstring: The command-line option to parse into a - bitmath object - :returns: A bitmath object representing ``bmstring`` - :raises ValueError: on any input that - :py:func:`bitmath.parse_string` already rejects - :raises ValueError: on **unquoted inputs** with whitespace - separating the value from the unit (e.g., - ``--some-option 10 MiB`` is bad, but - ``--some-option '10 MiB'`` is good) - - Let's take a look at a more in-depth example. - - A feature found in many command-line utilities is the ability to - specify some kind of file size using a string which roughly - describes some kind of parameter. For example, let's look at the - :program:`du` (disk usage) command. Invoking it as ``du -B`` - allows one to specify a desired block-size scaling factor in - printed results. - - Let's say we wanted to implement a similar mechanism in an - application of our own. Except, instead of abbreviating down to - ambiguous capital letters, we accept scaling factors as - :ref:`properly written values ` with associated - units. Such as **10 MiB**, or **1 MB**. - - To accomplish this, we'll use :py:mod:`argparse` to create an - argument parser and add one option to it, ``--block-size``. This - option will have a type of :func:`BitmathType` set. - - .. code-block:: python - :linenos: - :emphasize-lines: 3,6,7 - - >>> import argparse, bitmath - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--block-size', type=bitmath.BitmathType) - >>> args = "--block-size 1MiB" - >>> results = parser.parse_args(args.split()) - >>> print(type(results.block_size)) - - - On line **3** we add the ``--block-size`` option to the parser, - explicitly defining it's type as :func:`BitmathType`. In lines - **6** and **7** when we parse the provided arguments we find that - :py:mod:`argparse` has automatically created a bitmath object for - us. - - If an invalid scaling factor is provided by the user, such as one - which does not represent a recognizable unit, the bitmath library - will automatically detect this for us and signal to the argument - parser that an error has occurred. +Self-contained, copy-paste examples for integrating :mod:`bitmath` with +:mod:`argparse`, `click `_, and +`progressbar2 `_ are collected in +the :ref:`Integration Examples ` chapter. diff --git a/docsite/source/real_life_examples.rst b/docsite/source/real_life_examples.rst index f42260a..b380e46 100644 --- a/docsite/source/real_life_examples.rst +++ b/docsite/source/real_life_examples.rst @@ -384,12 +384,10 @@ connections. Creating Download Progress Bars ******************************* - -.. literalinclude:: ../../full_demo.py - -* View the the source for the `demo suite - `_ - on GitHub +For a self-contained, copy-paste example of a progress bar that +displays transfer speed using bitmath, see +:ref:`integration_examples_progressbar2` in the +*Integration Examples* chapter. .. _real_life_examples_read_device_storage_capacity: diff --git a/pyproject.toml b/pyproject.toml index 0809175..80b020c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ Homepage = "https://bitmath.readthedocs.io/en/latest/index.html" bitmath = "bitmath:cli_script" [tool.hatch.build.targets.wheel] -packages = ["bitmath", "bitmath.integrations"] +packages = ["bitmath"] [tool.hatch.publish.index] disable = true From 44eed6395805a087940a8421069a16fda6ec65c5 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 22:24:23 -0500 Subject: [PATCH 84/92] Doc updates --- bitmath/integrations/__init__.py | 36 ------ bitmath/integrations/bmargparse.py | 79 ------------ docsite/source/real_life_examples.rst | 14 +-- full_demo.py | 173 -------------------------- tests/test_argparse_type.py | 89 ------------- 5 files changed, 6 insertions(+), 385 deletions(-) delete mode 100644 bitmath/integrations/__init__.py delete mode 100644 bitmath/integrations/bmargparse.py delete mode 100755 full_demo.py delete mode 100644 tests/test_argparse_type.py diff --git a/bitmath/integrations/__init__.py b/bitmath/integrations/__init__.py deleted file mode 100644 index 6b7b09b..0000000 --- a/bitmath/integrations/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# SPDX-License-Identifier: MIT -# The MIT License (MIT) -# -# Copyright © 2014-2016 Tim Case -# See GitHub Contributors Graph for more information -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sub-license, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# Kept for backward compatibilty -from .bmargparse import BitmathType - -try: - from .bmprogressbar import BitmathFileTransferSpeed -except ImportError: - # Ignore missing dependency as argparse integration will fail if - # progressbar is not installed (#86). - pass diff --git a/bitmath/integrations/bmargparse.py b/bitmath/integrations/bmargparse.py deleted file mode 100644 index 37efcf8..0000000 --- a/bitmath/integrations/bmargparse.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -# SPDX-License-Identifier: MIT -# The MIT License (MIT) -# -# Copyright © 2014-2016 Tim Case -# See GitHub Contributors Graph for more information -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sub-license, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import bitmath -import argparse - - -def BitmathType(bmstring): - """An 'argument type' for integrations with the argparse module. - -For more information, see -https://docs.python.org/3/library/argparse.html#type Of particular -interest to us is this bit: - - ``type=`` can take any callable that takes a single string - argument and returns the converted value - -I.e., ``type`` can be a function (such as this function) or a class -which implements the ``__call__`` method. - -Example usage of the bitmath.BitmathType argparser type: - - >>> import bitmath - >>> import argparse - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument("--file-size", type=bitmath.BitmathType) - >>> parser.parse_args("--file-size 1337MiB".split()) - Namespace(file_size=MiB(1337.0)) - -Invalid usage includes any input that the bitmath.parse_string -function already rejects. Additionally, **UNQUOTED** arguments with -spaces in them are rejected (shlex.split used in the following -examples to conserve single quotes in the parse_args call): - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument("--file-size", type=bitmath.BitmathType) - >>> import shlex - - >>> # The following is ACCEPTABLE USAGE: - ... - >>> parser.parse_args(shlex.split("--file-size '1337 MiB'")) - Namespace(file_size=MiB(1337.0)) - - >>> # The following is INCORRECT USAGE because the string "1337 MiB" is not quoted! - ... - >>> parser.parse_args(shlex.split("--file-size 1337 MiB")) - error: argument --file-size: 1337 can not be parsed into a valid bitmath object -""" - try: - argvalue = bitmath.parse_string(bmstring) - except ValueError: - raise argparse.ArgumentTypeError("'%s' can not be parsed into a valid bitmath object" % - bmstring) - else: - return argvalue diff --git a/docsite/source/real_life_examples.rst b/docsite/source/real_life_examples.rst index b380e46..4966859 100644 --- a/docsite/source/real_life_examples.rst +++ b/docsite/source/real_life_examples.rst @@ -23,7 +23,7 @@ as such: :linenos: >>> import bitmath - >>> downstream = bitmath.Mib(50) + >>> downstream = bitmath.Mb(50) >>> print(downstream.to_MB()) MB(6.25) @@ -111,7 +111,7 @@ returns). We can use ``bitmath`` to do that too: Alternatively, we could simplify things and use -:ref:`bitmath.getsize() ` to read the file size +:func:`bitmath.getsize` to read the file size directly into a bitmath object: .. code-block:: python @@ -249,7 +249,7 @@ using the :py:mod:`bitmath` library. Let's see how: >>> from bitmath import GB - >>> tx = 1/8.0 + >>> tx = 1/8 >>> rtt = 0.199 * 10**-3 @@ -259,14 +259,12 @@ using the :py:mod:`bitmath` library. Let's see how: KiB(24.2919921875) -.. note:: - To avoid integer rounding during division, don't forget to divide by ``8.0`` rather than ``8`` We could shorten that even further: .. code-block:: python - >>> print((GB((1/8.0) * (0.199 * 10**-3))).to_Byte()) + >>> print((GB((1/8) * (0.199 * 10**-3))).to_Byte()) 24875.0Byte **Get the current kernel parameters** @@ -363,8 +361,8 @@ Set the **core-network** buffer sizes: .. code-block:: console $ sudo sysctl net.core.rmem_max=24875 net.core.wmem_max=24875 - net.core.rmem_max = 4235 - net.core.wmem_max = 4235 + net.core.rmem_max = 24875 + net.core.wmem_max = 24875 Set the **per-socket** buffer sizes: diff --git a/full_demo.py b/full_demo.py deleted file mode 100755 index a066da9..0000000 --- a/full_demo.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env python -# SPDX-License-Identifier: MIT -from __future__ import print_function -import logging -import time -import bitmath -from bitmath.integrations.bmargparse import BitmathType -from bitmath.integrations.bmprogressbar import BitmathFileTransferSpeed -import argparse -import requests -import progressbar -import os -import tempfile -import atexit -import random -from functools import reduce - -# Files of various sizes to use in the demo. -# -# Moar here: https://www.kernel.org/pub/linux/kernel/v3.0/?C=S;O=D -REMOTES = [ - # patch-3.0.70.gz 20-Mar-2013 20:02 1.0M - 'https://www.kernel.org/pub/linux/kernel/v3.0/patch-3.4.92.xz', - - # patch-3.16.gz 03-Aug-2014 22:39 8.0M - 'https://www.kernel.org/pub/linux/kernel/v3.0/patch-3.16.gz', - - # patch-3.2.gz 05-Jan-2012 00:43 22M - 'https://www.kernel.org/pub/linux/kernel/v3.0/patch-3.2.gz', -] - -###################################################################### -p = argparse.ArgumentParser(description='bitmath demo suite') -p.add_argument('-d', '--down', help="Download Rate", - type=BitmathType, - default=bitmath.MiB(4)) - -p.add_argument('-s', '--slowdown', - help='Randomly pause to slow down the transfer rate', - action='store_true', default=False) - -args = p.parse_args() - -###################################################################### -# Save our example files somewhere. And then clean up every trace that -# anything every happened there. shhhhhhhhhhhhhhhh -DESTDIR = tempfile.mkdtemp('demosuite', 'bitmath') -@atexit.register -def cleanup(): - for f in os.listdir(DESTDIR): - os.remove(os.path.join(DESTDIR, f)) - os.rmdir(DESTDIR) - -###################################################################### -for f in REMOTES: - print(""" -######################################################################""") - fname = os.path.basename(f) - # An array of widgets to design our progress bar. Note how we use - # BitmathFileTransferSpeed - widgets = ['Bitmath Demo Suite (%s): ' % fname, - progressbar.Percentage(), ' ', - progressbar.Bar(marker=progressbar.RotatingMarker()), ' ', - progressbar.ETA(), ' ', - BitmathFileTransferSpeed()] - - # The 'stream' keyword lets us http GET files in - # chunks. http://docs.python-requests.org/en/latest/user/quickstart/#raw-response-content - r = requests.get(f, stream=True) - # We haven't began receiving the payload content yet, we have only - # just received the response headers. Of interest is the - # 'content-length' header which describes our payload in bytes - # - # http://bitmath.readthedocs.org/en/latest/classes.html#bitmath.Byte - size = bitmath.Byte(int(r.headers['Content-Length'])) - - # Demonstrate 'with' context handler, allowing us to customize all - # bitmath string printing within the indented block. We don't need - # all that precision anyway, just two points should do. - # - # http://bitmath.readthedocs.org/en/latest/module.html#bitmath-format - with bitmath.format("{value:.2f} {unit}"): - print("Downloading %s (%s) in %s chunks" % (f, - size.best_prefix(), - args.down.best_prefix())) - - # We have to save these files somewhere - save_path = os.path.join(DESTDIR, fname) - print("Saving to: %s" % save_path) - print("") - - # OK. Let's create our actual progress bar now. See the 'maxval' - # keyword? That's the size of our payload in bytes. - pbar = progressbar.ProgressBar( - widgets=widgets, - maxval=int(size)).start() - - ###################################################################### - # Open a new file for binary writing and write 'args.down' size - # chunks into it until we've received the entire payload - with open(save_path, 'wb') as fd: - # The 'iter_content' method accepts integer values of - # bytes. Lucky for us, 'args.down' is a bitmath instance and - # has a 'bytes' attribute we can feed into the method call. - for chunk in r.iter_content(int(args.down.bytes)): - fd.write(chunk) - # The progressbar will end the entire cosmos as we know it - # if we try to .update() it beyond it's MAXVAL - # parameter. - # - # That's something I'd like to avoid taking the - # responsibility for. - if (pbar.currval + args.down.bytes) < pbar.maxval: - pbar.update(pbar.currval + int(args.down.bytes)) - - # We can add an pause to artificially speed up/slowdown - # the transfer rate. Allows us to see different units. - if args.slowdown: - # randomly slow down 1/5 of the time - if random.randrange(0, 100) % 5 == 0: - time.sleep(random.randrange(0, 500) * 0.01) - - # Nothing to see here. Go home. - pbar.finish() - -###################################################################### -print(""" -###################################################################### -List downloaded contents -* Filter for .xz files only -""") - -for p,bm in bitmath.listdir(DESTDIR, - filter='*.xz'): - print(p, bm) - -###################################################################### -print(""" -###################################################################### -List downloaded contents -* Filter for .gz files only -* Print using best human readable prefix -""") - -for p,bm in bitmath.listdir(DESTDIR, - filter='*.gz', - bestprefix=True): - print(p, bm) - -###################################################################### -print(""" -###################################################################### -List downloaded contents -* No filter set, to display all files -* Limit precision of printed file size to 3 digits -* Print using best human readable prefix -""") - -for p,bm in bitmath.listdir(DESTDIR, - bestprefix=True): - with bitmath.format("{value:.3f} {unit}"): - print(p, bm) - -###################################################################### -print(""" -###################################################################### -Sum the size of all downloaded files together -* Print with best prefix and 3 digits of precision -""") - -discovered_files = [f[1] for f in bitmath.listdir(DESTDIR)] -total_size = reduce(lambda x,y: x+y, discovered_files).best_prefix().format("{value:.3f} {unit}") -print("Total size of %s downloaded items: %s" % (len(discovered_files), total_size)) diff --git a/tests/test_argparse_type.py b/tests/test_argparse_type.py deleted file mode 100644 index 0fecb93..0000000 --- a/tests/test_argparse_type.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -# SPDX-License-Identifier: MIT -# The MIT License (MIT) -# -# Copyright © 2014 Tim Case -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - -""" -Test the argparse 'BitmathType' integration -""" - -from . import TestCase -import bitmath -from bitmath.integrations.bmargparse import BitmathType -import argparse -import shlex - - -class TestArgparseType(TestCase): - def setUp(self): - """Needful for the tests""" - # A simple one-argument parser that only accept one value. - self.parser_one_arg = argparse.ArgumentParser() - self.parser_one_arg.add_argument("--one-arg", type=BitmathType) - - # This parser take one argument, '--two-args'. It requires two values. - self.parser_two_args = argparse.ArgumentParser() - self.parser_two_args.add_argument("--two-args", type=BitmathType, - nargs=2) - - def _parse_one_arg(self, arg_str): - return self.parser_one_arg.parse_args(shlex.split(arg_str)) - - def _parse_two_args(self, arg_str): - return self.parser_two_args.parse_args(shlex.split(arg_str)) - - def test_BitmathType_good_one_arg(self): - """Argparse: BitmathType - Works when given a correct parameter""" - args = "--one-arg 1000EB" - result = self._parse_one_arg(args) - self.assertEqual(bitmath.EB(1000), result.one_arg) - - def test_BitmathType_good_two_args(self): - """Argparse: BitmathType - Works when given two correct parameters""" - args = "--two-args 1337B 0.001GiB" - result = self._parse_two_args(args) - self.assertEqual(len(result.two_args), 2) - self.assertIn(bitmath.Byte(1337), result.two_args) - self.assertIn(bitmath.GiB(0.001), result.two_args) - - def test_BitmathType_bad_wtfareyoudoing(self): - """Argparse: BitmathType - Notices when horrendously incorrect args are provided""" - args = "--one-arg 2098329324kdsjflksdjf" - with self.assertRaises(SystemExit): - self._parse_one_arg(args) - - def test_BitmathType_good_spaces_in_value(self): - """Argparse: BitmathType - 'Quoted values' can be separated from the units by whitespace""" - args = "--two-args '100 MiB' '200 KiB'" - result = self._parse_two_args(args) - self.assertEqual(len(result.two_args), 2) - self.assertIn(bitmath.MiB(100), result.two_args) - self.assertIn(bitmath.KiB(200), result.two_args) - - def test_BitmathType_bad_spaces_in_value(self): - """Argparse: BitmathType - Unquoted values separated from their units are detected""" - args = "--one-arg 1337 B" - with self.assertRaises(SystemExit): - self._parse_one_arg(args) From 1082e921368b8fae508e2c874a6a6a85d2cfb905 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 22:39:49 -0500 Subject: [PATCH 85/92] Modernize packaging: pyproject.toml sdist config, Makefile build targets, install docs - Replace MANIFEST.in with hatchling sdist include/exclude in pyproject.toml - Add build/pypi/pypitest Makefile targets using python -m build + twine - Update sdist/install Makefile targets to drop setup.py references - Fix 'Source' install instructions in README and docsite to use pip install . - Remove NOTE: UPDATE THIS placeholder from index.rst / index.rst.in --- MANIFEST.in | 4 ---- Makefile | 42 ++++++++++++++++++++++--------------- README.rst | 8 ++++--- docsite/source/index.rst | 17 ++++++++------- docsite/source/index.rst.in | 17 ++++++++------- pyproject.toml | 23 ++++++++++++++++++++ 6 files changed, 73 insertions(+), 38 deletions(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 3e99086..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include README.rst LICENSE bitmath.1 NEWS.rst -graft bitmath -graft tests -recursive-include docsite/source *.rst diff --git a/Makefile b/Makefile index 2c85c56..b6647b4 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,12 @@ # Makefile for bitmath # # useful targets: -# make sdist ---------------- produce a tarball +# make build ---------------- build sdist + wheel (output in dist/) +# make pypitest ------------- upload to TestPyPI (requires: pip install twine) +# make pypi ----------------- upload to PyPI (requires: pip install twine) # make rpm ----------------- produce RPMs -# make docs ----------------- rebuild the manpages (results are checked in) -# make pyflakes, make pycodestyle -- source code checks -# make test ----------------- run all unit tests (export LOG=true for /tmp/ logging) +# make docs ----------------- rebuild the docs +# make ci ----------------- run full test suite in virtualenv ######################################################## @@ -101,16 +102,23 @@ conf.py: docsite/source/conf.py.in python-bitmath.spec: python-bitmath.spec.in sed "s/%VERSION%/$(VERSION)/" $< > $@ -# Build the distutils setup file on the fly. -setup.py: setup.py.in VERSION python-bitmath.spec.in - sed -e "s/%VERSION%/$(VERSION)/" -e "s/%RELEASE%/$(RPMRELEASE)/" $< > $@ +build: clean + @echo "#############################################" + @echo "# Building sdist + wheel" + @echo "#############################################" + python -m build -# Upload sources to pypi/pypi-test -pypi: - python ./setup.py sdist upload +pypi: build + @echo "#############################################" + @echo "# Uploading to PyPI" + @echo "#############################################" + twine upload dist/* -pypitest: - python ./setup.py sdist upload -r test +pypitest: build + @echo "#############################################" + @echo "# Uploading to TestPyPI" + @echo "#############################################" + twine upload --repository testpypi dist/* # usage example: make tag TAG=1.1.0-1 tag: @@ -127,18 +135,18 @@ uniquetestnames: @echo "#############################################" ./tests/test_unique_testcase_names.sh -install: clean - python ./setup.py install +install: + pip install . mkdir -p /usr/share/man/man1/ gzip -9 -c bitmath.1 > /usr/share/man/man1/bitmath.1.gz -sdist: setup.py clean +sdist: clean @echo "#############################################" @echo "# Creating SDIST" @echo "#############################################" - python setup.py sdist + python -m build --sdist -rpmcommon: sdist python-bitmath.spec setup.py +rpmcommon: sdist python-bitmath.spec @echo "#############################################" @echo "# Building (S)RPM Now" @echo "#############################################" diff --git a/README.rst b/README.rst index d0d1e4b..4d27a2c 100644 --- a/README.rst +++ b/README.rst @@ -120,13 +120,15 @@ You could also install bitmath from `PyPi **Source**: -Or, if you want to install from source: +To install from source, clone the repository and use pip: .. code-block:: bash - $ sudo python ./setup.py install + $ git clone https://github.com/timlnx/bitmath.git + $ cd bitmath + $ pip install . -If you want the bitmath manpage installed as well: +To also install the ``bitmath`` manpage: .. code-block:: bash diff --git a/docsite/source/index.rst b/docsite/source/index.rst index 628efed..438ad89 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -90,18 +90,21 @@ developed, tested, and supported for `currently supported $ pip install --user bitmath -**Source**:: +**Source** - NOTE: UPDATE THIS FOR THE NEW HATCH.TOML THING - -Or, if you want to install from source: +To install from source, clone the repository and use pip: .. code-block:: bash - $ sudo python ./setup.py install + $ git clone https://github.com/timlnx/bitmath.git + $ cd bitmath + $ pip install . + +To also install the ``bitmath`` manpage: + +.. code-block:: bash -If you want the ``/bin/bitmath`` manpage too, then ``sudo make -install`` will install the Python library and the manpage. + $ sudo make install Contents diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index 6d9970d..7f1b7d1 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -90,18 +90,21 @@ developed, tested, and supported for `currently supported $ pip install --user bitmath -**Source**:: +**Source** - NOTE: UPDATE THIS FOR THE NEW HATCH.TOML THING - -Or, if you want to install from source: +To install from source, clone the repository and use pip: .. code-block:: bash - $ sudo python ./setup.py install + $ git clone https://github.com/timlnx/bitmath.git + $ cd bitmath + $ pip install . + +To also install the ``bitmath`` manpage: + +.. code-block:: bash -If you want the ``/bin/bitmath`` manpage too, then ``sudo make -install`` will install the Python library and the manpage. + $ sudo make install Contents diff --git a/pyproject.toml b/pyproject.toml index 80b020c..1125ef7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,29 @@ Homepage = "https://bitmath.readthedocs.io/en/latest/index.html" [project.scripts] bitmath = "bitmath:cli_script" +[tool.hatch.build.targets.sdist] +include = [ + "bitmath/", + "tests/", + "docsite/source/", + "README.rst", + "LICENSE", + "NEWS.rst", + "bitmath.1", + ".coveragerc", + "requirements.txt", +] +exclude = [ + # local-only dirs hatchling sees but git does not track + "bmintegrations/", + "bmintegrations311/", + ".claude/", + "docs/", + ".remember/", + "*.claude", + "CLAUDE.md", +] + [tool.hatch.build.targets.wheel] packages = ["bitmath"] From f01c87bf9dc36d61437680f18363f28b98ac80f7 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 22:49:48 -0500 Subject: [PATCH 86/92] Fix README.rst: replace Sphinx :py:func: roles with plain literals for PyPI rendering --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 4d27a2c..a15e7be 100644 --- a/README.rst +++ b/README.rst @@ -48,12 +48,12 @@ focusing on file size unit conversion, functionality now includes: * Basic arithmetic operations (subtracting 42KiB from 50GiB) * Rich comparison operations (``1024 Bytes == 1KiB``) * Bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) -* Rounding via :py:func:`math.floor`, :py:func:`math.ceil`, and :py:func:`round` +* Rounding via ``math.floor``, ``math.ceil``, and ``round`` * Reading a device's storage capacity (Linux/macOS support only) * String parsing, including flexible non-strict parsing of ambiguous input * Sorting -* Summing iterables via built-in :py:func:`sum` or :py:func:`bitmath.sum` for unit-normalised results -* f-string and :py:func:`format` support via the standard Python formatting protocol +* Summing iterables via built-in ``sum`` or ``bitmath.sum`` for unit-normalised results +* f-string and ``format`` support via the standard Python formatting protocol * `argparse `_ integration as a custom type From 21550d7b438393a8949e803eb4ebd29de08f5e54 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 22:52:46 -0500 Subject: [PATCH 87/92] Remove obsolete pip >= 1.1 note (issue #57 is 10 years old) --- README.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.rst b/README.rst index a15e7be..a5776b2 100644 --- a/README.rst +++ b/README.rst @@ -108,14 +108,6 @@ You could also install bitmath from `PyPi $ sudo pip install bitmath -.. note:: - - **pip** installs need pip >= 1.1. To workaround this, `download - bitmath `_, from - PyPi and then ``pip install bitmath-x.y.z.tar.gz``. See `issue #57 - `_ - for more information. - **Source**: From 2390bd29c91c3da5fcd460b577f1ca4743fc5f85 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 23:05:05 -0500 Subject: [PATCH 88/92] docs --- docsite/source/index.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docsite/source/index.rst b/docsite/source/index.rst index 438ad89..6d04939 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -364,8 +364,9 @@ Formatting ------------------------ A self-contained example showing how to use bitmath as an argparse -argument type is available in the :ref:`integration_examples_argparse` -section of the *Integration Examples* chapter. +argument type is available in the `Integration Examples +`_ +chapter of the documentation. .. code-block:: python From e809f149e6d2629138da04b6f0de5d43d166c64b Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 23:08:26 -0500 Subject: [PATCH 89/92] Fix coverage warning: remove redundant [run] include (conflicts with --source set by --cov) --- .coveragerc | 1 - 1 file changed, 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 27b36c4..de2c8e3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,5 @@ # -*- mode: conf -*- [run] -include = bitmath/__init__.py,bitmath/integrations.py [report] exclude_lines = # These should be impossible to trigger. From 0f2664da6edc680897db39c5c0a2c08e70eeabcd Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 23:25:17 -0500 Subject: [PATCH 90/92] Replace 2.0.0 NEWS.rst stub with full release notes --- NEWS.rst | 166 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 129 insertions(+), 37 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index d2dcf35..afca61a 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -10,43 +10,135 @@ NEWS bitmath-2.0.0 ************* -bitmath-2.0.0 **will be** the first new release since 1.3.3 was -released in 2018! - -Beginning with the release of 2.0.0 bitmath will only officially -support supported python versions. It is considered a happy bonus if -bitmath works with an unsupported Python version. - -The focus of the first "bitmath 2" releases will be -simplification. Some things will go again, they may come back a little -later. - - -What to Expect -============== - -In the semantic versioning world, a major version number increase is -meant to express fundamental changes to the software. Changes which -will **almost certainly** result in breakages for at least some of the -user base. For bitmath this will be true as well, and in this case -that user base is specifically anyone still using the library on -Python 2.x. - -* The Bitmath API - The fundamental API will remain unchanged. What - will change are Python language features used and dropping of - workarounds for older Python versions -* Bitmath :ref:`Integrations - ` - Integrations will be - removed from the primary source code for now. Many of these can be - provided as code examples instead which will simplify packaging and - testing requirements for the project. They'll be in the docs or just - in git, not sure yet. -* Packaging - It looks like a lot has changed in the last 2.5 years in - the Python packaging world, and I have a lot to catch up on. I guess - we use TOML instead of setup.py now, that's neat. -* Distribution - I daily drive Fedora Linux and Mac OS X, I don't have - time to keep up with other platforms. If someone wants to bring back - debian packaging, contact me and we'll work something out. +*Released: April 2026* + +Nearly eight years after 1.3.3 shipped in 2018, bitmath is back with +a major release. Version 2.0.0 is a thorough modernization: the +Python 2 era is officially over, the library picks up several +long-requested features, and the entire project infrastructure has +been rebuilt from scratch. If you've been running bitmath on Python +3.11 or later and quietly wishing it felt more modern — this release +is for you. + + +Breaking Changes +================ + +**Python support** + Python 3.11+ only. Python 2 and Python 3.7–3.10 are no longer + supported or tested. + +**parse_string() default system** + The default unit system when ``strict=False`` is now **NIST** + (binary). Previously it defaulted to SI. Code that relied on the + old default for ambiguous strings such as ``"1g"`` will get a + different result. See :ref:`bitmath_parse_string` for full details. + +**parse_string_unsafe() deprecated** + Use :func:`bitmath.parse_string` with ``strict=False`` instead. + The old name still works but emits a :exc:`DeprecationWarning`. + +**bitmath.integrations removed** + The argparse, click, and progressbar integrations have been removed + from the package. Copy-paste replacements are provided in the new + :ref:`Integration Examples ` documentation + chapter. No changes to calling code are required — just a local + copy of the relevant snippet. + +**Build and install** + ``setup.py`` and ``setup.py.in`` are gone. Installation is + ``pip install bitmath``. Source builds use ``python -m build``. + + +Library Improvements +==================== + +The core API is unchanged — every bitmath object you created before +still works exactly the same way. + +**Full NIST unit coverage** + The four largest NIST prefix units — :class:`~bitmath.ZiB`, + :class:`~bitmath.YiB`, :class:`~bitmath.Zib`, and + :class:`~bitmath.Yib` — are now first-class bitmath types. All + constants (``NIST_PREFIXES``, ``NIST_STEPS``, ``ALL_UNIT_TYPES``) + reflect reality. + +**f-string and format() support** + bitmath objects now implement the Python format protocol + (``__format__``, `PEP 3101 `_). They can be used directly in f-strings + with format specs — ``f"{some_size:.2f}"`` just works. See + :ref:`instances_dunder_format` for the full reference. Credit to + `Jonathan Eunice `_ for the + original concept in `PR #76 + `_. + +**bitmath.sum() and built-in sum()** + A new :func:`bitmath.sum` function returns a unit-normalised result + when summing mixed-type iterables. For uniform collections, the + built-in :py:func:`sum` now works directly on bitmath sequences + without a ``start=`` argument. + +**Thread-safe context manager** + The :func:`bitmath.format` context manager previously mutated + module-level globals, making it unsafe under concurrent access. It + now uses ``threading.local`` with proper save/restore semantics, + including correct nesting behavior. Closes `issue #83 + `_. + +**best_prefix() bit-family fix** + :func:`bitmath.best_prefix` incorrectly converted bit-family units + (e.g. ``Kib``) into byte-family units (e.g. ``KiB``). The unit + family is now preserved. Closes `issue #95 + `_. + +**Flexible string parsing** + :func:`bitmath.parse_string` with ``strict=False`` accepts + ambiguous input such as ``"1g"`` or ``"1GB"`` and resolves it to + the most likely unit. When the system cannot be reliably determined, + NIST is the tiebreaker. Closes `issue #54 + `_. + + +Project Infrastructure +====================== + +**Packaging** + ``pyproject.toml`` with a hatchling backend replaces the old + ``setup.py``/``setup.py.in`` template system. The package is PEP + 517/518 compliant. ``MANIFEST.in`` is gone; sdist content is + declared explicitly in ``pyproject.toml``. + +**GitHub Actions** + CI now runs against Python 3.11, 3.12, and 3.13 on both Ubuntu and + macOS, with actions pinned to current versions (``checkout@v4``, + ``setup-python@v5``). Tests run on every pull request, not just + pushes. + +**Security scanning** + CodeQL analysis runs on every push to master, every pull request, + and on a weekly schedule. + +**ReadTheDocs** + ``.readthedocs.yaml`` is now present and explicit. The RTD build + uses Python 3.11 and installs ``sphinx_rtd_theme`` directly. + +**Development workflow** + ``make ci`` is the single command for a full local build: unique + test name check, pycodestyle, flake8, and pytest with coverage. + ``make build``, ``make pypitest``, and ``make pypi`` replace the + old ``make sdist upload`` pattern. + + +Looking Forward +=============== + +bitmath started as a small utility for thinking clearly about file +sizes, and that's still exactly what it is. The 2.0.0 release doesn't +change what the library does — it changes what it's built on, so it +can keep doing it for the next eight years. The test suite sits at 288 +tests and 100% coverage. If you've been holding off on adopting +bitmath because the last release predated your Python version — now's +the time. .. _bitmath-1.4.0-1: From 4889c74f0870e52d62b3d76c57a6dbb069858335 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 23:37:40 -0500 Subject: [PATCH 91/92] NEWS.rst: sync prose and links with 2.0.0-release.md hand edits --- NEWS.rst | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index afca61a..08ea5df 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -54,14 +54,14 @@ Library Improvements ==================== The core API is unchanged — every bitmath object you created before -still works exactly the same way. +still works exactly the same way. What 2.0.0 adds on top of that: **Full NIST unit coverage** The four largest NIST prefix units — :class:`~bitmath.ZiB`, :class:`~bitmath.YiB`, :class:`~bitmath.Zib`, and :class:`~bitmath.Yib` — are now first-class bitmath types. All - constants (``NIST_PREFIXES``, ``NIST_STEPS``, ``ALL_UNIT_TYPES``) - reflect reality. + constants (:ref:`NIST_PREFIXES, NIST_STEPS, ALL_UNIT_TYPES + `) reflect reality. **f-string and format() support** bitmath objects now implement the Python format protocol @@ -102,11 +102,16 @@ still works exactly the same way. Project Infrastructure ====================== +The project infrastructure has been rebuilt to reflect how Python +projects are actually maintained in 2026: + **Packaging** ``pyproject.toml`` with a hatchling backend replaces the old - ``setup.py``/``setup.py.in`` template system. The package is PEP - 517/518 compliant. ``MANIFEST.in`` is gone; sdist content is - declared explicitly in ``pyproject.toml``. + ``setup.py``/``setup.py.in`` template system. The package is + `PEP 517 `_/`PEP 518 + `_ compliant. ``MANIFEST.in`` + is gone; sdist content is declared explicitly in + ``pyproject.toml``. **GitHub Actions** CI now runs against Python 3.11, 3.12, and 3.13 on both Ubuntu and @@ -129,16 +134,18 @@ Project Infrastructure old ``make sdist upload`` pattern. -Looking Forward -=============== +Closing Thoughts +================ bitmath started as a small utility for thinking clearly about file sizes, and that's still exactly what it is. The 2.0.0 release doesn't change what the library does — it changes what it's built on, so it can keep doing it for the next eight years. The test suite sits at 288 -tests and 100% coverage. If you've been holding off on adopting -bitmath because the last release predated your Python version — now's -the time. +tests and 100% coverage. The documentation has been comprehensively +reviewed and updated. The packaging is clean enough to pass ``twine +check`` on the first attempt (well, the second). If you've been +holding off on adopting bitmath because the last release predated your +Python version — now's the time. .. _bitmath-1.4.0-1: From 1010cfeca3717eb55602bfee5397601f5f747264 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Fri, 17 Apr 2026 23:39:58 -0500 Subject: [PATCH 92/92] NEWS.rst: fix undefined label ref for parse_string --- NEWS.rst | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 08ea5df..843998b 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -32,7 +32,7 @@ Breaking Changes The default unit system when ``strict=False`` is now **NIST** (binary). Previously it defaulted to SI. Code that relied on the old default for ambiguous strings such as ``"1g"`` will get a - different result. See :ref:`bitmath_parse_string` for full details. + different result. See :ref:`parse-string-non-strict` for full details. **parse_string_unsafe() deprecated** Use :func:`bitmath.parse_string` with ``strict=False`` instead. @@ -137,15 +137,28 @@ projects are actually maintained in 2026: Closing Thoughts ================ -bitmath started as a small utility for thinking clearly about file -sizes, and that's still exactly what it is. The 2.0.0 release doesn't -change what the library does — it changes what it's built on, so it -can keep doing it for the next eight years. The test suite sits at 288 -tests and 100% coverage. The documentation has been comprehensively -reviewed and updated. The packaging is clean enough to pass ``twine -check`` on the first attempt (well, the second). If you've been -holding off on adopting bitmath because the last release predated your -Python version — now's the time. +bitmath started as a small passion project of mine. A utility for +thinking about and clearly expressing file sizes, and that's still +exactly what it is. This 2.0.0 release doesn't change what the library +does. What I've done is change the very foundation that it's built +on. The test suite sits at 288 tests and 100% coverage. The +documentation has been comprehensively reviewed and updated. The +packaging is clean enough to pass ``twine check`` on the first attempt +(well, the second). + +It really is a remarkable milestone in project history. I have to give +the warmest thanks to all of the users and fans who have written bug +reports and submitted pull requests. Especially in the least active +years of the project. Most of those PRs and Issues have been +integrated into this massive 2.0 release. + +**Thanks for your patience and your participation.** + +If you've been holding off on adopting bitmath because the last +release predated your Python version — yeah I totally get it. This +place was a dumpster for the last 8 years. + +Go on, give it a shot. It really is better than ever. .. _bitmath-1.4.0-1: