Skip to content

Commit d74144c

Browse files
committed
YT-PYCC-9: Add the inherit_constructor parameter
Added the `inherit_constructor` parameter to the `const_class` decorator and unit tests covering the added functionality.
1 parent d5d58fc commit d74144c

File tree

7 files changed

+81
-10
lines changed

7 files changed

+81
-10
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
55

66
[project]
77
name = "pyconstclasses"
8-
version = "0.5"
8+
version = "0.8"
99
description = "Package with const class decoratos and utility"
1010
authors = [
1111
{name = "SpectraL519"}

src/constclasses/const_class.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
11
from .ccerror import ConstError, InitializationError
2-
from .const_class_base import CC_BASE_ATTR_NAME, ConstClassBase
2+
from .const_class_base import (
3+
CC_BASE_ATTR_NAME,
4+
CC_INITIALIZED_ATTR_NAME,
5+
ConstClassBase,
6+
)
37

48

59
def const_class_impl(
6-
cls, with_kwargs: bool, with_strict_types: bool, include: set[str], exclude: set[str]
10+
cls,
11+
with_kwargs: bool,
12+
with_strict_types: bool,
13+
include: set[str],
14+
exclude: set[str],
15+
inherit_constructor: bool,
716
):
817
class ConstClass(cls):
918
def __init__(self, *args, **kwargs):
10-
super(ConstClass, self).__init__()
11-
1219
self.__dict__[CC_BASE_ATTR_NAME] = ConstClassBase(
1320
with_strict_types=with_strict_types, include=include, exclude=exclude
1421
)
22+
self.__dict__[CC_INITIALIZED_ATTR_NAME] = False
23+
24+
if inherit_constructor:
25+
super(ConstClass, self).__init__(*args, **kwargs)
26+
self._cc_initialized = True
27+
return
28+
else:
29+
super(ConstClass, self).__init__()
1530

1631
if with_kwargs:
1732
for attr_name, attr_type in cls.__annotations__.items():
@@ -29,8 +44,10 @@ def __init__(self, *args, **kwargs):
2944
attr_name, attr_type, args[i]
3045
)
3146

47+
self._cc_initialized = True
48+
3249
def __setattr__(self, attr_name: str, attr_value) -> None:
33-
if self._cc_base.is_const_attribute(attr_name):
50+
if self._cc_initialized and self._cc_base.is_const_attribute(attr_name):
3451
raise ConstError(cls.__name__, attr_name)
3552
self.__dict__[attr_name] = self._cc_base.process_attribute_type(
3653
attr_name, cls.__annotations__.get(attr_name), attr_value
@@ -50,8 +67,11 @@ def const_class(
5067
with_strict_types: bool = False,
5168
include: set[str] = None,
5269
exclude: set[str] = None,
70+
inherit_constructor=False,
5371
):
5472
def _wrap(cls):
55-
return const_class_impl(cls, with_kwargs, with_strict_types, include, exclude)
73+
return const_class_impl(
74+
cls, with_kwargs, with_strict_types, include, exclude, inherit_constructor
75+
)
5676

5777
return _wrap if cls is None else _wrap(cls)

src/constclasses/const_class_base.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@ class ConstClassBase:
99
def __init__(
1010
self,
1111
/,
12+
cls_attrs: set[str] = set(),
1213
*,
1314
include: set[str] = None,
1415
exclude: set[str] = None,
1516
with_strict_types: bool = False,
1617
):
18+
intersection = cls_attrs & MANDATORY_CONST_ATTRS
19+
if intersection:
20+
raise ConfigurationError(
21+
f"Const class cannot have members: [{', '.join(intersection)}]"
22+
)
23+
1724
if include is not None and exclude is not None:
1825
raise ConfigurationError(
1926
"`include` and `exclude` parameters cannot be used simultaneously"

src/constclasses/static_const_class.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ def static_const_class_impl(
1212
):
1313
class StaticConstClass(cls):
1414
def __init__(self, *args, **kwargs):
15-
super(StaticConstClass, self).__init__(*args, **kwargs)
1615
self.__dict__[CC_BASE_ATTR_NAME] = ConstClassBase(
1716
with_strict_types=with_strict_types, include=include, exclude=exclude
1817
)
1918
self.__dict__[CC_INITIALIZED_ATTR_NAME] = False
19+
super(StaticConstClass, self).__init__()
2020

2121
def __setattr__(self, attr_name: str, attr_value) -> None:
2222
if self._cc_initialized and self._cc_base.is_const_attribute(attr_name):

test/common/utility.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ def msg(err: Exception) -> str:
55
return str(err.value)
66

77

8+
def config_not_empty_cls_attrs_intersection_msg_prefix() -> str:
9+
return "Const class cannot have members"
10+
11+
812
def config_include_and_exclude_used_error_msg_postfix() -> str:
913
return "`include` and `exclude` parameters cannot be used simultaneously"
1014

test/test_const_class.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,25 @@ class ConstClassStrictTypes:
176176
with pytest.raises(TypeError) as err:
177177
setattr(const_instance, attr_name, DUMMY_VALUE)
178178
assert util.msg(err).startswith(util.invalid_type_error_msg_prefix())
179+
180+
181+
def test_initialization_with_inherited_constructor():
182+
@const_class(inherit_constructor=True)
183+
class ConstClassWithConstructor:
184+
x: int
185+
s: str
186+
187+
def __init__(self, x: int):
188+
self.x = x
189+
self.s = str(x)
190+
191+
x_value = ATTR_VALS_1[X_ATTR_NAME]
192+
const_instance = None
193+
194+
def _initialize():
195+
nonlocal const_instance
196+
const_instance = ConstClassWithConstructor(x_value)
197+
198+
util.assert_does_not_throw(_initialize)
199+
assert const_instance.x == x_value
200+
assert const_instance.s == str(x_value)

test/test_const_class_base.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,18 @@
55
from constclasses.const_class_base import MANDATORY_CONST_ATTRS, ConstClassBase
66

77

8-
def test_const_class_base_setup_for_not_none_include_and_exclude_parameters():
8+
def test_init_for_class_attrs_intersecting_with_mandatory_const_attrs():
9+
for attr_name in MANDATORY_CONST_ATTRS:
10+
with pytest.raises(ConfigurationError) as err:
11+
_ = ConstClassBase(cls_attrs={attr_name})
12+
assert util.config_not_empty_cls_attrs_intersection_msg_prefix() in util.msg(err)
13+
14+
with pytest.raises(ConfigurationError) as err:
15+
_ = ConstClassBase(cls_attrs=MANDATORY_CONST_ATTRS)
16+
assert util.config_not_empty_cls_attrs_intersection_msg_prefix() in util.msg(err)
17+
18+
19+
def test_init_for_not_none_include_and_exclude_parameters():
920
with pytest.raises(ConfigurationError) as err:
1021
_ = ConstClassBase(include={}, exclude={})
1122

@@ -14,7 +25,7 @@ def test_const_class_base_setup_for_not_none_include_and_exclude_parameters():
1425
)
1526

1627

17-
def test_const_class_base_setup_for_exclude_intersecting_with_mandatory_const_fields():
28+
def test_init_for_exclude_intersecting_with_mandatory_const_fields():
1829
for mandatory_const_attr in MANDATORY_CONST_ATTRS:
1930
with pytest.raises(ConfigurationError) as err:
2031
_ = ConstClassBase(exclude={mandatory_const_attr})
@@ -31,6 +42,13 @@ def test_const_class_base_setup_for_exclude_intersecting_with_mandatory_const_fi
3142
assert err_msg.endswith(util.config_invalid_exclude_error_msg(MANDATORY_CONST_ATTRS))
3243

3344

45+
def test_init_with_valid_parameter_configuration():
46+
def _test():
47+
_ = ConstClassBase()
48+
49+
util.assert_does_not_throw(_test)
50+
51+
3452
@pytest.fixture
3553
def gen_attributes():
3654
no_attrs = 8

0 commit comments

Comments
 (0)