Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ jobs:
- dependencies: ruff
task: make -f Makefile fmt-travis
- dependencies: >
libatomic
python3-hypothesis
python3-justbases
python3-setuptools
ruff
task: make -f Makefile lint
task: PATH=${PATH}:/github/home/.local/bin make -f Makefile lint
- dependencies: >
python3-hypothesis
python3-justbases
Expand All @@ -45,7 +46,10 @@ jobs:
run: >
dnf install -y
make
pip
${{ matrix.dependencies }}
- name: Install pyright
run: pip install --user pyright
- name: ${{ matrix.task }}
run: ${{ matrix.task }}

Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.PHONY: lint
lint:
ruff check
pyright

.PHONY: test
test:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ target-version = "py312"
line-length = 88

[tool.ruff.lint]
select = ["PL"]
select = ["B", "F", "PL"]

[tool.ruff.lint.isort]
known-first-party = ["justbases"]
Expand Down
29 changes: 15 additions & 14 deletions src/justbytes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,32 +81,33 @@
# pylint: disable=wrong-import-position

# CONFIGURATION
from ._config import (
BaseConfig,
Config,
DigitsConfig,
DisplayConfig,
StringConfig,
StripConfig,
ValueConfig,
)
from ._config import BaseConfig as BaseConfig
from ._config import Config as Config
from ._config import DigitsConfig as DigitsConfig
from ._config import DisplayConfig as DisplayConfig
from ._config import StringConfig as StringConfig
from ._config import StripConfig as StripConfig
from ._config import ValueConfig as ValueConfig

# ROUNDING CONSTANTS
# UNIT CONSTANTS
from ._constants import ROUNDING_METHODS, UNITS, B
from ._constants import ROUNDING_METHODS as ROUNDING_METHODS
from ._constants import UNITS as UNITS
from ._constants import B as B
from ._constants import BinaryUnits as _BinaryUnits
from ._constants import DecimalUnits as _DecimalUnits
from ._constants import RoundingMethods as _RoundingMethods

# EXCEPTIONS
from ._errors import RangeError, RangeValueError
from ._errors import RangeError as RangeError
from ._errors import RangeValueError as RangeValueError

# SIZE
from ._size import Range
from ._sizes import AI
from ._size import Range as Range
from ._sizes import AI as AI

# VERSION
from .version import __version__
from .version import __version__ as __version__

KB = _DecimalUnits.KB
MB = _DecimalUnits.MB
Expand Down
14 changes: 8 additions & 6 deletions src/justbytes/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

# pylint: disable=invalid-name

from numbers import Rational

import justbases

from ._constants import PRECISE_NUMERIC_TYPES, UNITS, RoundingMethods
Expand Down Expand Up @@ -55,9 +57,9 @@ class DisplayConfig(justbases.DisplayConfig):
def __init__(
self,
show_approx_str=True,
base_config=BaseConfig(),
digits_config=DigitsConfig(),
strip_config=StripConfig(),
base_config=BaseConfig(), # noqa: B008 # FIXME
digits_config=DigitsConfig(), # noqa: B008 # FIXME
strip_config=StripConfig(), # noqa: B008 # FIXME
):
"""
Initializer.
Expand Down Expand Up @@ -106,8 +108,8 @@ class ValueConfig:

def __init__( # noqa: PLR0913,PLR0917
self,
max_places=2,
min_value=1,
max_places: int | None = 2,
min_value: int | Rational = 1,
binary_units=True,
exact_value=False,
unit=None,
Expand Down Expand Up @@ -196,7 +198,7 @@ class Config:

STRING_CONFIG = StringConfig(ValueConfig(), DisplayConfig(), justbases.String)

STRICT = False
STRICT: bool = False

@classmethod
def set_display_impl(cls, impl): # pragma: no cover
Expand Down
18 changes: 13 additions & 5 deletions src/justbytes/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
* Size units, e.g., Ki, Mi
"""

import abc
from numbers import Rational

import justbases
Expand Down Expand Up @@ -57,16 +56,25 @@ def __int__(self):
""" The universal unit, bytes. """


class Units(metaclass=abc.ABCMeta):
class Units:
"""
Generic class for units.
"""

FACTOR = abc.abstractproperty(doc="factor for each unit")
Subclasses must define FACTOR (int) and _UNITS (list).
"""

_UNITS = abc.abstractproperty(doc="ordered list of units")
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
required_attrs = ["FACTOR", "_UNITS"]
for attr in required_attrs:
if not hasattr(cls, attr):
raise TypeError(
f"Can't instantiate abstract class {cls.__name__} "
f"without class attribute {attr}"
)

_MAX_EXPONENT = None
_UNITS = []

@classmethod
def UNITS(cls):
Expand Down
8 changes: 7 additions & 1 deletion src/justbytes/_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

"""Special purpose generators."""

from typing import Callable, Iterable, TypeVar

T = TypeVar("T")


def takeuntil(pred, seq):
"""
Expand All @@ -31,7 +35,9 @@ def takeuntil(pred, seq):
break


def next_or_last(pred, seq, default=None):
def next_or_last(
pred: Callable[[T], bool], seq: Iterable[T], default: T | None = None
) -> T | None:
"""
Return the first element that matches the predicate or the last element in
the seq.
Expand Down
21 changes: 17 additions & 4 deletions src/justbytes/_size.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,20 @@
"""

from fractions import Fraction
from numbers import Rational
from typing import Generator

import justbases

from ._config import Config
from ._constants import PRECISE_NUMERIC_TYPES, UNIT_TYPES, B, BinaryUnits, DecimalUnits
from ._config import Config, ValueConfig
from ._constants import (
PRECISE_NUMERIC_TYPES,
UNIT_TYPES,
B,
BinaryUnits,
DecimalUnits,
Unit,
)
from ._errors import (
RangeFractionalResultError,
RangeNonsensicalBinOpError,
Expand All @@ -46,7 +55,9 @@ class Range:
_BYTES_SYMBOL = "B"

@classmethod
def _as_single_number(cls, value, config):
def _as_single_number(
cls, value: Rational, config: ValueConfig
) -> tuple[justbases.Radix, int]:
"""
Returns a rational value as a single number according to the
specified configuration.
Expand Down Expand Up @@ -387,7 +398,9 @@ def convertTo(self, spec=None):

return self._magnitude / factor

def componentsList(self, binary_units=True):
def componentsList(
self, binary_units: bool = True
) -> Generator[tuple[Fraction, Unit], None, None]:
"""
Yield a representation of this size for every unit,
decomposed into a Fraction value and a unit specifier
Expand Down
66 changes: 33 additions & 33 deletions tests/test_deterministic/test_size/test_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,42 +37,42 @@ def test_binary_operators_range(self):

# **
with self.assertRaises(RangeNonsensicalBinOpError):
size ** Range(2)
size ** Range(2) # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangePowerResultError):
size**2
size**2 # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
2 ** Range(0)
2 ** Range(0) # pyright: ignore[reportUnusedExpression]

def test_binary_operators_boolean(self):
"""Test binary operators with a boolean result."""

# <
self.assertTrue(Range(0, MiB) < Range(32))
with self.assertRaises(RangeNonsensicalBinOpError):
Range(0) < 1
Range(0) < 1 # noqa: B015 # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
1 < Range(32, TiB)
1 < Range(32, TiB) # noqa: B015 # pyright: ignore[reportUnusedExpression]

# <=
self.assertTrue(Range(0, MiB) <= Range(32))
with self.assertRaises(RangeNonsensicalBinOpError):
Range(0) <= 1
Range(0) <= 1 # noqa: B015 # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
1 <= Range(32, TiB)
1 <= Range(32, TiB) # noqa: B015 # pyright: ignore[reportUnusedExpression]

# >
self.assertTrue(Range(32, MiB) > Range(32))
with self.assertRaises(RangeNonsensicalBinOpError):
Range(32) > 1
Range(32) > 1 # noqa: B015 # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
1 > Range(0, TiB)
1 > Range(0, TiB) # noqa: B015 # pyright: ignore[reportUnusedExpression]

# >=
self.assertTrue(Range(32, MiB) >= Range(32))
with self.assertRaises(RangeNonsensicalBinOpError):
Range(32) >= 1
Range(32) >= 1 # noqa: B015 # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
1 >= Range(0, TiB)
1 >= Range(0, TiB) # noqa: B015 # pyright: ignore[reportUnusedExpression]

# !=
self.assertTrue(Range(32, MiB) != Range(32, GiB))
Expand Down Expand Up @@ -104,9 +104,9 @@ def test_exceptions(self):
"""Any non-size other raises an exception."""

with self.assertRaises(RangeNonsensicalBinOpError):
2 + Range(0)
2 + Range(0) # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
Range(0) + 2
Range(0) + 2 # pyright: ignore[reportUnusedExpression]


class DivmodTestCase(unittest.TestCase):
Expand Down Expand Up @@ -134,15 +134,15 @@ def test_exceptions(self):
"""Test that exceptions are thrown."""

with self.assertRaises(RangeNonsensicalBinOpError):
2048 // Range(12, B)
2048 // Range(12, B) # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
Range(12) // "str"
Range(12) // "str" # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpValueError):
Range(12) // Range(0)
Range(12) // Range(0) # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpValueError):
Range(12) // 0
Range(12) // 0 # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
Range(12) // Decimal("NaN")
Range(12) // Decimal("NaN") # pyright: ignore[reportUnusedExpression]


class ModTestCase(unittest.TestCase):
Expand All @@ -152,15 +152,15 @@ def test_exceptions(self):
"""Test that exceptions are thrown."""

with self.assertRaises(RangeNonsensicalBinOpError):
2048 % Range(12, B)
2048 % Range(12, B) # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
Range(12) % "str"
Range(12) % "str" # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpValueError):
Range(12) % Range(0)
Range(12) % Range(0) # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpValueError):
Range(12) % 0
Range(12) % 0 # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
Range(12) % Decimal("NaN")
Range(12) % Decimal("NaN") # pyright: ignore[reportUnusedExpression]


class MultiplicationTestCase(unittest.TestCase):
Expand All @@ -170,11 +170,11 @@ def test_exceptions(self):
"""Range others are unrepresentable."""

with self.assertRaises(RangePowerResultError):
Range(0) * Range(0)
Range(0) * Range(0) # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
Range(0) * Decimal("NaN")
Range(0) * Decimal("NaN") # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
Range(0) * "str"
Range(0) * "str" # pyright: ignore[reportUnusedExpression]


class RdivmodTestCase(unittest.TestCase):
Expand Down Expand Up @@ -254,9 +254,9 @@ def test_exceptions(self):
"""Any non-size other raises an exception."""

with self.assertRaises(RangeNonsensicalBinOpError):
2 - Range(0)
2 - Range(0) # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
Range(0) - 2
Range(0) - 2 # pyright: ignore[reportUnusedExpression]


class TruedivTestCase(unittest.TestCase):
Expand All @@ -266,12 +266,12 @@ def test_exceptions(self):
"""Test that exceptions are thrown."""

with self.assertRaises(RangeNonsensicalBinOpError):
2048 / Range(12, B)
2048 / Range(12, B) # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
Range(12) / "str"
Range(12) / "str" # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpValueError):
Range(12) / Range(0)
Range(12) / Range(0) # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpValueError):
Range(12) / 0
Range(12) / 0 # pyright: ignore[reportUnusedExpression]
with self.assertRaises(RangeNonsensicalBinOpError):
Range(12) / Decimal("NaN")
Range(12) / Decimal("NaN") # pyright: ignore[reportUnusedExpression]
Loading