Skip to content

quast_decisiontree.core.query

quast_decisiontree.core.query

logger module-attribute

logger = logging.getLogger('dt_logger')

standard_abort_message module-attribute

standard_abort_message = (
    "Do you want to exit the decisiontree?"
)

Query

Bases: ABC

parent class for all user inputs

Source code in src/quast_decisiontree/core/query.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
class Query(ABC):
    """parent class for all user inputs"""

    def input(self, mode: str | None = None) -> Any:
        """checks whether the query should be executed and collects input if that is the case"""
        if mode is None:
            mode = os.environ.get("QDT_MODE")
        return self.input_or_def(mode=mode)

    @abstractmethod
    def input_raw(self) -> Any:
        """gets correct user input and returns it"""

    def _init_default(self, default):
        if default == "":
            self.has_default = False
            self.default = None
        else:
            self.has_default = True
            self.default = default

    def set_default(self, default):
        """sets the default value for the query. For convenience, it doesn't change anything
        if None is passed as default argument
        """
        if default is None:
            return
        self.has_default = True
        self.default = default

    def input_or_def(self, mode: str | None = None, prefix_text_if_def: str = ""):
        allowed_modes = ["auto", "accept", "confirm", "manual"]
        if mode not in allowed_modes:
            mode = "confirm"

        collect_input = mode in ["confirm", "manual"]

        try:
            if not self.has_default:
                collect_input = True
        except AttributeError:
            collect_input = True

        if collect_input:
            return self.input_raw()
        else:
            print(prefix_text_if_def + f"Default value {self.default}")
            return self.default

    def print_def(self):
        """prints the default value if there is one"""
        try:
            if self.has_default:
                print(f"Default: {self.default}")
        except AttributeError:
            pass

    def replace_with_def(self, user_val):
        """returns the input if it's not the empty string or no default is set, and the
        default value otherwise
        """
        try:
            if user_val == "" and self.has_default:
                return self.default
        except AttributeError:
            pass
        return user_val

    def input_w_def(self, replace_default=True) -> str:
        """Requests input while distinguishing whether a default value exists or not. The behavior
        is as follows:

        if a default value exists and is not None:
            - empty input string: return default value if replace_default is True, and the
                empty string otherwise
            - input "exit": raise a QueryAbortError
            - any other input is directly returned

        if no default value exists or default value is None:
            - input "exit": raise a QueryAbortError
            - any other input is directly returned

        Args:
            replace_default (bool, optional): If a default value exists, determines whether
                or not the default value or the empty string will be returned. This is useful
                if you need to distinguish from outside the function whether the user provided
                default input (empty string) or manually typed the same value as default.
                Defaults to True.

        Returns:
            str: the input by the user, or default value
        """
        try:
            if self.has_default:
                user_val = _input(
                    "Type an option, 'exit' to abort, or hit enter to accept the default: "
                )
                check_exit(user_val)
                if replace_default:
                    return self.replace_with_def(user_val)
                else:
                    return user_val
        except AttributeError:
            pass
        user_val = _input("Type an option or 'exit' to abort: ")
        check_exit(user_val)
        return user_val

    def input_or_finish(self):
        """raises an InputFinishedTrigger if an empty string is entered"""
        user_val = _input("Type an option, 'exit' to abort, or hit enter to finish your input: ")
        check_exit(user_val)
        if user_val == "":
            raise InputFinishedTrigger
        else:
            return user_val

input

input(mode=None)

checks whether the query should be executed and collects input if that is the case

Source code in src/quast_decisiontree/core/query.py
39
40
41
42
43
def input(self, mode: str | None = None) -> Any:
    """checks whether the query should be executed and collects input if that is the case"""
    if mode is None:
        mode = os.environ.get("QDT_MODE")
    return self.input_or_def(mode=mode)

input_raw abstractmethod

input_raw()

gets correct user input and returns it

Source code in src/quast_decisiontree/core/query.py
45
46
47
@abstractmethod
def input_raw(self) -> Any:
    """gets correct user input and returns it"""

set_default

set_default(default)

sets the default value for the query. For convenience, it doesn't change anything if None is passed as default argument

Source code in src/quast_decisiontree/core/query.py
57
58
59
60
61
62
63
64
def set_default(self, default):
    """sets the default value for the query. For convenience, it doesn't change anything
    if None is passed as default argument
    """
    if default is None:
        return
    self.has_default = True
    self.default = default

input_or_def

input_or_def(mode=None, prefix_text_if_def='')
Source code in src/quast_decisiontree/core/query.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def input_or_def(self, mode: str | None = None, prefix_text_if_def: str = ""):
    allowed_modes = ["auto", "accept", "confirm", "manual"]
    if mode not in allowed_modes:
        mode = "confirm"

    collect_input = mode in ["confirm", "manual"]

    try:
        if not self.has_default:
            collect_input = True
    except AttributeError:
        collect_input = True

    if collect_input:
        return self.input_raw()
    else:
        print(prefix_text_if_def + f"Default value {self.default}")
        return self.default

print_def

print_def()

prints the default value if there is one

Source code in src/quast_decisiontree/core/query.py
85
86
87
88
89
90
91
def print_def(self):
    """prints the default value if there is one"""
    try:
        if self.has_default:
            print(f"Default: {self.default}")
    except AttributeError:
        pass

replace_with_def

replace_with_def(user_val)

returns the input if it's not the empty string or no default is set, and the default value otherwise

Source code in src/quast_decisiontree/core/query.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
def replace_with_def(self, user_val):
    """returns the input if it's not the empty string or no default is set, and the
    default value otherwise
    """
    try:
        if user_val == "" and self.has_default:
            return self.default
    except AttributeError:
        pass
    return user_val

input_w_def

input_w_def(replace_default=True)

Requests input while distinguishing whether a default value exists or not. The behavior is as follows:

if a default value exists and is not None
  • empty input string: return default value if replace_default is True, and the empty string otherwise
  • input "exit": raise a QueryAbortError
  • any other input is directly returned
if no default value exists or default value is None
  • input "exit": raise a QueryAbortError
  • any other input is directly returned

Parameters:

Name Type Description Default
replace_default bool

If a default value exists, determines whether or not the default value or the empty string will be returned. This is useful if you need to distinguish from outside the function whether the user provided default input (empty string) or manually typed the same value as default. Defaults to True.

True

Returns:

Name Type Description
str str

the input by the user, or default value

Source code in src/quast_decisiontree/core/query.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def input_w_def(self, replace_default=True) -> str:
    """Requests input while distinguishing whether a default value exists or not. The behavior
    is as follows:

    if a default value exists and is not None:
        - empty input string: return default value if replace_default is True, and the
            empty string otherwise
        - input "exit": raise a QueryAbortError
        - any other input is directly returned

    if no default value exists or default value is None:
        - input "exit": raise a QueryAbortError
        - any other input is directly returned

    Args:
        replace_default (bool, optional): If a default value exists, determines whether
            or not the default value or the empty string will be returned. This is useful
            if you need to distinguish from outside the function whether the user provided
            default input (empty string) or manually typed the same value as default.
            Defaults to True.

    Returns:
        str: the input by the user, or default value
    """
    try:
        if self.has_default:
            user_val = _input(
                "Type an option, 'exit' to abort, or hit enter to accept the default: "
            )
            check_exit(user_val)
            if replace_default:
                return self.replace_with_def(user_val)
            else:
                return user_val
    except AttributeError:
        pass
    user_val = _input("Type an option or 'exit' to abort: ")
    check_exit(user_val)
    return user_val

input_or_finish

input_or_finish()

raises an InputFinishedTrigger if an empty string is entered

Source code in src/quast_decisiontree/core/query.py
144
145
146
147
148
149
150
151
def input_or_finish(self):
    """raises an InputFinishedTrigger if an empty string is entered"""
    user_val = _input("Type an option, 'exit' to abort, or hit enter to finish your input: ")
    check_exit(user_val)
    if user_val == "":
        raise InputFinishedTrigger
    else:
        return user_val

MultiChoiceQuery

Bases: Query

multiple choice question / menu selection

Source code in src/quast_decisiontree/core/query.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
class MultiChoiceQuery(Query):
    """multiple choice question / menu selection"""

    def __init__(
        self, question: str, answers: dict, name: str = "", default: str | None = ""
    ) -> None:
        """creates a multiple choice query. The user will select one of more options from the
        dictionary provided.

        Args:
            question (str): The question to be asked to the user.
            answers (dict): The possible answers as a dictionary {key : description} where key
                is a short dictionary key the user is expected to type, and description a
                description of the associated option.
            name (str, optional): The name of the query.  Defaults to "".
            default (Optional[str], optional): a possible default value, must be contained in
                answers.keys(). If the user hits enter without typing other input, it will be
                replaced with the default key. Defaults to "".
        """
        self.name = name
        self.options = answers
        self.question = question
        self._init_default(default)

    def input_raw(self) -> Any:
        """presents the user with the options and requests a key while also allowing to abort the
        script
        """
        print(self.question)
        self.print_def()
        print_sep()
        print_query_line("exit", "Abort")
        ind = 0
        keys = []
        for key, val in self.options.items():
            ind += 1
            keys.append(key)
            print_query_line(key=key, val=val, ind=ind)

        user_val = None
        user_val_is_int = False
        while user_val not in list(self.options.keys()):
            user_val = self.input_w_def()
            try:
                if int(user_val) >= 1 and int(user_val) <= ind:
                    user_val_is_int = True
                    break
            except (ValueError, TypeError):
                continue
        print_sep()

        if user_val_is_int:
            return keys[int(user_val) - 1]
        else:
            return user_val

name instance-attribute

name = name

options instance-attribute

options = answers

question instance-attribute

question = question

__init__

__init__(question, answers, name='', default='')

creates a multiple choice query. The user will select one of more options from the dictionary provided.

Parameters:

Name Type Description Default
question str

The question to be asked to the user.

required
answers dict

The possible answers as a dictionary {key : description} where key is a short dictionary key the user is expected to type, and description a description of the associated option.

required
name str

The name of the query. Defaults to "".

''
default Optional[str]

a possible default value, must be contained in answers.keys(). If the user hits enter without typing other input, it will be replaced with the default key. Defaults to "".

''
Source code in src/quast_decisiontree/core/query.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
def __init__(
    self, question: str, answers: dict, name: str = "", default: str | None = ""
) -> None:
    """creates a multiple choice query. The user will select one of more options from the
    dictionary provided.

    Args:
        question (str): The question to be asked to the user.
        answers (dict): The possible answers as a dictionary {key : description} where key
            is a short dictionary key the user is expected to type, and description a
            description of the associated option.
        name (str, optional): The name of the query.  Defaults to "".
        default (Optional[str], optional): a possible default value, must be contained in
            answers.keys(). If the user hits enter without typing other input, it will be
            replaced with the default key. Defaults to "".
    """
    self.name = name
    self.options = answers
    self.question = question
    self._init_default(default)

input_raw

input_raw()

presents the user with the options and requests a key while also allowing to abort the script

Source code in src/quast_decisiontree/core/query.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def input_raw(self) -> Any:
    """presents the user with the options and requests a key while also allowing to abort the
    script
    """
    print(self.question)
    self.print_def()
    print_sep()
    print_query_line("exit", "Abort")
    ind = 0
    keys = []
    for key, val in self.options.items():
        ind += 1
        keys.append(key)
        print_query_line(key=key, val=val, ind=ind)

    user_val = None
    user_val_is_int = False
    while user_val not in list(self.options.keys()):
        user_val = self.input_w_def()
        try:
            if int(user_val) >= 1 and int(user_val) <= ind:
                user_val_is_int = True
                break
        except (ValueError, TypeError):
            continue
    print_sep()

    if user_val_is_int:
        return keys[int(user_val) - 1]
    else:
        return user_val

StringQuery

Bases: Query

string input

Source code in src/quast_decisiontree/core/query.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
class StringQuery(Query):
    """string input"""

    def __init__(self, question: str, name: str = "", default: str = "") -> None:
        """creates a query asking for input with a possible default value.

        Args:
            question (str): the question to print to the user
            name (str, optional): A possible name of the query. Defaults to "".
            default (str, optional): the default string provided if the user hits enter.
            Defaults to "".
        """
        self.name = name
        self.question = question
        self._init_default(default)

    def append_to_question(self, text: str, sep: str = "\n") -> None:
        self.question = self.question + sep + text

    def input_raw(self) -> str:
        print(self.question)
        self.print_def()
        print_sep()
        user_val = self.input_w_def()
        print_sep()

        return user_val

name instance-attribute

name = name

question instance-attribute

question = question

__init__

__init__(question, name='', default='')

creates a query asking for input with a possible default value.

Parameters:

Name Type Description Default
question str

the question to print to the user

required
name str

A possible name of the query. Defaults to "".

''
default str

the default string provided if the user hits enter.

''
Source code in src/quast_decisiontree/core/query.py
227
228
229
230
231
232
233
234
235
236
237
238
def __init__(self, question: str, name: str = "", default: str = "") -> None:
    """creates a query asking for input with a possible default value.

    Args:
        question (str): the question to print to the user
        name (str, optional): A possible name of the query. Defaults to "".
        default (str, optional): the default string provided if the user hits enter.
        Defaults to "".
    """
    self.name = name
    self.question = question
    self._init_default(default)

append_to_question

append_to_question(text, sep='\n')
Source code in src/quast_decisiontree/core/query.py
240
241
def append_to_question(self, text: str, sep: str = "\n") -> None:
    self.question = self.question + sep + text

input_raw

input_raw()
Source code in src/quast_decisiontree/core/query.py
243
244
245
246
247
248
249
250
def input_raw(self) -> str:
    print(self.question)
    self.print_def()
    print_sep()
    user_val = self.input_w_def()
    print_sep()

    return user_val

PathQuery

Bases: StringQuery

input of a file or directory path

Source code in src/quast_decisiontree/core/query.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
class PathQuery(StringQuery):
    """input of a file or directory path"""

    def __init__(
        self, question: str, must_exist: bool = True, name: str = "", default: str = ""
    ) -> None:
        """Creates a special StringQuery asking for a path.

        Args:
            question (str): the question to print to the user
            must_exist (bool, optional): whether the path needs to exists, other paths will be
                rejected. Defaults to True.
            name (str, optional): A possible name of the query. Defaults to "".
            default (str, optional): A possible default path provided if the user hits enter.
                Defaults to "".

        """
        super().__init__(question=question, name=name, default=default)
        self.must_exist = must_exist

    def input_raw(self) -> str:
        print(self.question)
        self.print_def()
        print_sep()
        user_val = None
        while (user_val is None) or (self.must_exist and not os.path.exists(user_val)):
            user_val = self.input_w_def()
            check_exit(user_val)
        print_sep()

        return user_val

must_exist instance-attribute

must_exist = must_exist

__init__

__init__(question, must_exist=True, name='', default='')

Creates a special StringQuery asking for a path.

Parameters:

Name Type Description Default
question str

the question to print to the user

required
must_exist bool

whether the path needs to exists, other paths will be rejected. Defaults to True.

True
name str

A possible name of the query. Defaults to "".

''
default str

A possible default path provided if the user hits enter. Defaults to "".

''
Source code in src/quast_decisiontree/core/query.py
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
def __init__(
    self, question: str, must_exist: bool = True, name: str = "", default: str = ""
) -> None:
    """Creates a special StringQuery asking for a path.

    Args:
        question (str): the question to print to the user
        must_exist (bool, optional): whether the path needs to exists, other paths will be
            rejected. Defaults to True.
        name (str, optional): A possible name of the query. Defaults to "".
        default (str, optional): A possible default path provided if the user hits enter.
            Defaults to "".

    """
    super().__init__(question=question, name=name, default=default)
    self.must_exist = must_exist

input_raw

input_raw()
Source code in src/quast_decisiontree/core/query.py
273
274
275
276
277
278
279
280
281
282
283
def input_raw(self) -> str:
    print(self.question)
    self.print_def()
    print_sep()
    user_val = None
    while (user_val is None) or (self.must_exist and not os.path.exists(user_val)):
        user_val = self.input_w_def()
        check_exit(user_val)
    print_sep()

    return user_val

IntQuery

Bases: StringQuery

int input

Source code in src/quast_decisiontree/core/query.py
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
class IntQuery(StringQuery):
    """int input"""

    def input_raw(self) -> int:
        print(self.question)
        self.print_def()
        print_sep()
        while True:
            user_val = self.input_w_def()
            try:
                user_val = int(user_val)
                break
            except (TypeError, ValueError):
                print("Input couldn't be interpreted as int, try again")
                continue
        print_sep()
        return user_val

input_raw

input_raw()
Source code in src/quast_decisiontree/core/query.py
292
293
294
295
296
297
298
299
300
301
302
303
304
305
def input_raw(self) -> int:
    print(self.question)
    self.print_def()
    print_sep()
    while True:
        user_val = self.input_w_def()
        try:
            user_val = int(user_val)
            break
        except (TypeError, ValueError):
            print("Input couldn't be interpreted as int, try again")
            continue
    print_sep()
    return user_val

FloatQuery

Bases: StringQuery

int input

Source code in src/quast_decisiontree/core/query.py
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
class FloatQuery(StringQuery):
    """int input"""

    def input_raw(self) -> int:
        print(self.question)
        self.print_def()
        print_sep()
        while True:
            user_val = self.input_w_def()
            try:
                user_val = float(user_val)
                break
            except (TypeError, ValueError):
                print("Input couldn't be interpreted as float, try again")
                continue
        print_sep()
        return user_val

input_raw

input_raw()
Source code in src/quast_decisiontree/core/query.py
311
312
313
314
315
316
317
318
319
320
321
322
323
324
def input_raw(self) -> int:
    print(self.question)
    self.print_def()
    print_sep()
    while True:
        user_val = self.input_w_def()
        try:
            user_val = float(user_val)
            break
        except (TypeError, ValueError):
            print("Input couldn't be interpreted as float, try again")
            continue
    print_sep()
    return user_val

QueryTree

tree structure for queries to allow branching and conditional execution

Source code in src/quast_decisiontree/core/query.py
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
class QueryTree:
    """tree structure for queries to allow branching and conditional execution"""

    def __init__(
        self,
        queries: list,
        conditions: list | None = None,
        title: str | None = None,
    ) -> None:
        """constructs a query tree

        Args:
            queries (list): a list of queries (subclass instances of Query)
            conditions (list): a list of conditions of the same length.
                Each condition is a callable condition(answers) where answers is the dict
                of responses collected so far, keyed by query name (unanswered/skipped
                entries are None); None means always-true.
            title (optional, str): The title of the full tree
        """

        self.queries = queries
        self.set_conditions(conditions)
        self.answers = dict(
            zip([query.name for query in self.queries], [None] * len(queries), strict=False)
        )
        self.title = title

    def set_conditions(self, conditions: None | list | tuple) -> None:
        """Sets the per-query conditions.

        Accepted forms (robust to list vs. tuple):
        - None: reset every condition to always-true.
        - a sequence with one entry per query, each callable or None.
        - a targeted (index, condition) pair, identified by an integer first
        element, replacing a single query's condition.
        """
        n = len(self.queries)

        if conditions is None:
            self.conditions = [always_true_if_none(None) for _ in range(n)]
            return

        if self._is_targeted_update(conditions):
            index, condition = conditions
            if not (condition is None or callable(condition)):
                raise ValueError("Individual conditions must be callable or None.")
            if not hasattr(self, "conditions"):
                self.conditions = [always_true_if_none(None) for _ in range(n)]
            self.conditions[index] = always_true_if_none(condition)
            return

        conditions = list(conditions)
        if len(conditions) != n:
            raise ValueError(
                "Length of conditions doesn't match the number of queries in the tree."
            )
        if not all(c is None or callable(c) for c in conditions):
            raise ValueError("Individual conditions must be callable or None.")
        self.conditions = [always_true_if_none(c) for c in conditions]

    @staticmethod
    def _is_targeted_update(conditions) -> bool:
        return (
            isinstance(conditions, tuple | list)
            and len(conditions) == 2
            and isinstance(conditions[0], int)
            and not isinstance(conditions[0], bool)
        )

    def input(self) -> dict:
        """executes the queries in the tree and returns the collected answers.

        Returns:
            dict: answers keyed by query name. Queries whose condition evaluates
                falsy are skipped and keep their pre-seeded ``None``.
        """
        self.answers = dict(
            zip([query.name for query in self.queries], [None] * len(self.queries), strict=False)
        )
        print_sep()
        print(self.title)
        print_sep()
        for query, condition in zip(self.queries, self.conditions, strict=False):
            try:
                run_query = condition(self.answers)
            except Exception:
                logger.error(
                    "Condition for query %r raised; treating as unmet and skipping the query.",
                    query.name,
                    exc_info=True,
                )
                run_query = False
            if run_query:
                self.answers[query.name] = query.input()
        return self.answers

queries instance-attribute

queries = queries

answers instance-attribute

answers = dict(
    zip(
        [(query.name) for query in (self.queries)],
        [None] * len(queries),
        strict=False,
    )
)

title instance-attribute

title = title

__init__

__init__(queries, conditions=None, title=None)

constructs a query tree

Parameters:

Name Type Description Default
queries list

a list of queries (subclass instances of Query)

required
conditions list

a list of conditions of the same length. Each condition is a callable condition(answers) where answers is the dict of responses collected so far, keyed by query name (unanswered/skipped entries are None); None means always-true.

None
title (optional, str)

The title of the full tree

None
Source code in src/quast_decisiontree/core/query.py
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
def __init__(
    self,
    queries: list,
    conditions: list | None = None,
    title: str | None = None,
) -> None:
    """constructs a query tree

    Args:
        queries (list): a list of queries (subclass instances of Query)
        conditions (list): a list of conditions of the same length.
            Each condition is a callable condition(answers) where answers is the dict
            of responses collected so far, keyed by query name (unanswered/skipped
            entries are None); None means always-true.
        title (optional, str): The title of the full tree
    """

    self.queries = queries
    self.set_conditions(conditions)
    self.answers = dict(
        zip([query.name for query in self.queries], [None] * len(queries), strict=False)
    )
    self.title = title

set_conditions

set_conditions(conditions)

Sets the per-query conditions.

Accepted forms (robust to list vs. tuple): - None: reset every condition to always-true. - a sequence with one entry per query, each callable or None. - a targeted (index, condition) pair, identified by an integer first element, replacing a single query's condition.

Source code in src/quast_decisiontree/core/query.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
def set_conditions(self, conditions: None | list | tuple) -> None:
    """Sets the per-query conditions.

    Accepted forms (robust to list vs. tuple):
    - None: reset every condition to always-true.
    - a sequence with one entry per query, each callable or None.
    - a targeted (index, condition) pair, identified by an integer first
    element, replacing a single query's condition.
    """
    n = len(self.queries)

    if conditions is None:
        self.conditions = [always_true_if_none(None) for _ in range(n)]
        return

    if self._is_targeted_update(conditions):
        index, condition = conditions
        if not (condition is None or callable(condition)):
            raise ValueError("Individual conditions must be callable or None.")
        if not hasattr(self, "conditions"):
            self.conditions = [always_true_if_none(None) for _ in range(n)]
        self.conditions[index] = always_true_if_none(condition)
        return

    conditions = list(conditions)
    if len(conditions) != n:
        raise ValueError(
            "Length of conditions doesn't match the number of queries in the tree."
        )
    if not all(c is None or callable(c) for c in conditions):
        raise ValueError("Individual conditions must be callable or None.")
    self.conditions = [always_true_if_none(c) for c in conditions]

input

input()

executes the queries in the tree and returns the collected answers.

Returns:

Name Type Description
dict dict

answers keyed by query name. Queries whose condition evaluates falsy are skipped and keep their pre-seeded None.

Source code in src/quast_decisiontree/core/query.py
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
def input(self) -> dict:
    """executes the queries in the tree and returns the collected answers.

    Returns:
        dict: answers keyed by query name. Queries whose condition evaluates
            falsy are skipped and keep their pre-seeded ``None``.
    """
    self.answers = dict(
        zip([query.name for query in self.queries], [None] * len(self.queries), strict=False)
    )
    print_sep()
    print(self.title)
    print_sep()
    for query, condition in zip(self.queries, self.conditions, strict=False):
        try:
            run_query = condition(self.answers)
        except Exception:
            logger.error(
                "Condition for query %r raised; treating as unmet and skipping the query.",
                query.name,
                exc_info=True,
            )
            run_query = False
        if run_query:
            self.answers[query.name] = query.input()
    return self.answers

HyperParamQuery

Bases: Query

asks for a hyperparameter value

Source code in src/quast_decisiontree/core/query.py
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
class HyperParamQuery(Query):
    """asks for a hyperparameter value"""

    def __init__(self, hyperparameter: HyperParam) -> None:
        super().__init__()
        self.hyperparameter = hyperparameter
        self.name = hyperparameter.name
        self.has_default = self.hyperparameter.has_default
        self.default = self.hyperparameter.default
        self.check_input = self.hyperparameter._check_single_value

    def input_raw(self) -> Any:
        """collects input for the hyperparameter"""
        self.hyperparameter.clear_value()

        print(f"{self.hyperparameter.name:20}: {self.hyperparameter.description}")
        self.print_def()
        if self.hyperparameter.allow_multiple:
            print("This hyperparameter accepts one or more values, enter them one after another.")
        print_sep()
        # first input
        user_val = []
        while True:
            trial_val = self.input_w_def(replace_default=False)
            if trial_val == "":
                defaulted = True
                user_val.append(self.default)
                break
            else:
                defaulted = False
            try:
                if self.check_input(trial_val):
                    user_val.append(self.hyperparameter.type(trial_val))
                    break
                else:
                    print(f"Input value {trial_val} isn't valid, try again.")
            except ValueError:
                print(f"Make sure your input can be cast to {self.hyperparameter.type}")

        if self.hyperparameter.allow_multiple and not defaulted:
            while True:
                try:
                    trial_val = self.input_or_finish()
                except InputFinishedTrigger:
                    break
                try:
                    trial_val = self.hyperparameter.type(trial_val)
                    if self.check_input(trial_val):
                        user_val.append(trial_val)
                except ValueError:
                    print(f"Make sure your input can be cast to {self.hyperparameter.type}")
        print_sep()
        if len(user_val) == 1:
            return user_val[0]
        else:
            return user_val

hyperparameter instance-attribute

hyperparameter = hyperparameter

name instance-attribute

name = hyperparameter.name

has_default instance-attribute

has_default = self.hyperparameter.has_default

default instance-attribute

default = self.hyperparameter.default

check_input instance-attribute

check_input = self.hyperparameter._check_single_value

__init__

__init__(hyperparameter)
Source code in src/quast_decisiontree/core/query.py
434
435
436
437
438
439
440
def __init__(self, hyperparameter: HyperParam) -> None:
    super().__init__()
    self.hyperparameter = hyperparameter
    self.name = hyperparameter.name
    self.has_default = self.hyperparameter.has_default
    self.default = self.hyperparameter.default
    self.check_input = self.hyperparameter._check_single_value

input_raw

input_raw()

collects input for the hyperparameter

Source code in src/quast_decisiontree/core/query.py
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def input_raw(self) -> Any:
    """collects input for the hyperparameter"""
    self.hyperparameter.clear_value()

    print(f"{self.hyperparameter.name:20}: {self.hyperparameter.description}")
    self.print_def()
    if self.hyperparameter.allow_multiple:
        print("This hyperparameter accepts one or more values, enter them one after another.")
    print_sep()
    # first input
    user_val = []
    while True:
        trial_val = self.input_w_def(replace_default=False)
        if trial_val == "":
            defaulted = True
            user_val.append(self.default)
            break
        else:
            defaulted = False
        try:
            if self.check_input(trial_val):
                user_val.append(self.hyperparameter.type(trial_val))
                break
            else:
                print(f"Input value {trial_val} isn't valid, try again.")
        except ValueError:
            print(f"Make sure your input can be cast to {self.hyperparameter.type}")

    if self.hyperparameter.allow_multiple and not defaulted:
        while True:
            try:
                trial_val = self.input_or_finish()
            except InputFinishedTrigger:
                break
            try:
                trial_val = self.hyperparameter.type(trial_val)
                if self.check_input(trial_val):
                    user_val.append(trial_val)
            except ValueError:
                print(f"Make sure your input can be cast to {self.hyperparameter.type}")
    print_sep()
    if len(user_val) == 1:
        return user_val[0]
    else:
        return user_val

print_sep

print_sep(width=SEPARATOR_WIDTH)
Source code in src/quast_decisiontree/core/query.py
32
33
def print_sep(width: int = SEPARATOR_WIDTH) -> None:
    print(separator(width=width))

print_query_line

print_query_line(
    key, val, key_len=QUERY_KEY_WIDTH, ind=None
)

prints the line of a multiple-choice query

Source code in src/quast_decisiontree/core/query.py
154
155
156
157
158
159
def print_query_line(key: Any, val: str, key_len=QUERY_KEY_WIDTH, ind: int | None = None):
    """prints the line of a multiple-choice query"""
    if ind is None:
        print(f"{key:{key_len}}: {val}")
    else:
        print(f"{ind:3} : {key:{key_len}}: {val}")

check_exit

check_exit(user_value)
Source code in src/quast_decisiontree/core/query.py
162
163
164
def check_exit(user_value: Any) -> None:
    if user_value == "exit":
        raise QueryAbortError("Aborted by user input")

always_true_if_none

always_true_if_none(element)
Source code in src/quast_decisiontree/core/query.py
327
328
329
330
331
def always_true_if_none(element):
    if element is None:
        return lambda x: True
    else:
        return element