|
13 | 13 |
|
14 | 14 | """ |
15 | 15 | import io |
| 16 | +import math |
16 | 17 | import os |
17 | 18 | import re |
18 | 19 | import warnings |
@@ -880,6 +881,124 @@ def remove_variable_block( |
880 | 881 |
|
881 | 882 | return removed_block |
882 | 883 |
|
| 884 | + def _check_name(self, name, name_label): |
| 885 | + if not is_string_like(name): |
| 886 | + raise TypeError(type_error_message(name_label, name, str)) |
| 887 | + if not name: |
| 888 | + raise ValueError(f"'{name_label}' must not be empty") |
| 889 | + |
| 890 | + def _set_rule(self, rule, name, label, getter): |
| 891 | + # Perform input checks |
| 892 | + self._check_name(name, label) |
| 893 | + if not isinstance(rule, Rule): |
| 894 | + raise TypeError(type_error_message("rule", rule, Rule)) |
| 895 | + |
| 896 | + # Get the object of "name" and set the serialized rule on it |
| 897 | + getattr(self, getter)(name).rule = repr(rule) |
| 898 | + |
| 899 | + def _get_rule(self, name, label, getter): |
| 900 | + # Perform input checks |
| 901 | + self._check_name(name, label) |
| 902 | + |
| 903 | + # Get the serialized rule from the object of "name" and pack it into a |
| 904 | + # `Rule` |
| 905 | + return Rule(name=getattr(self, getter)(name).rule) |
| 906 | + |
| 907 | + def set_variable_rule(self, variable_name, rule): |
| 908 | + """Sets a rule on a specified variable in the dictionary |
| 909 | +
|
| 910 | + Parameters |
| 911 | + ---------- |
| 912 | + variable_name : str |
| 913 | + Name of the variable the rule is set on. |
| 914 | + rule : `Rule` |
| 915 | + The rule to be set on the variable whose name is ``variable_name``. |
| 916 | +
|
| 917 | + Raises |
| 918 | + ------ |
| 919 | + `TypeError` |
| 920 | + If ``rule`` is not of type `Rule` |
| 921 | + If ``variable_name`` is not of type `str` |
| 922 | +
|
| 923 | + `ValueError` |
| 924 | + If ``variable_name`` is the empty string |
| 925 | +
|
| 926 | + `KeyError` |
| 927 | + If no variable of name ``variable_name`` exists in the dictionary |
| 928 | + """ |
| 929 | + self._set_rule(rule, variable_name, "variable_name", "get_variable") |
| 930 | + |
| 931 | + def set_variable_block_rule(self, variable_block_name, rule): |
| 932 | + """Sets a rule on a specified variable block in the dictionary |
| 933 | +
|
| 934 | + Parameters |
| 935 | + ---------- |
| 936 | + variable_block_name : str |
| 937 | + Name of the variable block the rule is set on. |
| 938 | + rule : `Rule` |
| 939 | + The rule to be set on the variable block whose name is |
| 940 | + ``variable_block_name``. |
| 941 | +
|
| 942 | + Raises |
| 943 | + ------ |
| 944 | + `TypeError` |
| 945 | + If ``rule`` is not of type `Rule` |
| 946 | + If ``variable_block_name`` is not of type `str` |
| 947 | +
|
| 948 | + `ValueError` |
| 949 | + If ``variable_block_name`` is the empty string |
| 950 | +
|
| 951 | + `KeyError` |
| 952 | + If no variable block of name ``variable_block_name`` exists in the |
| 953 | + dictionary |
| 954 | + """ |
| 955 | + self._set_rule( |
| 956 | + rule, variable_block_name, "variable_block_name", "get_variable_block" |
| 957 | + ) |
| 958 | + |
| 959 | + def get_variable_rule(self, variable_name): |
| 960 | + """Gets `Rule` from a specified variable |
| 961 | +
|
| 962 | + Parameters |
| 963 | + ---------- |
| 964 | + variable_name : str |
| 965 | + Name of the variable the rule is set on. |
| 966 | +
|
| 967 | + Raises |
| 968 | + ------ |
| 969 | + `TypeError` |
| 970 | + If ``variable_name`` is not of type `str` |
| 971 | +
|
| 972 | + `ValueError` |
| 973 | + If ``variable_name`` is the empty string |
| 974 | +
|
| 975 | + `KeyError` |
| 976 | + If no variable of name ``variable_name`` exists in the dictionary |
| 977 | + """ |
| 978 | + self._get_rule(variable_name, "variable_name", "get_variable") |
| 979 | + |
| 980 | + def get_variable_block_rule(self, variable_block_name): |
| 981 | + """Gets `Rule` from a specified variable block |
| 982 | +
|
| 983 | + Parameters |
| 984 | + ---------- |
| 985 | + variable_block_name : str |
| 986 | + Name of the variable block_the rule is set on. |
| 987 | +
|
| 988 | + Raises |
| 989 | + ------ |
| 990 | + `TypeError` |
| 991 | + If ``variable_block_name`` is not of type `str` |
| 992 | +
|
| 993 | + `ValueError` |
| 994 | + If ``variable_block_name`` is the empty string |
| 995 | +
|
| 996 | + `KeyError` |
| 997 | + If no variable block of name ``variable_block_name`` exists in the |
| 998 | + dictionary |
| 999 | + """ |
| 1000 | + self._get_rule(variable_block_name, "variable_block_name", "get_variable_block") |
| 1001 | + |
883 | 1002 | def is_key_variable(self, variable): |
884 | 1003 | """Returns ``True`` if a variable belongs to this dictionary's key |
885 | 1004 |
|
@@ -978,8 +1097,10 @@ class Variable: |
978 | 1097 | rule : str |
979 | 1098 | Derivation rule or external table reference. Set to "" if there is no |
980 | 1099 | rule associated to this variable. Examples: |
| 1100 | +
|
981 | 1101 | - standard rule: "Sum(Var1, Var2)" |
982 | 1102 | - reference rule: "[TableName]" |
| 1103 | +
|
983 | 1104 | variable_block : `VariableBlock` |
984 | 1105 | Block to which the variable belongs. Not set if the variable does not belong to |
985 | 1106 | a block. |
@@ -1386,6 +1507,134 @@ def write(self, writer): |
1386 | 1507 | writer.writeln("") |
1387 | 1508 |
|
1388 | 1509 |
|
| 1510 | +class Rule: |
| 1511 | + """A rule of a variable in a Khiops dictionary |
| 1512 | +
|
| 1513 | + Parameters |
| 1514 | + ---------- |
| 1515 | + name : str |
| 1516 | + Name of the rule. |
| 1517 | + It is intepreted as the verbatim representation of an entire rule if and |
| 1518 | + only if: |
| 1519 | +
|
| 1520 | + - it starts with an UpperCamelCase string, followed by a |
| 1521 | + parenthesized block (...) |
| 1522 | + - ``operands`` is empty |
| 1523 | +
|
| 1524 | + It is intepreted as a reference rule if and only if: |
| 1525 | +
|
| 1526 | + - the first condition above does *not* apply |
| 1527 | + - the second condition above applies |
| 1528 | +
|
| 1529 | + operands : tuple of operands |
| 1530 | + Each operand can have one of the following types: |
| 1531 | +
|
| 1532 | + - str |
| 1533 | + - int |
| 1534 | + - float |
| 1535 | + - ``Variable`` |
| 1536 | + - ``Rule`` |
| 1537 | +
|
| 1538 | + If no operand is specified, then the rule is: |
| 1539 | +
|
| 1540 | + - a standard rule if ``name`` is the verbatim representation of an |
| 1541 | + entire rule |
| 1542 | + - a reference rule if ``name`` does not satisfy the condition above |
| 1543 | +
|
| 1544 | + Attributes |
| 1545 | + ---------- |
| 1546 | + name : str |
| 1547 | + Name of the rule. |
| 1548 | + operands : tuple of operands |
| 1549 | + Each operand has one of the following types: |
| 1550 | +
|
| 1551 | + - str |
| 1552 | + - int |
| 1553 | + - float |
| 1554 | + - ``Variable`` |
| 1555 | + - ``Rule`` |
| 1556 | + """ |
| 1557 | + |
| 1558 | + def __init__(self, name, *operands): |
| 1559 | + """See class docstring""" |
| 1560 | + |
| 1561 | + # Check the types of the parameters |
| 1562 | + if not isinstance(name, str): |
| 1563 | + raise TypeError(type_error_message("name", name, str)) |
| 1564 | + for operand in operands: |
| 1565 | + if not isinstance(operand, (str, int, float, Variable, Rule)): |
| 1566 | + raise TypeError( |
| 1567 | + type_error_message( |
| 1568 | + f"Operand '{operand}'", operand, str, int, float, Variable, Rule |
| 1569 | + ) |
| 1570 | + ) |
| 1571 | + |
| 1572 | + # name must not be empty |
| 1573 | + if not name: |
| 1574 | + raise ValueError(f"'name' must be a non-empty string") |
| 1575 | + |
| 1576 | + self.name = name |
| 1577 | + self.operands = operands |
| 1578 | + |
| 1579 | + def __repr__(self): |
| 1580 | + stream = io.BytesIO() |
| 1581 | + writer = KhiopsOutputWriter(stream) |
| 1582 | + self.write(writer) |
| 1583 | + return str(stream.getvalue(), encoding="utf8", errors="replace") |
| 1584 | + |
| 1585 | + def copy(self): |
| 1586 | + """Copies this rule instance |
| 1587 | +
|
| 1588 | + Returns |
| 1589 | + ------- |
| 1590 | + `Rule` |
| 1591 | + A copy of this instance |
| 1592 | + """ |
| 1593 | + return Rule(self.name, *self.operands) |
| 1594 | + |
| 1595 | + def write(self, writer): |
| 1596 | + """Writes the rule to a file writer in the ``.kdic`` format |
| 1597 | +
|
| 1598 | + Parameters |
| 1599 | + ---------- |
| 1600 | + writer : `.KhiopsOutputWriter` |
| 1601 | + Output writer. |
| 1602 | + """ |
| 1603 | + # Check file object type |
| 1604 | + if not isinstance(writer, KhiopsOutputWriter): |
| 1605 | + raise TypeError(type_error_message("writer", writer, KhiopsOutputWriter)) |
| 1606 | + |
| 1607 | + # Write standard rule |
| 1608 | + rule_regex = re.compile(r"^[A-Z]([a-zA-Z]*)\(.*\)") |
| 1609 | + if self.operands: |
| 1610 | + n_operands = len(self.operands) |
| 1611 | + writer.write(f"{_format_name(self.name)}(") |
| 1612 | + for i, operand in enumerate(self.operands): |
| 1613 | + # Write operand, according to its type |
| 1614 | + # Variable operands have their name written only |
| 1615 | + if isinstance(operand, Rule): |
| 1616 | + operand.write(writer) |
| 1617 | + elif isinstance(operand, Variable): |
| 1618 | + writer.write(_format_name(operand.name)) |
| 1619 | + elif isinstance(operand, str): |
| 1620 | + writer.write(f'"{operand}"') |
| 1621 | + elif math.isnan(float(operand)): |
| 1622 | + writer.write("#Missing") |
| 1623 | + else: |
| 1624 | + writer.write(f"{operand}") |
| 1625 | + if i < n_operands - 1: |
| 1626 | + writer.write(", ") |
| 1627 | + writer.write(")") |
| 1628 | + |
| 1629 | + # Write verbatim-given rule |
| 1630 | + elif rule_regex.match(self.name): |
| 1631 | + writer.write(self.name) |
| 1632 | + |
| 1633 | + # Write rule as a reference rule |
| 1634 | + else: |
| 1635 | + writer.write(f"[{self.name}]") |
| 1636 | + |
| 1637 | + |
1389 | 1638 | class MetaData: |
1390 | 1639 | """A metadata container for a dictionary, a variable or variable block |
1391 | 1640 |
|
|
0 commit comments