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