Exercise: Away From Raw Dictionaries

Requirement

Up to the past two iterations (Exercise: Refactoring - Extract Both CSV Formats Into Module and Exercise: Convert User Record To JSON And Back), we have represented the Persons as raw dictionaries of the following form:

{
    'id': 2,    # int
    'firstname': 'Jörg',
    'lastname': 'Faschingbauer',
    'birth': '19.6.1966',
}

Now that we know what classes are, and which benefits they bring (Classes And Dictionaries), lets

  • design a class Person

  • re-implement all of the CSV stuff, by creating a parallel set of function that return Person objects instead of those dictionaries

Note

The lovely collections.namedtuple can be used to solve that problem.

Test Code

The following tests cover the required class Person changes.

import userdb_csv


def test_noheader_person(tmpdir):
    with open(tmpdir/'noheader.csv', 'w', encoding='cp1252') as f:
        f.writelines([
            '1;"Jörg;DI";Faschingbauer;19.6.1966\n',
            '2;Caro;Faschingbauer;25.4.1997\n',
            '3;Johanna;Faschingbauer;11.6.1995\n',
            '4;Philipp;Lichtenberger;6.4.1986\n',
            '5;Elizabeth II;Queen;1.1.1900\n',
        ])

    users = list(userdb_csv.read_noheader_person(tmpdir/'noheader.csv'))

    assert type(users[0]) is userdb_csv.Person
    assert users[0].id == 1
    assert users[0].firstname == 'Jörg;DI'
    assert users[0].lastname == 'Faschingbauer'
    assert users[0].birth == '19.6.1966'

    assert type(users[1]) is userdb_csv.Person
    assert users[1].id == 2
    assert users[1].firstname == 'Caro'
    assert users[1].lastname == 'Faschingbauer'
    assert users[1].birth == '25.4.1997'

    assert type(users[2]) is userdb_csv.Person
    assert users[2].id == 3
    assert users[2].firstname == 'Johanna'
    assert users[2].lastname == 'Faschingbauer'
    assert users[2].birth == '11.6.1995'

    assert type(users[3]) is userdb_csv.Person
    assert users[3].id == 4
    assert users[3].firstname == 'Philipp'
    assert users[3].lastname == 'Lichtenberger'
    assert users[3].birth == '6.4.1986'

    assert type(users[4]) is userdb_csv.Person
    assert users[4].id == 5
    assert users[4].firstname == 'Elizabeth II'
    assert users[4].lastname == 'Queen'
    assert users[4].birth == '1.1.1900'

def test_must_not_use_global_variables_as_return_object(tmpdir):
    filename1 = tmpdir/'noheader1.csv'
    with open(filename1, 'w', encoding='cp1252') as f:
        f.writelines([
            '1;"Jörg;DI";Faschingbauer;19.6.1966\n',
            '2;Caro;Faschingbauer;25.4.1997\n',
            '3;Johanna;Faschingbauer;11.6.1995\n',
            '4;Philipp;Lichtenberger;6.4.1986\n',
            '5;Elizabeth II;Queen;1.1.1900\n',
        ])

    filename2 = tmpdir/'noheader2.csv'
    with open(filename2, 'w', encoding='cp1252') as f:
        f.writelines([
            '3;Johanna;Faschingbauer;11.6.1995\n',
            '4;Philipp;Lichtenberger;6.4.1986\n',
            '5;Elizabeth II;Queen;1.1.1900\n',
        ])

    users1 = list(userdb_csv.read_noheader_person(filename1))
    users2 = list(userdb_csv.read_noheader_person(filename2))

    assert len(users1) == 5
    assert len(users2) == 3
import userdb_csv


def test_header_person(tmpdir):
    with open(tmpdir/'header.csv', 'w', encoding='cp1252') as f:
        f.writelines([
            'ID;First name;Last name;Date of Birth\n',
            '1;"Jörg;DI";Faschingbauer;19.6.1966\n',
            '2;Caro;Faschingbauer;25.4.1997\n',
            '3;Johanna;Faschingbauer;11.6.1995\n',
            '4;Philipp;Lichtenberger;6.4.1986\n',
            '5;Elizabeth II;Queen;1.1.1900\n',
        ])
            
    users = list(userdb_csv.read_header_person(tmpdir/'header.csv'))

    assert type(users[0]) is userdb_csv.Person
    assert users[0].id == 1
    assert users[0].firstname == 'Jörg;DI'
    assert users[0].lastname == 'Faschingbauer'
    assert users[0].birth == '19.6.1966'

    assert type(users[1]) is userdb_csv.Person
    assert users[1].id == 2
    assert users[1].firstname == 'Caro'
    assert users[1].lastname == 'Faschingbauer'
    assert users[1].birth == '25.4.1997'

    assert type(users[2]) is userdb_csv.Person
    assert users[2].id == 3
    assert users[2].firstname == 'Johanna'
    assert users[2].lastname == 'Faschingbauer'
    assert users[2].birth == '11.6.1995'

    assert type(users[3]) is userdb_csv.Person
    assert users[3].id == 4
    assert users[3].firstname == 'Philipp'
    assert users[3].lastname == 'Lichtenberger'
    assert users[3].birth == '6.4.1986'

    assert type(users[4]) is userdb_csv.Person
    assert users[4].id == 5
    assert users[4].firstname == 'Elizabeth II'
    assert users[4].lastname == 'Queen'
    assert users[4].birth == '1.1.1900'

def test_must_not_use_global_variables_as_return_object(tmpdir):
    filename1 = tmpdir/'noheader1.csv'
    with open(filename1, 'w', encoding='cp1252') as f:
        f.writelines([
            '1;"Jörg;DI";Faschingbauer;19.6.1966\n',
            '2;Caro;Faschingbauer;25.4.1997\n',
            '3;Johanna;Faschingbauer;11.6.1995\n',
            '4;Philipp;Lichtenberger;6.4.1986\n',
            '5;Elizabeth II;Queen;1.1.1900\n',
        ])

    filename2 = tmpdir/'noheader2.csv'
    with open(filename2, 'w', encoding='cp1252') as f:
        f.writelines([
            '3;Johanna;Faschingbauer;11.6.1995\n',
            '4;Philipp;Lichtenberger;6.4.1986\n',
            '5;Elizabeth II;Queen;1.1.1900\n',
        ])

    users1 = list(userdb_csv.read_noheader_person(filename1))
    users2 = list(userdb_csv.read_noheader_person(filename2))

    assert len(users1) == 5
    assert len(users2) == 3
from userdb_csv import Person
import userdb_json


def test_person_to_json():
    joerg = Person(
        id=1,
        firstname='Jörg',
        lastname='Faschingbauer',
        birth='19.6.1966',
    )

    joerg_sent = userdb_json.to_json_person(joerg)
    joerg_received = userdb_json.from_json_person(joerg_sent)

    assert joerg_received.id == 1
    assert joerg_received.firstname == 'Jörg'
    assert joerg_received.lastname == 'Faschingbauer'
    assert joerg_received.birth == '19.6.1966'    

Dependencies

cluster_python Python Programming cluster_python_exercises Exercises cluster_python_exercises_userdb User Database (Exercise Series) cluster_python_basics Python: The Language Fundamentals cluster_python_advanced Python: More Language Features cluster_python_advanced_oo Object Oriented Programming cluster_python_misc Python: Miscellaneous Topics python_exercises_userdb_user_class_person Exercise: Away From Raw Dictionaries python_exercises_userdb_userdb_module Exercise: Refactoring - Extract Both CSV Formats Into Module python_exercises_userdb_user_class_person->python_exercises_userdb_userdb_module python_exercises_userdb_user_json Exercise: Convert User Record To JSON And Back python_exercises_userdb_user_class_person->python_exercises_userdb_user_json python_advanced_oo_namedtuple collections.namedtuple python_exercises_userdb_user_class_person->python_advanced_oo_namedtuple python_exercises_userdb_csvdictreader_function Exercise: Refactoring - Extract CSV Reading Into Function (csv.dictreader) python_exercises_userdb_userdb_module->python_exercises_userdb_csvdictreader_function python_exercises_userdb_csvreader Exercise: Read CSV File (csv.reader) python_exercises_userdb_userdb_module->python_exercises_userdb_csvreader python_advanced_modules Modules and Packages python_exercises_userdb_userdb_module->python_advanced_modules python_exercises_userdb_csvdictreader Exercise: Read CSV File (csv.dictreader) python_exercises_userdb_csvdictreader->python_exercises_userdb_csvreader python_basics_python_0139_commandline_argv Commandline Arguments (sys.argv) python_exercises_userdb_csvdictreader->python_basics_python_0139_commandline_argv python_misc_csv CSV Files python_exercises_userdb_csvdictreader->python_misc_csv python_exercises_userdb_user_json->python_exercises_userdb_userdb_module python_exercises_userdb_csvdictreader_function->python_exercises_userdb_csvdictreader python_basics_python_0270_functions Functions python_exercises_userdb_csvdictreader_function->python_basics_python_0270_functions python_exercises_userdb_csvreader->python_basics_python_0139_commandline_argv python_exercises_userdb_csvreader->python_misc_csv python_basics_python_0170_if The if Statement python_basics_python_0160_boolean Boolean python_basics_python_0170_if->python_basics_python_0160_boolean python_basics_python_0500_files File I/O python_basics_python_0220_for for Loops python_basics_python_0500_files->python_basics_python_0220_for python_misc_encoding Encoding python_basics_python_0500_files->python_misc_encoding python_basics_python_0125_running Running Python Programs python_basics_python_0120_helloworld Hello World python_basics_python_0125_running->python_basics_python_0120_helloworld python_basics_python_0139_commandline_argv->python_basics_python_0125_running python_basics_python_0130_syntax_etc Syntax etc. python_basics_python_0139_commandline_argv->python_basics_python_0130_syntax_etc python_basics_python_0110_blahblah Blahblah python_basics_python_0120_helloworld->python_basics_python_0110_blahblah python_basics_python_0193_while while Loops python_basics_python_0193_while->python_basics_python_0170_if python_basics_python_0193_while->python_basics_python_0160_boolean python_basics_python_0150_datatypes_overview Datatypes python_basics_python_0140_variables Variables python_basics_python_0150_datatypes_overview->python_basics_python_0140_variables python_basics_python_0270_functions->python_basics_python_0150_datatypes_overview python_basics_python_0270_functions->python_basics_python_0140_variables python_basics_python_0220_for->python_basics_python_0193_while python_basics_python_0200_sequential_types Sequential Datatypes python_basics_python_0220_for->python_basics_python_0200_sequential_types python_basics_python_0320_strings_methods Miscellaneous String Methods python_basics_python_0300_strings More About Strings python_basics_python_0320_strings_methods->python_basics_python_0300_strings python_basics_python_0250_refs_flat_deep_copy References, (Im)mutability python_basics_python_0250_refs_flat_deep_copy->python_basics_python_0150_datatypes_overview python_basics_python_0150_datatypes_overview_compound Compound Datatypes python_basics_python_0250_refs_flat_deep_copy->python_basics_python_0150_datatypes_overview_compound python_basics_python_0250_refs_flat_deep_copy->python_basics_python_0140_variables python_basics_python_0160_boolean->python_basics_python_0150_datatypes_overview python_basics_python_0150_datatypes_overview_compound->python_basics_python_0150_datatypes_overview python_basics_python_0140_variables->python_basics_python_0130_syntax_etc python_basics_python_0200_sequential_types->python_basics_python_0150_datatypes_overview_compound python_basics_python_0300_strings->python_basics_python_0150_datatypes_overview python_basics_python_0300_strings->python_basics_python_0250_refs_flat_deep_copy python_basics_python_0300_strings->python_basics_python_0200_sequential_types python_basics_python_0130_syntax_etc->python_basics_python_0120_helloworld python_advanced_oo_classes_and_dicts Classes And Dictionaries python_advanced_oo_namedtuple->python_advanced_oo_classes_and_dicts python_advanced_oo_classes_and_dicts->python_basics_python_0150_datatypes_overview_compound python_misc_encoding->python_basics_python_0150_datatypes_overview python_misc_encoding->python_basics_python_0320_strings_methods python_misc_csv->python_basics_python_0500_files python_misc_csv->python_basics_python_0220_for python_misc_csv->python_basics_python_0150_datatypes_overview_compound