Skip to content

Commit bdc57fd

Browse files
committed
Streamline Rule object support for reference and verbatim rules
1 parent f156330 commit bdc57fd

File tree

2 files changed

+104
-42
lines changed

2 files changed

+104
-42
lines changed

khiops/core/dictionary.py

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,7 +1187,7 @@ def full_type(self):
11871187

11881188
def get_rule(self):
11891189
"""Gets `Rule` from a specified variable"""
1190-
return Rule(name=self.rule)
1190+
return Rule(verbatim=self.rule, is_reference=self.is_reference_rule())
11911191

11921192
def set_rule(self, rule):
11931193
"""Sets a rule on a specified variable in the dictionary
@@ -1383,7 +1383,7 @@ def get_value(self, key):
13831383

13841384
def get_rule(self):
13851385
"""Gets `Rule` from a specified variable block"""
1386-
return Rule(name=self.rule)
1386+
return Rule(verbatim=self.rule)
13871387

13881388
def set_rule(self, rule):
13891389
"""Sets a rule on a specified variable block in the dictionary
@@ -1397,9 +1397,14 @@ def set_rule(self, rule):
13971397
------
13981398
`TypeError`
13991399
If ``rule`` is not of type `Rule`
1400+
1401+
`ValueError`
1402+
If ``rule`` is a reference rule
14001403
"""
14011404
if not isinstance(rule, Rule):
14021405
raise TypeError(type_error_message("rule", rule, Rule))
1406+
if rule.is_reference:
1407+
raise ValueError("Cannot set reference rule on a variable block")
14031408
self.rule = repr(rule)
14041409

14051410
def write(self, writer):
@@ -1459,16 +1464,8 @@ class Rule:
14591464
14601465
Parameters
14611466
----------
1462-
name : str or bytes
1463-
Name or verbatim of the rule. It is intepreted as the verbatim
1464-
representation of an entire rule if and only if:
1465-
1466-
- it starts with an UpperCamelCase string, followed by a
1467-
parenthesized block (...)
1468-
- ``operands`` is empty
1469-
1470-
operands : tuple of operands
1471-
Each operand can have one of the following types:
1467+
name_and_operands : tuple of rule fragments
1468+
Each rule fragment can have one of the following types:
14721469
14731470
- str
14741471
- bytes
@@ -1479,6 +1476,17 @@ class Rule:
14791476
- upper-scoped `Variable`
14801477
- upper-scoped `Rule`
14811478
1479+
.. note::
1480+
The first element of the ``name_and_operands`` tuple is the name of
1481+
the rule and must be str or bytes and non-empty for a standard rule,
1482+
i.e. if ``is_reference`` is not set.
1483+
1484+
verbatim : str or bytes, optional
1485+
Verbatim representation of an entire rule.
1486+
1487+
.. note::
1488+
``names_and_operands`` must be empty if ``verbatim`` is set.
1489+
14821490
is_reference : bool, default ``False``
14831491
If set to ``True``, then the rule is serialized as a reference rule:
14841492
``Rule(Operand1, Operand2, ...)`` is serialized as
@@ -1487,7 +1495,7 @@ class Rule:
14871495
Attributes
14881496
----------
14891497
name : str or bytes
1490-
Name of the rule.
1498+
Name of the rule. It is ``None`` for reference rules.
14911499
operands : tuple of operands
14921500
Each operand has one of the following types:
14931501
@@ -1507,12 +1515,47 @@ class Rule:
15071515
This attribute cannot be changed on a `Rule` instance.
15081516
"""
15091517

1510-
def __init__(self, name, *operands, is_reference=False):
1518+
def __init__(self, *name_and_operands, verbatim=None, is_reference=False):
15111519
"""See class docstring"""
1512-
# Check input parameters
1513-
if not is_string_like(name):
1514-
raise TypeError(type_error_message("name", name, "string-like"))
1515-
for operand in operands:
1520+
# Check input parameters and initialize rule fragments accordigly
1521+
if not isinstance(is_reference, bool):
1522+
raise TypeError(type_error_message("is_reference", is_reference, bool))
1523+
if verbatim is not None:
1524+
if not is_string_like(verbatim):
1525+
raise TypeError(type_error_message("verbatim", verbatim, "string-like"))
1526+
if not verbatim:
1527+
raise ValueError(
1528+
"'verbatim' must not be set to an empty string-like object"
1529+
)
1530+
if name_and_operands:
1531+
raise ValueError(
1532+
"Rule name and operands must not be provided for verbatim rules"
1533+
)
1534+
self.name = None
1535+
self.operands = ()
1536+
else:
1537+
if not name_and_operands:
1538+
raise ValueError("A name must be provided to a standard rule")
1539+
if is_reference:
1540+
self.name = None
1541+
self.operands = name_and_operands
1542+
else:
1543+
name, *operands = name_and_operands
1544+
if not is_string_like(name):
1545+
raise TypeError(type_error_message("name", name, "string-like"))
1546+
if not name:
1547+
raise ValueError("'name' must be a non-empty string")
1548+
self.name = name
1549+
self.operands = operands
1550+
1551+
self._check_operands()
1552+
1553+
# Initialize private attributes
1554+
self._verbatim = verbatim
1555+
self._is_reference = is_reference
1556+
1557+
def _check_operands(self):
1558+
for operand in self.operands:
15161559
if not is_string_like(operand) and not isinstance(
15171560
operand, (int, float, Variable, Rule, _ScopedOperand)
15181561
):
@@ -1529,15 +1572,6 @@ def __init__(self, name, *operands, is_reference=False):
15291572
"upper-scoped Rule",
15301573
)
15311574
)
1532-
if not isinstance(is_reference, bool):
1533-
raise TypeError(type_error_message("is_reference", is_reference, bool))
1534-
if not is_reference and not name:
1535-
raise ValueError("'name' must be a non-empty string")
1536-
1537-
# Initialize attributes
1538-
self.name = name
1539-
self.operands = operands
1540-
self._is_reference = is_reference
15411575

15421576
@property
15431577
def is_reference(self):
@@ -1568,8 +1602,8 @@ def write(self, writer):
15681602
Output writer.
15691603
15701604
.. note::
1571-
If ``self.is_reference`` is set, then ``self.name`` is not
1572-
included in the serialization.
1605+
``self.name`` is not included in the serialization of reference
1606+
rules.
15731607
"""
15741608
# Check the type of the writer
15751609
if not isinstance(writer, KhiopsOutputWriter):
@@ -1606,14 +1640,17 @@ def write(self, writer):
16061640
writer.write("]")
16071641
else:
16081642
writer.write(")")
1609-
# Write verbatim-given rule
1643+
# Write no-operand rule
16101644
elif (
16111645
isinstance(self.name, str)
16121646
and rule_regex.match(self.name)
16131647
or isinstance(self.name, bytes)
16141648
and bytes_rule_regex.match(self.name)
16151649
):
16161650
writer.write(self.name)
1651+
# Write verbatim-given rule
1652+
elif self._verbatim:
1653+
writer.write(self._verbatim)
16171654

16181655

16191656
class _ScopedOperand:

tests/test_core.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,9 +1945,26 @@ def test_dictionary_accessors(self):
19451945
repr(dictionary_copy.get_variable(variable_name).get_rule()),
19461946
repr(some_rule),
19471947
)
1948+
some_reference_rule = kh.Rule(
1949+
"some_reference_operand_for_variable" + variable_index * "i",
1950+
kh.Variable(
1951+
json_data={
1952+
"name": "SomeReferenceVariable" + variable_index * "i",
1953+
"type": "Categorical",
1954+
}
1955+
),
1956+
is_reference=True,
1957+
)
1958+
dictionary_copy.get_variable(variable_name).set_rule(
1959+
some_reference_rule
1960+
)
1961+
self.assertEqual(
1962+
dictionary_copy.get_variable(variable_name).rule,
1963+
repr(some_reference_rule),
1964+
)
19481965
self.assertEqual(
19491966
repr(dictionary_copy.get_variable(variable_name).get_rule()),
1950-
repr(some_rule),
1967+
repr(some_reference_rule),
19511968
)
19521969
for variable_block_index, variable_block_name in enumerate(
19531970
[
@@ -1976,6 +1993,16 @@ def test_dictionary_accessors(self):
19761993
),
19771994
repr(some_rule),
19781995
)
1996+
some_reference_rule = kh.Rule(
1997+
"some_reference_operand_for_variable block"
1998+
+ variable_block_index * "i",
1999+
3,
2000+
is_reference=True,
2001+
)
2002+
with self.assertRaises(ValueError):
2003+
dictionary_copy.get_variable_block(
2004+
variable_block_name
2005+
).set_rule(some_reference_rule)
19792006

19802007
def test_dictionary_rule_construction(self):
19812008
"""Tests the Rule construction and serialization"""
@@ -2019,11 +2046,11 @@ def test_dictionary_rule_construction(self):
20192046
[kh.Rule(b"SomeRule")],
20202047
[
20212048
kh.Rule("SomeRule", "some_operand", 2),
2022-
kh.Rule('SomeRule("some_operand", 2)'),
2049+
kh.Rule(verbatim='SomeRule("some_operand", 2)'),
20232050
],
20242051
[
20252052
kh.Rule(b"SomeRule", b"some_operand", 2),
2026-
kh.Rule(b'SomeRule("some_operand", 2)'),
2053+
kh.Rule(verbatim=b'SomeRule("some_operand", 2)'),
20272054
],
20282055
[
20292056
kh.Rule("SomeRule", 'some"operand', 2),
@@ -2032,7 +2059,7 @@ def test_dictionary_rule_construction(self):
20322059
kh.Rule(b"SomeRule", b'some"operand', 2),
20332060
],
20342061
[
2035-
kh.Rule('SomeRule("some_operand", 2, SomeVariable)'),
2062+
kh.Rule(verbatim='SomeRule("some_operand", 2, SomeVariable)'),
20362063
kh.Rule(
20372064
"SomeRule",
20382065
"some_operand",
@@ -2127,13 +2154,13 @@ def test_dictionary_rule_construction(self):
21272154
kh.Rule("SomeRule", "some_operand", math.nan),
21282155
kh.Rule("SomeRule", "some_operand", float("inf")),
21292156
kh.Rule("SomeRule", "some_operand", float("-inf")),
2130-
kh.Rule('SomeRule("some_operand", #Missing)'),
2157+
kh.Rule(verbatim='SomeRule("some_operand", #Missing)'),
21312158
],
21322159
[
21332160
kh.Rule(b"SomeRule", b"some_operand", math.nan),
21342161
kh.Rule(b"SomeRule", b"some_operand", float("inf")),
21352162
kh.Rule(b"SomeRule", b"some_operand", float("-inf")),
2136-
kh.Rule(b'SomeRule("some_operand", #Missing)'),
2163+
kh.Rule(verbatim=b'SomeRule("some_operand", #Missing)'),
21372164
],
21382165
[
21392166
kh.Rule(
@@ -2143,10 +2170,10 @@ def test_dictionary_rule_construction(self):
21432170
kh.Rule("SomeEmbeddedRule", "some_other_operand"),
21442171
),
21452172
kh.Rule(
2146-
(
2173+
verbatim=(
21472174
'SomeRule("some_operand", 2, '
21482175
'SomeEmbeddedRule("some_other_operand"))'
2149-
)
2176+
),
21502177
),
21512178
],
21522179
[
@@ -2195,11 +2222,10 @@ def test_dictionary_rule_construction(self):
21952222
),
21962223
)
21972224
],
2198-
[kh.Rule("", "some_reference_operand", is_reference=True)],
2199-
[kh.Rule(b"", b"some_reference_operand", is_reference=True)],
2225+
[kh.Rule("some_reference_operand", is_reference=True)],
2226+
[kh.Rule(b"some_reference_operand", is_reference=True)],
22002227
[
22012228
kh.Rule(
2202-
"",
22032229
"some_reference_operand",
22042230
kh.Variable(
22052231
json_data={
@@ -2212,7 +2238,6 @@ def test_dictionary_rule_construction(self):
22122238
],
22132239
[
22142240
kh.Rule(
2215-
b"",
22162241
b"some_reference_operand",
22172242
kh.Variable(
22182243
json_data={

0 commit comments

Comments
 (0)