Skip to content

Commit

Permalink
Merge pull request #17 from johnomotani/int-float-convert
Browse files Browse the repository at this point in the history
Implicitly convert any `Number` when `value_type=float`
  • Loading branch information
johnomotani authored Nov 18, 2022
2 parents 7461bfd + a1121d6 commit 12a4ad7
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 16 deletions.
3 changes: 1 addition & 2 deletions .flake8
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
[flake8]
max-line-length = 88
ignore = (
ignore =
E741, # 'ambiguous variable names' forbids using 'I', 'O' or 'l'
W503, # 'line break before binary operator', but this is allowed and useful inside brackets
E203, # 'whitespace before ':'', but black formats some slice expressions with space before ':'
E231, # missing whitespace after ',', but black formats some expressions without space after ','
)
exclude =
optionsfactory/_version.py
versioneer.py
6 changes: 6 additions & 0 deletions optionsfactory/_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
from numbers import Number


def _checked(value, *, meta=None, name=None):
if (meta is not None) and meta.value_type is float and isinstance(value, Number):
# Allow converting any numerical type to float
value = float(value)
if (
(meta is not None)
and (meta.value_type is not None)
Expand Down
4 changes: 2 additions & 2 deletions optionsfactory/optionsfactory.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ def doc(self):
return {key: value.doc for key, value in self.__defaults.items()}

def add(self, **kwargs):
"""Create a more specific version of the factory with extra options. For example,
may be useful for a subclass like
"""Create a more specific version of the factory with extra options. For
example, may be useful for a subclass like
class Parent:
options_factory = OptionsFactory(...)
Expand Down
46 changes: 38 additions & 8 deletions optionsfactory/tests/test_mutableoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,37 @@ def test_defaults(self):
with pytest.raises(KeyError):
opts.is_default("x")

opts["f"] = 3

assert opts.a == 3
assert opts.b == 3
assert opts.c == 3
assert opts.d == 6.0
assert opts.e == 3
assert opts.f == 3.0
assert opts.g == 11
assert opts.h == 5

assert opts["a"] == 3
assert opts["b"] == 3
assert opts["c"] == 3
assert opts["d"] == 6.0
assert opts["e"] == 3
assert opts["f"] == 3.0
assert opts["g"] == 11
assert opts["h"] == 5

assert not opts.is_default("a")
assert opts.is_default("b")
assert opts.is_default("c")
assert opts.is_default("d")
assert opts.is_default("e")
assert not opts.is_default("f")
assert opts.is_default("g")
assert opts.is_default("h")
with pytest.raises(KeyError):
opts.is_default("x")

assert "a" in opts
assert "b" in opts
assert "c" in opts
Expand All @@ -155,22 +186,23 @@ def test_defaults(self):
assert sorted([k for k in opts]) == sorted(
["a", "b", "c", "d", "e", "f", "g", "h"]
)
assert sorted(opts.values()) == sorted([3, 3, 3, 5.0, 3, 2.0, 11, 5])
assert sorted(opts.values()) == sorted([3, 3, 3, 6.0, 3, 3.0, 11, 5])
assert sorted(opts.items()) == sorted(
[
("a", 3),
("b", 3),
("c", 3),
("d", 5.0),
("d", 6.0),
("e", 3),
("f", 2.0),
("f", 3.0),
("g", 11),
("h", 5),
]
)

# Reset "a" to default
# Reset "a" and "f" to default
del opts["a"]
del opts["f"]
assert opts.a == 1
assert opts.b == 1
assert opts.c == 1
Expand Down Expand Up @@ -322,8 +354,7 @@ def test_initialise(self):
opts = factory.create({"f": 2.5})
with pytest.raises(TypeError):
opts = factory.create({"f": "2.0"})
with pytest.raises(TypeError):
opts = factory.create({"f": 2})
assert factory.create({"f": 2}).f == 2.0
with pytest.raises(ValueError):
opts = factory.create({"g": -1})
with pytest.raises(ValueError):
Expand Down Expand Up @@ -1054,8 +1085,7 @@ def test_initialise(self):
opts = factory.create_immutable({"f": 2.5})
with pytest.raises(TypeError):
opts = factory.create_immutable({"f": "2.0"})
with pytest.raises(TypeError):
opts = factory.create_immutable({"f": 2})
assert factory.create_immutable({"f": 2}).f == 2.0
with pytest.raises(ValueError):
opts = factory.create_immutable({"g": -1})
with pytest.raises(ValueError):
Expand Down
129 changes: 128 additions & 1 deletion optionsfactory/tests/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,135 @@ def test_initialise(self):
opts = factory.create({"f": 2.5})
with pytest.raises(TypeError):
opts = factory.create({"f": "2.0"})
assert factory.create({"f": 2}).f == 2.0
with pytest.raises(ValueError):
opts = factory.create({"g": -1})
with pytest.raises(ValueError):
opts = factory.create({"g": 30})
with pytest.raises(TypeError):
opts = factory.create({"g": 3.5})
with pytest.raises(ValueError):
opts = factory.create({"h": -7})
assert factory.create({"h": -21}).h == -21
with pytest.raises(TypeError):
opts = factory.create({"h": 3.5})
with pytest.raises(ValueError):
opts = factory.create({"a": -7})
assert factory.create({"a": -23}).h == -21
with pytest.raises(TypeError):
opts = factory.create({"a": 3.5})

def test_initialise_with_conversion_to_float(self):
factory = OptionsFactory(
a=1,
b=lambda options: options.a,
c=lambda options: options["a"],
d=lambda options: options.b + options.c,
e=WithMeta("b", value_type=int),
f=WithMeta(2.0, doc="option f", value_type=float, allowed=[2.0, 3.0]),
g=WithMeta(
11,
doc="option g",
value_type=int,
check_all=[is_positive, lambda x: x < 20],
),
h=WithMeta(
lambda options: options.a + 2,
doc="option h",
value_type=int,
check_any=[is_positive, lambda x: x < -20],
),
)

opts = factory.create({"a": 4, "b": 5, "f": 3, "g": 13, "z": 17})

assert opts.a == 4
assert opts.b == 5
assert opts.c == 4
assert opts.d == 9
assert opts.e == 5
assert opts.f == 3.0
assert isinstance(opts.f, float)
assert opts.g == 13
assert opts.h == 6

# "z" should have been ignored
with pytest.raises(AttributeError):
opts.z

assert opts["a"] == 4
assert opts["b"] == 5
assert opts["c"] == 4
assert opts["d"] == 9
assert opts["e"] == 5
assert opts["f"] == 3.0
assert isinstance(opts["f"], float)
assert opts["g"] == 13
assert opts["h"] == 6

# "z" should have been ignored
with pytest.raises(KeyError):
opts["z"]

assert opts.doc["a"] is None
assert opts.doc["b"] is None
assert opts.doc["c"] is None
assert opts.doc["d"] is None
assert opts.doc["e"] is None
assert opts.doc["f"] == "option f"
assert opts.doc["g"] == "option g"
assert opts.doc["h"] == "option h"

with pytest.raises(TypeError):
opts.a = 2

with pytest.raises(TypeError):
opts = factory.create({"f": 2})
opts["a"] = 2

assert not opts.is_default("a")
assert not opts.is_default("b")
assert opts.is_default("c")
assert opts.is_default("d")
assert opts.is_default("e")
assert not opts.is_default("f")
assert not opts.is_default("g")
assert opts.is_default("h")
with pytest.raises(KeyError):
opts.is_default("x")

assert "a" in opts
assert "b" in opts
assert "c" in opts
assert "d" in opts
assert "e" in opts
assert "f" in opts
assert "g" in opts
assert "h" in opts
assert not ("x" in opts)

assert len(opts) == 8
assert sorted([k for k in opts]) == sorted(
["a", "b", "c", "d", "e", "f", "g", "h"]
)
assert sorted(opts.values()) == sorted([4, 5, 4, 9, 5, 3.0, 13, 6])
assert sorted(opts.items()) == sorted(
[
("a", 4),
("b", 5),
("c", 4),
("d", 9),
("e", 5),
("f", 3.0),
("g", 13),
("h", 6),
]
)

with pytest.raises(ValueError):
opts = factory.create({"f": 2.5})
with pytest.raises(TypeError):
opts = factory.create({"f": "2.0"})
assert factory.create({"f": 2}).f == 2.0
with pytest.raises(ValueError):
opts = factory.create({"g": -1})
with pytest.raises(ValueError):
Expand Down
9 changes: 7 additions & 2 deletions optionsfactory/tests/test_withmeta.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ def test_value_type(self):
assert x.evaluate_expression({}) == 3.0

x.value = 3
with pytest.raises(TypeError):
x.evaluate_expression({})
assert x.evaluate_expression({}) == 3.0

def test_float_conversion(self):
x = WithMeta(3, value_type=float)
assert x.value_type is float
assert x.evaluate_expression({}) == 3.0
assert isinstance(x.evaluate_expression({}), float)

def test_value_type_sequence(self):
x = WithMeta(3.0, value_type=[float, NoneType])
Expand Down
7 changes: 6 additions & 1 deletion optionsfactory/withmeta.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import Sequence
from numbers import Number

from ._utils import _checked

Expand Down Expand Up @@ -65,7 +66,11 @@ def __init__(
self.check_any = value.check_any
return

self.value = value
if value_type is float and isinstance(value, Number):
# Allow converting any numerical type to float
self.value = float(value)
else:
self.value = value
self.doc = doc

if isinstance(value_type, Sequence):
Expand Down

0 comments on commit 12a4ad7

Please sign in to comment.