Skip to content

helpers

songbirdcli.helpers

helpers.py module for helping with parsing inputs

get_input(prompt, out_type=None, quit_str='q', choices=None)

Given a prompt, get input from stdio and perform basic type validation

Parameters:

  • prompt (str) –

    the prompt to use

  • out_type (type, default: None ) –

    expected type for input. Defaults to None.

  • quit_str (str, default: 'q' ) –

    a character to signify quitting from the input gatherer. Defaults to "q".

  • choices (Optional[List], default: None ) –

    valid character options to parse as input. Defaults to None.

Returns:

  • Optional[Any]

    Optional[Any]: the typed user input, None if error occured, quit_str if use quit

Source code in songbirdcli/helpers.py
156
157
158
159
160
161
162
163
164
165
166
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
def get_input(
    prompt: str, out_type=None, quit_str="q", choices: Optional[List] = None
) -> Optional[Any]:
    """Given a prompt, get input from stdio and perform basic type validation

    Args:
        prompt (str): the prompt to use
        out_type (type, optional): expected type for input. Defaults to None.
        quit_str (str, optional): a character to signify quitting from the input gatherer. Defaults to "q".
        choices (Optional[List], optional): valid character options to parse as input. Defaults to None.

    Returns:
        Optional[Any]: the typed user input, None if error occured, quit_str if use quit
    """
    while True:
        built_prompt = prompt
        if choices is not None:
            built_prompt += f" , choices=({choices})"
        built_prompt += " ['q' quits]: "
        inp = input(built_prompt)
        if inp == quit_str:
            return quit_str

        # initialize type to be empty by default
        typed = None
        if out_type is not None:
            try:
                typed = out_type(inp)

            except ValueError as e:
                logger.error(
                    "Invalid type received. Try again, inputting an '{out_type}'"
                )
        else:
            typed = inp

        if choices is None:
            return typed

        if typed not in choices:
            logger.error(f"You must input one of {choices}")
        else:
            return typed

get_input_list(prompt, sep, out_type=int, quit_str='q')

Take an input prompt, and generate a typed list from it, validating against the given input type.

Parameters:

  • prompt (str) –

    the prompt to display to the user

  • sep (str) –

    the expected separator in the input list

  • out_type (_type_, default: int ) –

    The expected data type. Defaults to int.

Raises:

  • ValueError

    If the input does not match the given type.

Returns:

  • List[int]

    Optional[List[int]]: the typed list, or the quit_str value if user quits

Source code in songbirdcli/helpers.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
def get_input_list(prompt: str, sep: str, out_type=int, quit_str="q") -> List[int]:
    """Take an input prompt, and generate a typed list from it, validating against the given input type.

    Args:
        prompt (str): the prompt to display to the user
        sep (str): the expected separator in the input list
        out_type (_type_, optional): The expected data type. Defaults to int.

    Raises:
        ValueError: If the input does not match the given type.

    Returns:
        Optional[List[int]]: the typed list, or the quit_str value if user quits
    """
    while True:
        inp = input(prompt + f" ['{quit_str}' quits]: ")
        if inp == quit_str:
            return quit_str

        str_list = inp.split(sep)
        typed_list = []
        # by default fail type check
        type_check_passed = False
        for item in str_list:
            type_check_passed = True
            try:
                typed_inp = out_type(item)
            except ValueError as e:
                logger.error(
                    f"Invalid input {inp} recieved, expected list with types: {out_type}, separated by '{sep}'"
                )
                type_check_passed = False
                break

            typed_list.append(out_type(item))

        # only exit loop if the type check has passed
        if type_check_passed:
            break
    return typed_list

launch_album_mode(artist_album_string='', quit_str='q')

launch album mode: collect album and songs to download

Parameters:

  • artist_album_string (str, default: '' ) –

    the string used when querying itunes api. Defaults to ''.

  • quit_str (str, default: 'q' ) –

    the str to signify user quitting the process. Defaults to "q".

Returns:

  • Optional[Union[List[ItunesApiSongModel], str]]

    Optional[Union[List[itunes_api.ItunesApiSongModel],str]]: the list of song properties gathered from the search, None if error occured, quit_str if user quit

Source code in songbirdcli/helpers.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
def launch_album_mode(
    artist_album_string="", quit_str="q"
) -> Optional[Union[List[itunes_api.ItunesApiSongModel], str]]:
    """launch album mode: collect album and songs to download

    Args:
        artist_album_string (str, optional): the string used when querying itunes api. Defaults to ''.
        quit_str (str, optional): the str to signify user quitting the process. Defaults to "q".

    Returns:
        Optional[Union[List[itunes_api.ItunesApiSongModel],str]]: the list of song properties gathered from the search, None if error occured, quit_str if user quit

    """
    while True:
        album_props = parse_itunes_search_api(
            search_variable=artist_album_string,
            mode=modes.Modes.ALBUM,
            lookup=False,
            # disable no selection here, user must select album properties.
            no_selection_value=None,
        )
        # check if user quit
        if album_props == quit_str:
            return quit_str

        if album_props is None:
            return None

        # get the song matching the album metadata
        songs_in_album_props = itunes.query_api(
            search_variable=album_props.collectionId,  # get list of songs for chosen album
            limit=album_props.trackCount,
            mode=modes.Modes.SONG,
            lookup=True,
        )
        # api error occurred
        if songs_in_album_props == None:
            return None
        # no songs found for album
        if len(songs_in_album_props) == 0:
            logger.error(
                "Sorry. Cant seem to find any details for this album in itunes api! Please select a different album."
            )
            return None

        songs_in_album_props = remove_songs_selected(
            song_properties_list=songs_in_album_props, quit_str=quit_str
        )
        if songs_in_album_props == quit_str:
            return quit_str
        if songs_in_album_props is None:
            return None

        return songs_in_album_props

parse_itunes_search_api(search_variable, mode, limit=20, lookup=False, no_selection_value=-1, quit_str='q')

perform a query of the items api, allowing user to select an item from the returned list of options

Parameters:

  • search_variable (str) –

    the value for the query

  • mode (Modes) –

    the mode to run

  • limit (int, default: 20 ) –

    number of results. Defaults to 20.

  • lookup (bool, default: False ) –

    whether to enable 'lookup' mode in itunes api. Defaults to False.

  • no_selection_value (int, default: -1 ) –

    whether to set an option for 'no selection' from stdin

  • quit_str (str, default: 'q' ) –

    str value used to determine whether to quit the song selection process

Returns:

  • Optional[Union[bool, ItunesApiSongModel, ItunesApiAlbumKeys]]

    Optional[Union[itunes_api.ItunesApiSongModel]]: returns the selected song properties, a bool=False if use continues without selection, None if error occurred, or quit_str if user quit.

Source code in songbirdcli/helpers.py
 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
def parse_itunes_search_api(
    search_variable: str,
    mode: modes.Modes,
    limit: int = 20,
    lookup: bool = False,
    no_selection_value: Optional[int] = -1,
    quit_str: str = "q",
) -> Optional[
    Union[bool, itunes_api.ItunesApiSongModel, itunes_api.ItunesApiAlbumKeys]
]:
    """perform a query of the items api, allowing user to select
    an item from the returned list of options

    Args:
        search_variable (str): the value for the query
        mode (modes.Modes): the mode to run
        limit (int, optional): number of results. Defaults to 20.
        lookup (bool, optional): whether to enable 'lookup' mode in itunes api. Defaults to False.
        no_selection_value (int, optional): whether to set an option for 'no selection' from stdin
        quit_str (str, optional): str value used to determine whether to quit the song selection process

    Returns:
        Optional[Union[itunes_api.ItunesApiSongModel]]: returns the selected song properties, a bool=False if use continues without selection, None if error occurred, or quit_str if user quit.
    """
    parsed_results_list = itunes.query_api(search_variable, limit, mode, lookup=lookup)

    # Present results to user
    common.pretty_list_of_basemodel_printer(parsed_results_list)
    logger.info("Searched for: %s" % (search_variable))
    # Only one item can be selected
    user_selection = select_items_from_list(
        prompt="Select the number for the properties you want",
        lyst=parsed_results_list,
        n_choices=1,
        quit_str=quit_str,
        no_selection_value=no_selection_value,
    )

    # user has quit
    if user_selection == quit_str:
        logger.info("Quitting.")
        return quit_str
    if len(user_selection) == 0:
        logger.info("Continuing without properties.")
        return False

    print(f"Selected item: ")
    for k, v in user_selection[0].model_dump().items():
        print(" - %s : %s" % (k, v))

    return user_selection[0]

remove_songs_selected(song_properties_list, quit_str='q')

Given a list of songs properties, allow the user to remove via stdio

Parameters:

  • song_properties_list (Any) –

    the list of song properties

Returns:

  • Optional[List]

    Optional[List]: the properties list after selection

Source code in songbirdcli/helpers.py
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
152
153
def remove_songs_selected(song_properties_list, quit_str: str = "q") -> Optional[List]:
    """Given a list of songs properties, allow the user to remove
    via stdio

    Args:
        song_properties_list (Any): the list of song properties

    Returns:
        Optional[List]: the properties list after selection
    """
    common.pretty_list_of_basemodel_printer(song_properties_list)
    input_string = "Enter song id's (1 4 5 etc.) you dont want from this album"
    user_input = select_items_from_list(
        input_string,
        song_properties_list,
        n_choices=len(song_properties_list) - 1,
        sep=" ",
        quit_str=quit_str,
        opposite=True,
        no_selection_value=-1,
    )
    # user has quit
    if user_input == quit_str:
        return quit_str
    if user_input == None:
        return None
    # user selects all songs
    if user_input == []:
        return song_properties_list
    # otherwise, user gets the processed list with their items removed
    return user_input

select_items_from_list(prompt, lyst, n_choices, sep=None, quit_str='q', opposite=False, no_selection_value=None, return_value=True)

Input validation against a list.

Parameters:

  • prompt (str) –

    prompt to display to user

  • sep ( (str, default: None ) –

    the separator for the input

  • lyst (List) –

    the list to perform input validation against

  • n_choices (int) –

    the number of choices allowed

  • quit_str (str, default: 'q' ) –

    The string to cancel validation and exit. Defaults to "q".

  • opposite (bool, default: False ) –

    return everything BUT the user selection

  • no_selection_value (int, default: None ) –

    value to indicate no selection wanted from the user

  • return_value (bool, default: True ) –

    if true, return the value, otherwise return the indicies of selections

Returns:

  • List

    Optiona[List]: quit_str if user quits, [] if user selects nothing, otherwise a list of the users selections are returned.

Source code in songbirdcli/helpers.py
243
244
245
246
247
248
249
250
251
252
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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
def select_items_from_list(
    prompt: str,
    lyst: List,
    n_choices: int,
    sep: Optional[str] = None,
    quit_str: str = "q",
    opposite: bool = False,
    no_selection_value: int = None,
    return_value: bool = True,
) -> List:
    """Input validation against a list.

    Args:
        prompt (str): prompt to display to user
        sep    (str): the separator for the input
        lyst (List): the list to perform input validation against
        n_choices (int): the number of choices allowed
        quit_str (str): The string to cancel validation and exit. Defaults to "q".
        opposite (bool): return everything BUT the user selection
        no_selection_value (int, optional): value to indicate no selection wanted from the user
        return_value (bool): if true, return the value, otherwise return the indicies of selections

    Returns:
        Optiona[List]: quit_str if user quits, [] if user selects nothing, otherwise a list of the users selections are returned.
    """
    tries = 0
    low = 0
    high = len(lyst) - 1
    # provide useful ranges to user
    if high == -1:
        range_display = ""
    elif high == 0:
        range_display = f" (choose {high})"
    else:
        range_display = f" (choose {low} to {high}) "
    # fill appropriate prompt based on opposite feature
    if opposite:
        opposite_prompt = " select all"
    else:
        opposite_prompt = " continue without selection"

    if no_selection_value is None:
        no_selection_prompt = ""
    else:
        no_selection_prompt = f"[{no_selection_value} {opposite_prompt}]"

    while True:
        tries += 1
        if tries > 5:
            logger.info(f"Wtf man. {tries} tries so far? Just get it right!")
        # get user input
        inp = get_input_list(
            prompt + f"{range_display} {no_selection_prompt}",
            sep,
            quit_str=quit_str,
            out_type=int,
        )
        if inp == quit_str:
            return quit_str

        if len(inp) == 0:
            logger.info("You selected nothing.")
            return []
        # verify if the value of the input is the selection value
        if inp[0] == no_selection_value:
            return []
        # if user has entered too many choices, try again!!
        if len(inp) > n_choices:
            logger.error(f"You're only allowed {n_choices} here. Try again.")
            continue
        # user has passed validation, now check the values are reasonable

        boundary_check_passed = True
        for val in inp:
            if val > high or val < low:
                logger.error(
                    f"Sorry, the value {val} is out of bounds. Please enter within the interval [{low}, {high}]"
                )
                boundary_check_passed = False
        if not boundary_check_passed:
            continue

        # if user gets passed all of above, actually select values or indices at this point, and return
        if not opposite:
            result = []
            for val in inp:
                if return_value:
                    result.append(lyst[val])
                else:
                    result.append(val)
        else:
            result = []
            for idx in range(len(lyst)):
                if idx not in inp:
                    if return_value:
                        result.append(lyst[idx])
                    else:
                        result.append(idx)
        return result