Skip to content
This repository was archived by the owner on Jun 27, 2023. It is now read-only.

Commit 67e73af

Browse files
4.0.0 refactoring - some tests added
1 parent 34d7a53 commit 67e73af

14 files changed

Lines changed: 222 additions & 359 deletions

multibar/api/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
from .writers import *
99

1010
__all__ = (
11-
"CalculationServiceAware",
11+
"AbstractCalculationService",
1212
"ProgressbarClientAware",
1313
"ContractAware",
1414
"ContractCheck",
1515
"ContractManagerAware",
1616
"HookSignatureType",
1717
"HooksAware",
1818
"ProgressbarAware",
19-
"SectorAware",
19+
"AbstractSector",
2020
"SignatureSegmentProtocol",
2121
"ProgressbarSignatureProtocol",
2222
"ProgressbarWriterAware",

multibar/impl/contracts.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from returns.io import IO
1111

1212
from multibar import errors, output
13-
from multibar import types as progress_types
1413
from multibar.api import contracts
1514

1615

multibar/settings.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import collections.abc
4-
import copy
54
import typing
65

76
SelfT = typing.TypeVar("SelfT", bound="Settings")
@@ -14,8 +13,6 @@ def _config_get_function(self: SelfT, key: str) -> typing.Any:
1413

1514

1615
class Settings:
17-
__slots__ = ("_config",)
18-
1916
__getattr__ = __getitem__ = _config_get_function
2017

2118
def __init__(self) -> None:
@@ -29,30 +26,11 @@ def __contains__(self, item: typing.Any) -> bool:
2926
def __copy__(self) -> Settings:
3027
return self.copy()
3128

32-
def __deepcopy__(
33-
self,
34-
memodict: typing.Optional[dict[int, typing.Any]] = None,
35-
/,
36-
) -> Settings:
37-
return self.deepcopy(memodict)
38-
3929
def copy(self) -> Settings:
4030
new_instance = self.__class__()
4131
new_instance.configure(**self._config)
4232
return new_instance
4333

44-
def deepcopy(self, memodict: typing.Optional[dict[int, typing.Any]] = None, /) -> Settings:
45-
if memodict is None:
46-
memodict = {}
47-
48-
state = self.__class__()
49-
memodict[id(self)] = state
50-
for slot in self.__slots__:
51-
self_value = getattr(self, slot)
52-
setattr(state, slot, copy.deepcopy(self_value, memodict))
53-
54-
return state
55-
5634
def configure(self, **kwargs: typing.Any) -> None:
5735
self._config.update(kwargs)
5836

multibar/types.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,7 @@
66
from multibar.api import calculation_service, progressbars, sectors, signatures
77

88

9-
class ContractMetadata(typing.TypedDict, total=False):
10-
pass
11-
12-
13-
class ProgressMetadataType(ContractMetadata, total=False):
9+
class ProgressMetadataType(typing.TypedDict, total=False):
1410
start_value: int
1511
end_value: int
1612
length: int

multibar/utils.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__all__ = ["none_or"]
1+
__all__ = ["none_or", "cached_property"]
22

33
import typing
44

@@ -33,15 +33,9 @@ def __get__(
3333
instance: object,
3434
owner: typing.Optional[typing.Type[typing.Any]] = None,
3535
) -> typing.Any:
36-
if not hasattr(instance, "__dict__"):
37-
# For correct execution of cls.update_cache_for
38-
return self.func
3936
result = instance.__dict__[self.func.__name__] = self.func(instance)
4037
return result
4138

4239
@classmethod
43-
def update_cache_for(cls, state: object, prop_name: str) -> None:
44-
state_type = type(state)
45-
origin = getattr(state_type, prop_name)
46-
delattr(state, prop_name)
47-
setattr(state_type, prop_name, cls(origin))
40+
def update_cache_for(cls, state: object, prop_name: str, /) -> None:
41+
del state.__dict__[prop_name]

tests/pyhamcrest.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from __future__ import annotations
2+
3+
__all__ = ("subclass_of",)
4+
5+
import typing
6+
7+
from hamcrest.core.base_matcher import BaseMatcher
8+
9+
if typing.TYPE_CHECKING:
10+
from hamcrest.core.description import Description
11+
12+
13+
class IsSubclassOf(BaseMatcher):
14+
def __init__(
15+
self,
16+
cls_or_tuple: typing.Union[typing.Type[typing.Any], tuple[typing.Type[typing.Any], ...]], /
17+
) -> None:
18+
self._cls_or_tuple = cls_or_tuple
19+
self.failed: typing.Optional[str] = None
20+
21+
def _matches(self, cls: typing.Type[typing.Any]) -> bool:
22+
result = issubclass(cls, self._cls_or_tuple)
23+
if not result:
24+
self.failed = cls
25+
return result
26+
27+
def describe_to(self, description: Description) -> None:
28+
(
29+
description.append_text("failing on ").append_text(
30+
f"<{self.failed}> attribute"
31+
)
32+
)
33+
34+
35+
def subclass_of(
36+
cls_or_tuple: typing.Union[typing.Type[typing.Any], tuple[typing.Type[typing.Any], ...]], /
37+
) -> IsSubclassOf:
38+
return IsSubclassOf(cls_or_tuple)
39+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from hamcrest import assert_that, equal_to, has_properties, instance_of, has_length
2+
3+
from multibar import iterators
4+
from multibar.impl.calculation_service import ProgressbarCalculationService
5+
6+
7+
def test_percentage() -> None:
8+
percentage = ProgressbarCalculationService.get_progress_percentage(50, 100)
9+
assert_that(percentage, equal_to(50.0))
10+
11+
12+
def test_calculation_service() -> None:
13+
calc_service = ProgressbarCalculationService(50, 100, 20)
14+
15+
assert_that(
16+
calc_service,
17+
has_properties(
18+
{
19+
"start_value": equal_to(50),
20+
"end_value": equal_to(100),
21+
"length_value": equal_to(20),
22+
"progress_percents": equal_to(50.0),
23+
}
24+
)
25+
)
26+
27+
first_part = calc_service.calculate_filled_indexes()
28+
second_part = calc_service.calculate_unfilled_indexes()
29+
30+
assert_that(first_part, instance_of(iterators.AbstractIterator))
31+
assert_that(second_part, instance_of(iterators.AbstractIterator))
32+
33+
assert_that(list(first_part), has_length(calc_service.length_value // 2))
34+
assert_that(list(second_part), has_length(calc_service.length_value // 2))

tests/unit/test_iterators.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import collections.abc
2+
3+
import pytest
4+
from hamcrest import assert_that, instance_of, is_in, equal_to
5+
6+
from multibar import iterators
7+
from tests.pyhamcrest import subclass_of
8+
9+
10+
def test_iterator() -> None:
11+
iterator = iterators.Iterator(iter(()))
12+
13+
assert_that(iterators.Iterator, subclass_of(collections.abc.Iterator))
14+
assert_that(iterator, instance_of(collections.abc.Iterator))
15+
16+
assert_that("_iterator", is_in(iterator.__slots__))
17+
18+
with pytest.raises(StopIteration):
19+
next(iterator)
20+
21+
22+
def test_convertable_index_iterator() -> None:
23+
iterator1 = iterators.Iterator(iter((24, 235, 34)))
24+
assert_that(list(iterator1.indexes()), equal_to([0, 1, 2]))
25+
26+
iterator2 = iterators.Iterator(iter((34, 6546, 32)))
27+
assert_that(list(iterator2.indexes(conversion=lambda i: i + 1)), equal_to([1, 2, 3]))

tests/unit/test_settings.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import copy
2+
3+
from hamcrest import assert_that, is_in, instance_of, equal_to, calling, raises, not_
4+
5+
from multibar.settings import settings
6+
7+
8+
class TestSettings:
9+
def test_settings_copy(self) -> None:
10+
settings_copy = settings.copy()
11+
supports_copy = copy.copy(settings)
12+
13+
settings_copy.configure(key="value")
14+
15+
assert_that("key", is_in(settings_copy))
16+
assert_that("key", not_(is_in(settings)))
17+
18+
supports_copy.configure(key="value")
19+
20+
assert_that("key", is_in(supports_copy))
21+
assert_that("key", not_(is_in(settings)))
22+
23+
def test_settings_state(self) -> None:
24+
mock_settings = settings.copy()
25+
26+
assert_that("_config", is_in(mock_settings.__dict__))
27+
28+
def test_settings_get(self) -> None:
29+
mock_settings = settings.copy()
30+
mock_settings.__dict__["__dunder__"] = "I'm dunder."
31+
32+
assert_that(mock_settings.__dunder__, instance_of(str))
33+
assert_that(mock_settings["__dunder__"], instance_of(str))
34+
35+
mock_settings.configure(non_dunder="non_dunder")
36+
37+
assert_that(mock_settings.non_dunder, equal_to("non_dunder"))
38+
assert_that(mock_settings["non_dunder"], equal_to("non_dunder"))
39+
40+
mock_settings.configure(__new_dunder__="new_dunder")
41+
42+
# Because all dunder-attrs getting from self.__dict__
43+
assert_that(calling(lambda: mock_settings["__new_dunder__"]), raises(KeyError))
44+
assert_that(calling(lambda: mock_settings.__new_dunder__), raises(KeyError))
45+
46+
def test_setting_in(self) -> None:
47+
mock_settings = settings.copy()
48+
49+
class NonHashableMock:
50+
__hash__ = None
51+
52+
non_hashable_mock = NonHashableMock()
53+
mock_settings.configure(non_hashable=non_hashable_mock)
54+
55+
# Hashable keys are searching in dict.keys()
56+
assert_that("non_hashable", is_in(mock_settings))
57+
# Non-hashable keys are searching in dict.values()
58+
assert_that(non_hashable_mock, is_in(mock_settings))

tests/unit/test_utils.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from hamcrest import assert_that, not_, is_in, equal_to
2+
3+
from multibar.utils import cached_property, none_or
4+
5+
6+
class MockClass:
7+
def __init__(self) -> None:
8+
self.state_attr: int = 2
9+
10+
@cached_property
11+
def mock_property(self) -> int:
12+
return self.state_attr ** 1_000
13+
14+
15+
class TestCachedProperty:
16+
def test_assert_property_in_cache(self) -> None:
17+
fake_instance = MockClass()
18+
assert_that("mock_property", not_(is_in(fake_instance.__dict__)))
19+
20+
# Getting attr for prop caching
21+
getattr(fake_instance, "mock_property")
22+
23+
assert_that("mock_property", is_in(fake_instance.__dict__))
24+
25+
def test_cachedproperty_update_cache(self) -> None:
26+
fake_instance = MockClass()
27+
assert_that("mock_property", not_(is_in(fake_instance.__dict__)))
28+
29+
# Getting attr for prop caching
30+
raw_value = fake_instance.mock_property
31+
32+
assert_that("mock_property", is_in(fake_instance.__dict__))
33+
34+
fake_instance.state_attr = 3
35+
value_from_cache = fake_instance.mock_property
36+
assert_that(value_from_cache, equal_to(raw_value))
37+
38+
cached_property.update_cache_for(fake_instance, "mock_property")
39+
# Getting attr for updating prop cache
40+
new_raw_value = fake_instance.mock_property
41+
new_value_from_cache = fake_instance.mock_property
42+
43+
assert_that(new_raw_value, equal_to(new_value_from_cache))
44+
assert_that(new_raw_value, not_(equal_to(value_from_cache)))
45+
46+
47+
class TestNoneOr:
48+
def test_actual_is_none(self) -> None:
49+
cls = None
50+
result = none_or(int, cls)
51+
52+
assert_that(result, equal_to(int))
53+
54+
def test_actual_is_not_none(self) -> None:
55+
cls = float
56+
result = none_or(int, cls)
57+
58+
assert_that(result, equal_to(float))

0 commit comments

Comments
 (0)