Building nested dictionaries to dump it to .json later I am looking for some way to improve my code. It works but it doesn't look good and I am almost sure that lot of Best Practices rules are violated.

The save_session() function, creates dictionary with other dictionaries nested in. At the end the dictionary is dump to .json file.

I will appreciate any suggestions and advice for improvement of this code.

For better understanding the code of save_session():

workouts_instances is the list of current WorkoutPlan instances,

WorkoutPlan stores instances of Training and Exercise,

Training stores instances of Exercise,

class WorkoutPlan:
    def __init__(self, name, trainings=list(), exercises=list()):
        self.name = name
        self.trainings = trainings
        self.exercises = exercises
    ...

class Training:
    def __init__(self, name, exercises=list()):
        self.name = name
        self.exercises = exercises
    ...

class Exercise:
    def __init__(self, name, details=dict()):
        self.name = name
        self.details = details
    ...


Here is the code of save_session():

def save_session():
    """Exports current data to .json file."""
    data_to_save = {}

    # add workout plans
    for workout in workouts_instances:
        data_to_save[workout.name] = {}

        # add trainings
        if workout.trainings:
            data_to_save[workout.name]["trainings"] = {}

            for training in workout.trainings:
                data_to_save[workout.name]["trainings"][training.name] = {}

                # add training exercises
                if training.exercises:
                    data_to_save[workout.name]["trainings"][training.name]['exercises'] = {
                        exercise.name: {} for exercise in training.exercises
                    }

                    for exercise in training.exercises:

                        if exercise.details:
                            data_to_save[workout.name]["trainings"][training.name]['exercises'][exercise.name][
                                'details'] = {detail: value for detail, value in
                                              exercise.details.items()}

        # add exercises
        if workout.exercises:
            data_to_save[workout.name]["exercises"] = {}

            exercises_to_save = data_to_save[workout.name]["exercises"]

            for exercise in workout.exercises:
                exercises_to_save[exercise.name] = {}

                if exercise.details:
                    details = exercise.details
                    exercises_to_save[exercise.name]['details'] = {detail: value for detail, value in details.items()}

    with open(WORKOUTS_FILE, 'w') as json_file:
        json.dump(data_to_save, json_file, indent=4)


and here is the example of output, data stored in .json file:

{
    "FBV - Full Body Workout": {
        "trainings": {
            "Training A": {
                "exercises": {
                    "squats": {
                        "details": {
                            "description": "squats with barbell",
                            "series": 4,
                            "repeats": 4,
                            "load": 70
                        }
                    }
                }
            }
        },
        "exercises": {
            "some exercise name": {
                "details": {
                    "description": "some description",
                    "series": 5,
                    "repeats": 5,
                    "load": 60
                }
            },
            "bench press - wide": {
                "details": {
                    "description": "bench press with wide grip",
                    "series": 5,
                    "repeats": 5,
                    "load": 60
                }
            }
        }
    }
}
Some suggestions:


Do not use mutable default arguments, they will mess with your subsequent calls. There are some very rare cases where mutable default arguments make sense, but this does not look like one of those.
Split generation of data from saving that data to a file. That way either method can be reused. If you also pull out the file handle to a main method you can dependency inject the file handle and test the whole functionality.
details have the same pattern everywhere, so they should also be a class or should be part of Exercise.
Like most Python code this could benefit from type annotations using MyPy.