Skip to content

Commit 9a9dd3e

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

2 files changed

Lines changed: 102 additions & 44 deletions

File tree

khiops/core/dictionary.py

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,11 +1161,11 @@ def is_reference_rule(self):
11611161
"""
11621162
if self.rule:
11631163
if isinstance(self.rule, str):
1164-
if self.rule[0] == "[":
1164+
if self.rule.startswith("[") and self.rule.endswith("]"):
11651165
return True
11661166
else:
11671167
assert isinstance(self.rule, bytes)
1168-
if self.rule[0] == b"[":
1168+
if self.rule.startswith(b"[") and self.rule.endswith(b"]"):
11691169
return True
11701170
return False
11711171

@@ -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):
@@ -1455,20 +1460,12 @@ def write(self, writer):
14551460

14561461

14571462
class Rule:
1458-
"""A rule of a variable in a Khiops dictionary
1463+
"""A rule of a variable or variable block in a Khiops dictionary
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
1468+
Each tuple member can have one of the following types:
14721469
14731470
- str
14741471
- bytes
@@ -1479,15 +1476,24 @@ 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. If set, then ``names_and_operands``
1486+
must be empty.
1487+
14821488
is_reference : bool, default ``False``
14831489
If set to ``True``, then the rule is serialized as a reference rule:
14841490
``Rule(Operand1, Operand2, ...)`` is serialized as
14851491
``[Operand1, Operand2, ...]``.
14861492
14871493
Attributes
14881494
----------
1489-
name : str or bytes
1490-
Name of the rule.
1495+
name : str or bytes or ``None``
1496+
Name of the rule. It is ``None`` for reference rules.
14911497
operands : tuple of operands
14921498
Each operand has one of the following types:
14931499
@@ -1507,12 +1513,42 @@ class Rule:
15071513
This attribute cannot be changed on a `Rule` instance.
15081514
"""
15091515

1510-
def __init__(self, name, *operands, is_reference=False):
1516+
def __init__(self, *name_and_operands, verbatim=None, is_reference=False):
15111517
"""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:
1518+
# Check input parameters and initialize rule fragments accordigly
1519+
if not isinstance(is_reference, bool):
1520+
raise TypeError(type_error_message("is_reference", is_reference, bool))
1521+
1522+
# Rule provided as name plus operands
1523+
if verbatim is None:
1524+
if not name_and_operands:
1525+
raise ValueError("A name must be provided to a standard rule")
1526+
if is_reference:
1527+
self.name = None
1528+
self.operands = name_and_operands
1529+
else:
1530+
name, *operands = name_and_operands
1531+
if not is_string_like(name):
1532+
raise TypeError(type_error_message("name", name, "string-like"))
1533+
if not name:
1534+
raise ValueError("'name' must be a non-empty string")
1535+
self.name = name
1536+
self.operands = operands
1537+
# Rule provided as verbatim
1538+
else:
1539+
if not is_string_like(verbatim):
1540+
raise TypeError(type_error_message("verbatim", verbatim, "string-like"))
1541+
if not verbatim:
1542+
raise ValueError("'verbatim' must be a non-empty string")
1543+
if name_and_operands:
1544+
raise ValueError(
1545+
"Rule name and operands must not be provided for verbatim rules"
1546+
)
1547+
self.name = None
1548+
self.operands = ()
1549+
1550+
# Check operand types
1551+
for operand in self.operands:
15161552
if not is_string_like(operand) and not isinstance(
15171553
operand, (int, float, Variable, Rule, _ScopedOperand)
15181554
):
@@ -1529,14 +1565,9 @@ def __init__(self, name, *operands, is_reference=False):
15291565
"upper-scoped Rule",
15301566
)
15311567
)
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")
15361568

1537-
# Initialize attributes
1538-
self.name = name
1539-
self.operands = operands
1569+
# Initialize private attributes
1570+
self._verbatim = verbatim
15401571
self._is_reference = is_reference
15411572

15421573
@property
@@ -1568,8 +1599,7 @@ def write(self, writer):
15681599
Output writer.
15691600
15701601
.. note::
1571-
If ``self.is_reference`` is set, then ``self.name`` is not
1572-
included in the serialization.
1602+
``self.name`` is not included in the serialization of reference rules.
15731603
"""
15741604
# Check the type of the writer
15751605
if not isinstance(writer, KhiopsOutputWriter):
@@ -1606,14 +1636,17 @@ def write(self, writer):
16061636
writer.write("]")
16071637
else:
16081638
writer.write(")")
1609-
# Write verbatim-given rule
1639+
# Write no-operand rule
16101640
elif (
16111641
isinstance(self.name, str)
16121642
and rule_regex.match(self.name)
16131643
or isinstance(self.name, bytes)
16141644
and bytes_rule_regex.match(self.name)
16151645
):
16161646
writer.write(self.name)
1647+
# Write verbatim-given rule
1648+
elif self._verbatim:
1649+
writer.write(self._verbatim)
16171650

16181651

16191652
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)