4.1. Serialization CSV

4.1.1. Rationale

  • CSV - Comma Separated Values

  • CSV - Character Separated Values

CSV file with numeric values.:

5.4,3.9,1.3,0.4,0
5.9,3.0,5.1,1.8,1
6.0,3.4,4.5,1.6,2

CSV file with text values. First line is a header.:

"First Name", "Last Name"
"Mark", "Watney"
"Jan", "Twardowski"
"Melissa", "Lewis"
"Alex", "Vogel"

CSV file with mixed values (numeric and strings). First line is a header.:

sepal_length,sepal_width,petal_length,petal_width,species
5.4,3.9,1.3,0.4,setosa
5.9,3.0,5.1,1.8,virginica
6.0,3.4,4.5,1.6,versicolor
7.3,2.9,6.3,1.8,virginica
5.6,2.5,3.9,1.1,versicolor
5.4,3.9,1.3,0.4,setosa

4.1.2. Encoding

  • utf-8 - international standard (should be always used!)

  • iso-8859-1 - ISO standard for Western Europe and USA

  • iso-8859-2 - ISO standard for Central Europe (including Poland)

  • cp1250 or windows-1250 - Polish encoding on Windows

  • cp1251 or windows-1251 - Russian encoding on Windows

  • cp1252 or windows-1252 - Western European encoding on Windows

  • ASCII - ASCII characters only

with open(FILE, encoding='utf-8') as file:
    ...

4.1.3. Quoting

  • csv.QUOTE_ALL (safest)

  • csv.QUOTE_MINIMAL

  • csv.QUOTE_NONE

  • csv.QUOTE_NONNUMERIC

quoting=csv.QUOTE_ALL:

"Sepal length","Sepal width","Petal length","Petal width","Species"
"5.8","2.7","5.1","1.9","virginica"
"5.1","3.5","1.4","0.2","setosa"
"5.7","2.8","4.1","1.3","versicolor"

quoting=csv.QUOTE_MINIMAL:

Sepal length,Sepal width,Petal length,Petal width,Species
5.8,2.7,5.1,1.9,virginica
5.1,3.5,1.4,0.2,setosa
5.7,2.8,4.1,1.3,versicolor

quoting=csv.QUOTE_NONE:

Sepal length,Sepal width,Petal length,Petal width,Species
5.8,2.7,5.1,1.9,virginica
5.1,3.5,1.4,0.2,setosa
5.7,2.8,4.1,1.3,versicolor

quoting=csv.QUOTE_NONNUMERIC:

"Sepal length","Sepal width","Petal length","Petal width","Species"
5.8,2.7,5.1,1.9,"virginica"
5.1,3.5,1.4,0.2,"setosa"
5.7,2.8,4.1,1.3,"versicolor"

4.1.4. Quotechar

  • " - quote char (best)

  • ' - apostrophe

quotechar='"':

"Sepal length","Sepal width","Petal length","Petal width","Species"
"5.8","2.7","5.1","1.9","virginica"
"5.1","3.5","1.4","0.2","setosa"
"5.7","2.8","4.1","1.3","versicolor"

quotechar="'":

'Sepal length','Sepal width','Petal length','Petal width','Species'
'5.8','2.7','5.1','1.9','virginica'
'5.1','3.5','1.4','0.2','setosa'
'5.7','2.8','4.1','1.3','versicolor'

quotechar='|':

|Sepal length|,|Sepal width|,|Petal length|,|Petal width|,|Species|
|5.8|,|2.7|,|5.1|,|1.9|,|virginica|
|5.1|,|3.5|,|1.4|,|0.2|,|setosa|
|5.7|,|2.8|,|4.1|,|1.3|,|versicolor|

quotechar='/':

/Sepal length/,/Sepal width/,/Petal length/,/Petal width/,/Species/
/5.8/,/2.7/,/5.1/,/1.9/,/virginica/
/5.1/,/3.5/,/1.4/,/0.2/,/setosa/
/5.7/,/2.8/,/4.1/,/1.3/,/versicolor/

4.1.5. Delimiter

delimiter=',':

Sepal length,Sepal width,Petal length,Petal width,Species
5.8,2.7,5.1,1.9,virginica
5.1,3.5,1.4,0.2,setosa
5.7,2.8,4.1,1.3,versicolor

delimiter=';':

Sepal length;Sepal width;Petal length;Petal width;Species
5.8;2.7;5.1;1.9;virginica
5.1;3.5;1.4;0.2;setosa
5.7;2.8;4.1;1.3;versicolor

delimiter='|':

Sepal length|Sepal width|Petal length|Petal width|Species
5.8|2.7|5.1|1.9|virginica
5.1|3.5|1.4|0.2|setosa
5.7|2.8|4.1|1.3|versicolor

delimiter='\t':

Sepal length        Sepal width     Petal length    Petal width     Species
5.8 2.7     5.1     1.9     virginica
5.1 3.5     1.4     0.2     setosa
5.7 2.8     4.1     1.3     versicolor

4.1.6. Lineterminator

  • \r\n - New line on Windows

  • \n - New line on *nix

  • *nix operating systems: Linux, macOS, BSD and other POSIX compliant OSes (excluding Windows)

4.1.7. Dialects

1,2 and 2,5 -> 1,2;2,5  # delimiter=';'
1.2 and 2.5 -> 1.2,2.5  # delimiter=','
1,2;2,5 -> 1 and 2; and 2 and 5  # delimiter=','
1,2 -> 1,2;2,5  # delimiter=';'
1.2,2.5 -> 1.2 and 2.5  # delimiter=','
import csv

csv.list_dialects()
# ['excel', 'excel-tab', 'unix']
  • Microsoft Excel 2016 uses:

    • quotechar='"'

    • delimiter=','

    • lineterminator='\n'

    • encoding='...' - depends on Windows version and settings typically windows-*

4.1.8. Reader Object

Read data from CSV file using csv.reader():

import csv

FILE = r'_temporary.csv'
# sepal_length,sepal_width,petal_length,petal_width,species
# 5.4,3.9,1.3,0.4,setosa
# 5.9,3.0,5.1,1.8,virginica
# 6.0,3.4,4.5,1.6,versicolor


with open(FILE) as file:
    result = csv.reader(file)

    for line in result:
        print(line)

# ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']
# ['5.4', '3.9', '1.3', '0.4', 'setosa']
# ['5.9', '3.0', '5.1', '1.8', 'virginica']
# ['6.0', '3.4', '4.5', '1.6', 'versicolor']

4.1.9. Writer Object

Writing data to CSV file using csv.writer():

import csv

FILE = r'_temporary.csv'

DATA = [('Sepal length', 'Sepal width', 'Petal length', 'Petal width', 'Species'),
        (5.8, 2.7, 5.1, 1.9, 'virginica'),
        (5.1, 3.5, 1.4, 0.2, 'setosa'),
        (5.7, 2.8, 4.1, 1.3, 'versicolor')]

with open(FILE, mode='w') as file:
    result = csv.writer(file)
    result.writerows(DATA)

# Sepal length,Sepal width,Petal length,Petal width,Species
# 5.8,2.7,5.1,1.9,virginica
# 5.1,3.5,1.4,0.2,setosa
# 5.7,2.8,4.1,1.3,versicolor

4.1.10. DictReader

Read data from CSV file using csv.DictReader():

import csv

FILE = r'_temporary.csv'
# sepal_length,sepal_width,petal_length,petal_width,species
# 5.4,3.9,1.3,0.4,setosa
# 5.9,3.0,5.1,1.8,virginica
# 6.0,3.4,4.5,1.6,versicolor


with open(FILE) as file:
    result = csv.DictReader(file)

    for line in result:
        print(line)

# {'sepal_length': '5.4', 'sepal_width': '3.9', 'petal_length': '1.3', 'petal_width': '0.4', 'species': 'setosa'}
# {'sepal_length': '5.9', 'sepal_width': '3.0', 'petal_length': '5.1', 'petal_width': '1.8', 'species': 'virginica'}
# {'sepal_length': '6.0', 'sepal_width': '3.4', 'petal_length': '4.5', 'petal_width': '1.6', 'species': 'versicolor'}

Read data from CSV file using csv.DictReader():

import csv

FILE = r'_temporary.csv'
# 'sepal_length';'sepal_width';'petal_length';'petal_width';'species'
# '5,4';'3,9';'1,3';'0,4';'setosa'
# '5,9';'3,0';'5,1';'1,8';'virginica'
# '6,0';'3,4';'4,5';'1,6';'versicolor'


def isnumeric(value):
    try:
        float(value)
        return True
    except ValueError:
        return False


def clean(line):
    return {key: float(v) if isnumeric(v) else v
            for key, value in line.items()
            if (v := value.replace(',', '.'))}


with open(FILE) as file:
    result = csv.DictReader(file, delimiter=';', quotechar="'")

    for line in result:
        print(clean(line))


# {'sepal_length': 5.4, 'sepal_width': 3.9, 'petal_length': 1.3, 'petal_width': 0.4, 'species': 'setosa'}
# {'sepal_length': 5.9, 'sepal_width': 3.0, 'petal_length': 5.1, 'petal_width': 1.8, 'species': 'virginica'}
# {'sepal_length': 6.0, 'sepal_width': 3.4, 'petal_length': 4.5, 'petal_width': 1.6, 'species': 'versicolor'}

Read data from CSV file using csv.DictReader(). While giving custom names note, that first line (typically a header) will be treated like normal data. Therefore we skip it using header = file.readline():

import csv

FILE = r'_temporary.csv'
# sepal_length,sepal_width,petal_length,petal_width,species
# 5.4,3.9,1.3,0.4,setosa
# 5.9,3.0,5.1,1.8,virginica
# 6.0,3.4,4.5,1.6,versicolor

FIELDNAMES = [
    'Sepal Length',
    'Sepal Width',
    'Petal Length',
    'Petal Width',
    'Species',
]


with open(FILE) as file:
    result = csv.DictReader(file, fieldnames=FIELDNAMES, delimiter=',')
    file.readline()  # skip first line

    for line in result:
        print(line)

# {'Sepal Length': '5.4', 'Sepal Width': '3.9', 'Petal Length': '1.3', 'Petal Width': '0.4', 'Species': 'setosa'}
# {'Sepal Length': '5.9', 'Sepal Width': '3.0', 'Petal Length': '5.1', 'Petal Width': '1.8', 'Species': 'virginica'}
# {'Sepal Length': '6.0', 'Sepal Width': '3.4', 'Petal Length': '4.5', 'Petal Width': '1.6', 'Species': 'versicolor'}

4.1.11. DictWriter

  • Remember to add mode='w' to open() function

  • Default encoding is encoding='utf-8'

import csv

FILE = r'_temporary.csv'

DATA = [{'Sepal Length': 5.4, 'Sepal Width': 3.9, 'Petal Length': 1.3, 'Petal Width': 0.4, 'Species': 'setosa'},
        {'Sepal Length': 5.9, 'Sepal Width': 3.0, 'Petal Length': 5.1, 'Petal Width': 1.8, 'Species': 'virginica'},
        {'Sepal Length': 6.0, 'Sepal Width': 3.4, 'Petal Length': 4.5, 'Petal Width': 1.6, 'Species': 'versicolor'}]

header = DATA[0].keys()

with open(FILE, mode='w') as file:
    result = csv.DictWriter(file, fieldnames=header)
    result.writeheader()
    result.writerows(DATA)


# Sepal Length,Sepal Width,Petal Length,Petal Width,Species
# 5.4,3.9,1.3,0.4,setosa
# 5.9,3.0,5.1,1.8,virginica
# 6.0,3.4,4.5,1.6,versicolor

Write data to CSV file using csv.DictWriter():

import csv

FILE = r'_temporary.csv'

DATA = [{'sepal_length': 5.4, 'sepal_width': 3.9, 'petal_length': 1.3, 'petal_width': 0.4, 'species': 'setosa'},
        {'sepal_length': 5.9, 'sepal_width': 3.0, 'petal_length': 5.1, 'petal_width': 1.8, 'species': 'virginica'},
        {'sepal_length': 6.0, 'sepal_width': 3.4, 'petal_length': 4.5, 'petal_width': 1.6, 'species': 'versicolor'}]

FIELDNAMES = ['sepal_length', 'sepal_width', 'petal_length',
              'petal_width', 'species']

with open(FILE, mode='w', encoding='utf-8') as file:
    result = csv.DictWriter(
        f=file,
        fieldnames=FIELDNAMES,
        delimiter=',',
        quotechar='"',
        quoting=csv.QUOTE_ALL,
        lineterminator='\n')

    result.writeheader()
    result.writerows(DATA)

# "sepal_length","sepal_width","petal_length","petal_width","species"
# "5.4","3.9","1.3","0.4","setosa"
# "5.9","3.0","5.1","1.8","virginica"
# "6.0","3.4","4.5","1.6","versicolor"

4.1.12. Parsing Non-CSV Files

Parsing /etc/passwd file with csv.DictReader():

import csv


FILE = r'_temporary.txt'
# root:x:0:0:root:/root:/bin/bash
# watney:x:1000:1000:Mark Watney:/home/watney:/bin/bash
# lewis:x:1001:1001:José Jiménez:/home/lewis:/bin/bash
# twardowski:x:1002:1002:Jan Twardowski:/home/twardowski:/bin/bash

with open(FILE) as file:
    result = csv.DictReader(
        file,
        fieldnames=['username', 'password', 'uid', 'gid', 'fullname', 'home', 'shell'],
        delimiter=':',
        lineterminator='\n',
        quoting=csv.QUOTE_NONE)

    for line in result:
        print(line)

# {'username': 'root', 'password': 'x', 'uid': '0',...}
# {'username': 'watney', 'password': 'x', 'uid': '1000',...}
# {'username': 'lewis', 'password': 'x', 'uid': '1001',...}
# {'username': 'twardowski', 'password': 'x', 'uid': '1002',...}

Parsing Java properties file with csv.DictReader():

import csv


FILE = r'_temporary.properties'
# sonar.projectKey=habitatOS
# sonar.projectName=habitatOS
# sonar.language=py
# sonar.sourceEncoding=UTF-8
# sonar.verbose=true

with open(FILE) as file:
    result = csv.DictReader(
        file,
        fieldnames=['property', 'value'],
        delimiter='=',
        lineterminator='\n',
        quoting=csv.QUOTE_NONE)

    for line in result:
        print(line)

# {'property': 'sonar.projectKey', 'value': 'habitatOS'}
# {'property': 'sonar.projectName', 'value': 'habitatOS'}
# {'property': 'sonar.language', 'value': 'py'}
# {'property': 'sonar.sourceEncoding', 'value': 'UTF-8'}
# {'property': 'sonar.verbose', 'value': 'true'}

4.1.13. Use Cases

import csv

FILE = r'_temporary.csv'

total = 0
count = 0

with open(FILE) as file:
    data = csv.reader(file)
    next(data)

    for line in data:
        total += float(line[1])
        count += 1

mean = total / count
print(mean)

4.1.14. Serialization

../_images/csv-relations-flat-attrs.png
../_images/csv-relations-flat-norel.png
../_images/csv-relations-ffill-dash.png
../_images/csv-relations-ffill-duplicate.png
../_images/csv-relations-ffill-empty.png
../_images/csv-relations-ffill-uniqid.png
../_images/csv-relations-rel-has.png
../_images/csv-relations-rel-m2m.png
../_images/csv-relations-rel-m2o.png
../_images/csv-relations-serialize-cls.png
../_images/csv-relations-serialize-clsattr.png
../_images/csv-relations-serialize-obj.png
../_images/csv-relations-serialize-objattr.png

4.1.15. Good Practices

  • Always specify:

    • delimiter=',' to csv.DictReader() object

    • quotechar='"' to csv.DictReader() object

    • quoting=csv.QUOTE_ALL to csv.DictReader() object

    • lineterminator='\n' to csv.DictReader() object

    • encoding='utf-8' to open() function (especially when working with Microsoft Excel)

4.1.16. Assignments

Code 4.1. Solution
"""
* Assignment: Serialization CSV Writer
* Complexity: easy
* Lines of code: 4 lines
* Time: 5 min

English:
    1. Use data from "Given" section (see below)
    3. Using `csv.writer()` save `DATA` to file
    4. Non functional requirements:
        a. Do not use quotes in output CSV file
        b. Use `,` to separate columns
        c. Use `utf-8` encoding
        d. Use Unix `\n` newline
    5. Run doctests - all must succeed

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    3. Za pomocą `csv.writer()` zapisz `DATA` do pliku
    4. Wymagania niefunkcjonalne:
        a. Nie używaj cudzysłowów w wynikowym pliku CSV
        b. Użyj `,` do oddzielenia kolumn
        c. Użyj kodowania `utf-8`
        d. Użyj zakończenia linii Unix `\n`
    5. Uruchom doctesty - wszystkie muszą się powieść

Hint:
    * For Python before 3.8: `dict(OrderedDict)`

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> result = open(FILE).read()
    >>> print(result)
    Sepal length,Sepal width,Petal length,Petal width,Species
    5.8,2.7,5.1,1.9,virginica
    5.1,3.5,1.4,0.2,setosa
    5.7,2.8,4.1,1.3,versicolor
    6.3,2.9,5.6,1.8,virginica
    6.4,3.2,4.5,1.5,versicolor
    4.7,3.2,1.3,0.2,setosa
    7.0,3.2,4.7,1.4,versicolor
    7.6,3.0,6.6,2.1,virginica
    4.9,3.0,1.4,0.2,setosa
    <BLANKLINE>
    >>> from os import remove
    >>> remove(FILE)
"""


# Given
FILE = r'_temporary.csv'
DATA = [('Sepal length', 'Sepal width', 'Petal length', 'Petal width', 'Species'),
        (5.8, 2.7, 5.1, 1.9, 'virginica'),
        (5.1, 3.5, 1.4, 0.2, 'setosa'),
        (5.7, 2.8, 4.1, 1.3, 'versicolor'),
        (6.3, 2.9, 5.6, 1.8, 'virginica'),
        (6.4, 3.2, 4.5, 1.5, 'versicolor'),
        (4.7, 3.2, 1.3, 0.2, 'setosa'),
        (7.0, 3.2, 4.7, 1.4, 'versicolor'),
        (7.6, 3.0, 6.6, 2.1, 'virginica'),
        (4.9, 3.0, 1.4, 0.2, 'setosa'),]


Code 4.2. Solution
"""
* Assignment: Serialization CSV DictWriter
* Complexity: easy
* Lines of code: 10 lines
* Time: 8 min

English:
    1. Use data from "Given" section (see below)
    2. Using `csv.DictWriter()` save `DATA` to file
    3. Open file in your spreadsheet program like Microsoft Excel / Libre Office / Numbers etc.
    4. Open file in simple in your IDE and simple text editor (like Notepad, vim, gedit)
    5. Non functional requirements:
        a. All fields must be enclosed by double quote `"` character
        b. Use `,` to separate columns
        c. Use `utf-8` encoding
        d. Use Unix `\n` newline
    6. Run doctests - all must succeed

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Za pomocą `csv.DictWriter()` zapisz `DATA` do pliku
    3. Spróbuj otworzyć plik w arkuszu kalkulacyjnym tj. Microsoft Excel / Libre Office / Numbers itp
    4. Spróbuj otworzyć plik w IDE i prostym edytorze tekstu tj. Notepad, vim lub gedit
    5. Wymagania niefunkcjonalne:
        a. Wszystkie pola muszą być otoczone znakiem cudzysłowu `"`
        b. Użyj `,` do oddzielenia kolumn
        c. Użyj kodowania `utf-8`
        d. Użyj zakończenia linii Unix `\n`
    6. Uruchom doctesty - wszystkie muszą się powieść

Hint:
    * For Python before 3.8: `dict(OrderedDict)`

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> result = open(FILE).read()
    >>> print(result)   # doctest: +NORMALIZE_WHITESPACE
    "firstname","lastname"
    "Jan","Twardowski"
    "José","Jiménez"
    "Mark","Watney"
    "Ivan","Ivanovic"
    "Melissa","Lewis"
    >>> from os import remove
    >>> remove(FILE)
"""


# Given
FILE = r'_temporary.csv'
DATA = [{'firstname': 'Jan', 'lastname': 'Twardowski'},
        {'firstname': 'José', 'lastname': 'Jiménez'},
        {'firstname': 'Mark', 'lastname': 'Watney'},
        {'firstname': 'Ivan', 'lastname': 'Ivanovic'},
        {'firstname': 'Melissa', 'lastname': 'Lewis'}]


Code 4.3. Solution
"""
* Assignment: Serialization CSV DictReader
* Complexity: easy
* Lines of code: 10 lines
* Time: 8 min

English:
    1. Use data from "Given" section (see below)
    2. Using `csv.DictReader` read the `FILE` content
    3. Use explicit `encoding`, `delimiter` and `quotechar`
    4. Replace column names with `FIELDNAMES`
    5. Skip the first line (header)
    6. Add rows to `result: list[dict]`
    7. Run doctests - all must succeed

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Korzystając z `csv.DictReader` wczytaj zawartość pliku `FILE`
    3. Podaj jawnie `encoding`, `delimiter` oraz `quotechar`
    4. Podmień nazwy kolumn na `FIELDNAMES`
    5. Pomiń pierwszą linię (nagłówek)
    6. Dodaj wiersze do `result: list[dict]`
    7. Uruchom doctesty - wszystkie muszą się powieść

Hint:
    * For Python before 3.8: `dict(OrderedDict)`

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> type(result)
    <class 'list'>
    >>> result  # doctest: +NORMALIZE_WHITESPACE
    [{'Sepal Length': '5.8', 'Sepal Width': '2.7', 'Petal Length': '5.1', 'Petal Width': '1.9', 'Species': 'virginica'},
     {'Sepal Length': '5.1', 'Sepal Width': '3.5', 'Petal Length': '1.4', 'Petal Width': '0.2', 'Species': 'setosa'},
     {'Sepal Length': '5.7', 'Sepal Width': '2.8', 'Petal Length': '4.1', 'Petal Width': '1.3', 'Species': 'versicolor'}]
    >>> from os import remove
    >>> remove(FILE)
"""


# Given
FILE = r'_temporary.csv'
FIELDNAMES = ['Sepal Length', 'Sepal Width',
              'Petal Length', 'Petal Width', 'Species']

DATA = """sepal_length,sepal_width,petal_length,petal_width,species
5.8,2.7,5.1,1.9,virginica
5.1,3.5,1.4,0.2,setosa
5.7,2.8,4.1,1.3,versicolor"""

with open(FILE, mode='w') as file:
    file.write(DATA)


result: list = []


Code 4.4. Solution
"""
* Assignment: Serialization CSV Schemaless
* Complexity: medium
* Lines of code: 7 lines
* Time: 8 min

English:
    1. Use data from "Given" section (see below)
    2. Using `csv.DictWriter()` write variable schema data to CSV file
    3. `fieldnames` must be automatically generated from `DATA`
    4. Non functional requirements:
        a. All fields must be enclosed by double quote `"` character
        b. Use `;` to separate columns
        c. Use `utf-8` encoding
        d. Use Unix `\n` newline
        e. Sort `fieldnames` using `sorted()`
    5. Run doctests - all must succeed

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Za pomocą `csv.DictWriter()` zapisz do pliku CSV dane o zmiennej strukturze
    3. `fieldnames` musi być generowane automatycznie na podstawie `DATA`
    4. Wymagania niefunkcjonalne:
        a. Wszystkie pola muszą być otoczone znakiem cudzysłowu `"`
        b. Użyj `,` do oddzielenia kolumn
        c. Użyj kodowania `utf-8`
        d. Użyj zakończenia linii Unix `\n`
        e. Posortuj `fieldnames` używając `sorted()`
    5. Uruchom doctesty - wszystkie muszą się powieść

Hint:
    * For Python before 3.8: `dict(OrderedDict)`

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> result = open(FILE).read()
    >>> print(result)
    "Petal length","Petal width","Sepal length","Sepal width","Species"
    "","","5.1","3.5","setosa"
    "4.1","1.3","","","versicolor"
    "","1.8","6.3","","virginica"
    "","0.2","5.0","","setosa"
    "4.1","","","2.8","versicolor"
    "","1.8","","2.9","virginica"
    <BLANKLINE>
    >>> from os import remove
    >>> remove(FILE)
"""


# Given
FILE = r'_temporary.csv'
DATA = [{'Sepal length': 5.1, 'Sepal width': 3.5, 'Species': 'setosa'},
        {'Petal length': 4.1, 'Petal width': 1.3, 'Species': 'versicolor'},
        {'Sepal length': 6.3, 'Petal width': 1.8, 'Species': 'virginica'},
        {'Sepal length': 5.0, 'Petal width': 0.2, 'Species': 'setosa'},
        {'Sepal width': 2.8, 'Petal length': 4.1, 'Species': 'versicolor'},
        {'Sepal width': 2.9, 'Petal width': 1.8, 'Species': 'virginica'}]


Code 4.5. Solution
"""
* Assignment: Serialization CSV Objects
* Complexity: medium
* Lines of code: 6 lines
* Time: 8 min

English:
    1. Use data from "Given" section (see below)
    2. Using `csv.DictWriter()` save data to CSV file
    3. Non functional requirements:
        a. All fields must be enclosed by double quote `"` character
        b. Use `,` to separate columns
        c. Use `utf-8` encoding
        d. Use Unix `\n` newline
    4. Run doctests - all must succeed

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Za pomocą `csv.DictWriter()` zapisz dane do pliku CSV
    3. Wymagania niefunkcjonalne:
        a. Wszystkie pola muszą być otoczone znakiem cudzysłowu `"`
        b. Użyj `,` do oddzielenia kolumn
        c. Użyj kodowania `utf-8`
        d. Użyj zakończenia linii Unix `\n`
    4. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `vars(obj)`
    * For Python before 3.8: `dict(OrderedDict)`

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> result = open(FILE).read()
    >>> print(result)
    sepal_length,sepal_width,petal_length,petal_width,species
    5.1,3.5,1.4,0.2,setosa
    5.8,2.7,5.1,1.9,virginica
    5.1,3.5,1.4,0.2,setosa
    5.7,2.8,4.1,1.3,versicolor
    6.3,2.9,5.6,1.8,virginica
    6.4,3.2,4.5,1.5,versicolor
    <BLANKLINE>
    >>> from os import remove
    >>> remove(FILE)
"""


# Given
from csv import DictWriter


class Iris:
    def __init__(self, sepal_length, sepal_width,
                 petal_length, petal_width, species):
        self.sepal_length = sepal_length
        self.sepal_width = sepal_width
        self.petal_length = petal_length
        self.petal_width = petal_width
        self.species = species


FILE = r'_temporary.txt'

DATA = [Iris(5.1, 3.5, 1.4, 0.2, 'setosa'),
        Iris(5.8, 2.7, 5.1, 1.9, 'virginica'),
        Iris(5.1, 3.5, 1.4, 0.2, 'setosa'),
        Iris(5.7, 2.8, 4.1, 1.3, 'versicolor'),
        Iris(6.3, 2.9, 5.6, 1.8, 'virginica'),
        Iris(6.4, 3.2, 4.5, 1.5, 'versicolor')]


Code 4.6. Solution
"""
* Assignment: Serialization CSV Relations
* Complexity: hard
* Lines of code: 11 lines
* Time: 21 min

English:
    1. Use data from "Given" section (see below)
    2. Using `csv.DictWriter()` save contacts from addressbook to CSV file
    3. How to write relations to CSV file (contact has many addresses)?
    4. Recreate object structure from CSV file
    5. Non functional requirements:
        a. All fields must be enclosed by double quote `"` character
        b. Use `,` to separate mission fields
        c. Use `;` to separate missions
        d. Use Unix `\n` newline
        e. Sort `fieldnames` using `sorted()`
    6. Run doctests - all must succeed

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Za pomocą `csv.DictWriter()` zapisz kontakty z książki adresowej w pliku
    3. Jak zapisać w CSV dane relacyjne (kontakt ma wiele adresów)?
    4. Odtwórz strukturę obiektów na podstawie danych odczytanych z pliku
    5. Wymagania niefunkcjonalne:
        a. Wszystkie pola muszą być otoczone znakiem cudzysłowu `"`
        b. Użyj `,` do oddzielania pól mission
        c. Użyj `;` do oddzielenia missions
        d. Użyj zakończenia linii Unix `\n`
        e. Posortuj `fieldnames` używając `sorted()`
    6. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `vars(obj)`
    * For Python before 3.8: `dict(OrderedDict)`

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> result = open(FILE).read()
    >>> print(result)
    "lastname","missions","name"
    "Twardowski","1969,Apollo 11;2024,Artemis 3","Jan"
    "Watney","2035,Ares 3","Mark"
    "Lewis","","Melissa"
    <BLANKLINE>
    >>> from os import remove
    >>> remove(FILE)
"""


# Given
import csv

FILE = r'_temporary.csv'


class Astronaut:
    def __init__(self, firstname, lastname, missions=None):
        self.name = firstname
        self.lastname = lastname
        self.missions = list(missions) if missions else []

class Mission:
    def __init__(self, year, name):
        self.year = year
        self.name = name


CREW = [
    Astronaut('Jan', 'Twardowski', missions=[
        Mission(1969, 'Apollo 11'),
        Mission(2024, 'Artemis 3')]),

    Astronaut('Mark', 'Watney', missions=[
        Mission(2035, 'Ares 3')]),

    Astronaut('Melissa', 'Lewis'),
]

result: list = []