def search(query, medium, credentials):
    """Searches MyAnimeList for a [medium] matching the keyword(s) given by query.
    :param query  The keyword(s) to search with.
    :param medium Anime or manga (tokens.Medium.ANIME or tokens.Medium.MANGA).
    :return A list of all items that are of type [medium] and match the
             given keywords, or, an empty list if none matched.
    :raise ValueError For bad arguments.
    """
    helpers.check_creds(credentials, header)
    if len(query) == 0:
        raise ValueError(constants.INVALID_EMPTY_QUERY)
    api_query = helpers.get_query_url(medium, query)
    if api_query is None:
        raise ValueError(constants.INVALID_MEDIUM)
    search_resp = requests.get(api_query, auth=credentials, headers=header)
    if search_resp is None or search_resp.status_code == 204: # is there a better way to do this...
        return []
    query_soup = BeautifulSoup(search_resp.text, 'lxml')
    if medium == tokens.Medium.ANIME:
        entries = query_soup.anime
        if entries is None:
            return helpers.reschedule(search, constants.DEFAULT_WAIT_SECS, query,
                                      medium, credentials)

        return [objects.Anime(entry) for entry in entries.findAll('entry')]
    elif medium == tokens.Medium.MANGA:
        entries = query_soup.manga
        if entries is None:
            return helpers.reschedule(search, constants.DEFAULT_WAIT_SECS, query,
                                      medium)
        return [objects.Manga(entry) for entry in entries.findAll('entry')]