Random assignment function with caveat
Well, you have your code in a function, but it is still top down. It makes it harder to read, and much harder to improve. 

If you instead break the code down into separate functions you'll have an easier time improving code. You'll also not repeat your self. 



Make use of annotations! 

def assign_chores(chore: list, prev_assignment: dict or None, persons: dict):
    ...


that way you don't have to litter your variable names with clues to what types they might be.



I think your trying to create a deep copy and not a shallow copy: 

chore_list_copy = chore_list


to make a deep copy;

chore_list_copy = chore_list[:]


that way you'll actually get a copy. 



for name in people_data.keys():


is the same as 

for name in people_data:




Instead of working with the objects in people_data i suggest you work with data structures. It becomes easier to manipulate that way. 

The data you care about can be represented with:

chores_dist = [[] for _ in range(len(persons))]


and if previous is summited:

prev_chores = [person["chores"] for person in prev_assigment.values()]


Work with data structures instead. 



Protect your code by:

def main():
    ...

if __name__ == '__main__':
    main()


that way your code don't get executed if you import your code into another file.



Instead of choosing a random chore, you could just shuffle them all beforehand, that way the randomness task is done. 

chores = chores[:]
shuffle(chores)




Another way to call your function then to send in a None

def assign_chores(chore_list, prev_assignment_data, people_data):
    ...

prev_assignment_data = None
    assigned_chores = assign_chores(chore_list, prev_assignment_data,
                                    people_data)


is to define it in your function

def assign_chore(chores: list, persons: dict, prev_assignment: dict=None):
    ...


now you can call it like

people_data = assign_chore(chores, people_data)
people_data = assign_chore(chores, people_data, some_previous_data)




When I broke down your code I did it like this:

from random import shuffle


def assign(chores: list, persons: dict):
    for key, person in enumerate(persons):
        persons[person]["chores"] = chores[key]
    return persons


def get_shuffled_chores(chores: list, persons: dict):
    chores = chores[:]
    shuffle(chores)
    chores_dist = [[] for _ in range(len(persons))]
    for key, i in enumerate(range(len(chores))):
        chores_dist[key % len(persons)] += [chores.pop()]
    return chores_dist


def assign_without_regard(chores: list, persons: dict):
    return assign(get_shuffled_chores(chores, persons), persons)


def is_repetitive(news: list, olds: list):
    for new, old in zip(news, olds):
        if any(chore in old for chore in new):
            return False
    return True


def assign_with_regard(chores: list, prev_assigment: dict, persons: dict):
    prev_chores = [person["chores"] for person in prev_assigment.values()]
    new_chores = get_shuffled_chores(chores, persons)
    return assign(new_chores, persons) \
        if not is_repetitive(new_chores, prev_chores) \
        else assign_chore(chores, prev_assigment, persons)


def assign_chore(chores: list, persons: dict, prev_assignment: dict=None):
    if prev_assignment is None:
        return assign_without_regard(chores, persons)
    return assign_with_regard(chores, prev_assignment, persons)


def main():
    chores = ["hoovering", "dusting", "wash dishes", "tidy garden",
              "clean windows", "empty dishwasher", "dust", "cook"]

    people_data = {"David": {"email": "example@gmail.com",
                             "chores": []},
                   "Mark": {"email": "example@gmail.com",
                            "chores": []},
                   "Anaya": {"email": "example@gmail.com",
                             "chores": []},
                   "Cooper": {"email": "example@gmail.com",
                              "chores": []}
                   }

    people_data = assign_chore(chores, people_data)
    print(people_data)
    print(assign_chore(chores, people_data, people_data))


if __name__ == '__main__':
    main()