5.6. Relations

5.6.1. Relations

class Astronaut:
    def __init__(self, firstname, lastname, missions=()):
        self.firstname = firstname
        self.lastname = lastname
        self.missions = list(missions)


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


DATA = [
    Astronaut('Jan', 'Twardowski', missions=[
        Mission('1967', 'Apollo 1'),
        Mission('1970', 'Apollo 13'),
        Mission('1973', 'Apollo 18')]),

    Astronaut('Ivan', 'Ivanovic', missions=[
        Mission('2023', 'Artemis 2'),
        Mission('2024', 'Artemis 3')]),

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

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

5.6.2. Serialization

  • pickle - has relations

  • json - has relations

  • csv - non-relational format

../../_images/oop-relations-serialize-dbdump.png

Figure 5.2. Relational files or database dump

../../_images/oop-relations-serialize-ffill1.png

Figure 5.3. Ffill - Forward fill

../../_images/oop-relations-serialize-ffill2.png

Figure 5.4. Fill in specified columns

../../_images/oop-relations-serialize-uniqid.png

Figure 5.5. Data duplication with unique ID

../../_images/oop-relations-serialize-colattr.png

Figure 5.6. Each relations attribute adds one column

../../_images/oop-relations-serialize-colobj.png

Figure 5.7. Each relations instance adds one column

../../_images/oop-relations-serialize-colcls.png

Figure 5.8. Each relations class adds one column

../../_images/oop-relations-serialize-split.png

Figure 5.9. Relations attributes split into columns

../../_images/oop-relations-serialize-hybrid.png

Figure 5.10. Hybrid compact and separate columns

5.6.3. Assignments

5.6.3.1. OOP Relations Model

  • Assignment name: OOP Relations Model

  • Last update: 2020-10-01

  • Complexity level: easy

  • Lines of code to write: 10 lines

  • Estimated time of completion: 13 min

  • Solution: solution/oop_relations_model.py

English
  1. Use data from "Input" section (see below)

  2. In DATA we have two classes

  3. Model data using classes and relations

  4. Create instances dynamically based on DATA

  5. Compare result with "Output" section (see below)

Polish
  1. Użyj danych z sekcji "Input" (patrz poniżej)

  2. W DATA mamy dwie klasy

  3. Zamodeluj problem wykorzystując klasy i relacje między nimi

  4. Twórz instancje dynamicznie na podstawie DATA

  5. Porównaj wyniki z sekcją "Output" (patrz poniżej)

The whys and wherefores
  • OOP modeling

  • working with objects

  • nesting objects and relations

  • casting objects to str

Input
Listing 5.98. Python list[dict] or JSON?
DATA = [
    {"firstname": "Jan", "lastname": "Twardowski", "addresses": [
        {"street": "Kamienica Pod św. Janem Kapistranem", "city": "Kraków", "postcode": "31-008", "region": "Małopolskie", "country": "Poland"}]},
    {"firstname": "José", "lastname": "Jiménez", "addresses": [
        {"street": "2101 E NASA Pkwy", "city": "Houston", "postcode": 77058, "region": "Texas", "country": "USA"},
        {"street": "", "city": "Kennedy Space Center", "postcode": 32899, "region": "Florida", "country": "USA"}]},
    {"firstname": "Mark", "lastname": "Watney", "addresses": [
        {"street": "4800 Oak Grove Dr", "city": "Pasadena", "postcode": 91109, "region": "California", "country": "USA"},
        {"street": "2825 E Ave P", "city": "Palmdale", "postcode": 93550, "region": "California", "country": "USA"}]},
    {"firstname": "Иван", "lastname": "Иванович", "addresses": [
        {"street": "", "city": "Космодро́м Байкону́р", "postcode": "", "region": "Кызылординская область", "country": "Қазақстан"},
        {"street": "", "city": "Звёздный городо́к", "postcode": 141160, "region": "Московская область", "country": "Россия"}]},
    {"firstname": "Melissa", "lastname": "Lewis", "addresses": []},
    {"firstname": "Alex", "lastname": "Vogel", "addresses": [
        {"street": "Linder Hoehe", "city": "Köln", "postcode": 51147, "region": "North Rhine-Westphalia", "country": "Germany"}]}
]
Output
>>> assert type(result) is list

>>> assert all(type(astro) is Astronaut
...            for astro in result)

>>> assert all(type(addr) is Address
...            for astro in result
...            for addr in astro.addresses)

>>> result  # doctest: +NORMALIZE_WHITESPACE
[Astronaut(firstname='Jan',
           lastname='Twardowski',
           addresses=[Address(street='Kamienica Pod św. Janem Kapistranem', city='Kraków', postcode='31-008', region='Małopolskie', country='Poland')]),
 Astronaut(firstname='José',
           lastname='Jiménez',
           addresses=[Address(street='2101 E NASA Pkwy', city='Houston', postcode=77058, region='Texas', country='USA'),
                      Address(street='', city='Kennedy Space Center', postcode=32899, region='Florida', country='USA')]),
 Astronaut(firstname='Mark',
           lastname='Watney',
           addresses=[Address(street='4800 Oak Grove Dr', city='Pasadena', postcode=91109, region='California', country='USA'),
                      Address(street='2825 E Ave P', city='Palmdale', postcode=93550, region='California', country='USA')]),
 Astronaut(firstname='Иван',
           lastname='Иванович',
           addresses=[Address(street='', city='Космодро́м Байкону́р', postcode='', region='Кызылординская область', country='Қазақстан'),
                      Address(street='', city='Звёздный городо́к', postcode=141160, region='Московская область', country='Россия')]),
 Astronaut(firstname='Melissa',
           lastname='Lewis',
           addresses=[]),
 Astronaut(firstname='Alex',
           lastname='Vogel',
           addresses=[Address(street='Linder Hoehe', city='Köln', postcode=51147, region='North Rhine-Westphalia', country='Germany')])]

5.6.3.2. OOP Relations Flatten

  • Assignment name: OOP Relations Flatten

  • Last update: 2020-10-01

  • Complexity level: hard

  • Lines of code to write: 20 lines

  • Estimated time of completion: 21 min

  • Solution: solution/oop_relations_flatten.py

English
  1. Use code from "Input" 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:

    • All fields must be enclosed by double quote " character

    • Use ; to separate columns

    • Use utf-8 encoding

    • Use Unix \n newline

  6. Compare result with "Output" section (see below)

Polish
  1. Użyj kodu z sekcji "Input" (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:

    • Wszystkie pola muszą być otoczone znakiem cudzysłowu "

    • Użyj ; do oddzielenia kolumn

    • Użyj kodowania utf-8

    • Użyj zakończenia linii Unix \n

  6. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Input
import csv


class Astronaut:
    def __init__(self, firstname, lastname, missions=()):
        self.firstname = firstname
        self.lastname = lastname
        self.missions = list(missions)


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

FILE = r'_temporary.csv'

DATA = [
    Astronaut('Jan', 'Twardowski', missions=[
        Mission('1967', 'Apollo 1'),
        Mission('1970', 'Apollo 13'),
        Mission('1973', 'Apollo 18')]),

    Astronaut('Ivan', 'Ivanovic', missions=[
        Mission('2023', 'Artemis 2'),
        Mission('2024', 'Artemis 3')]),

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

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

result = []


with open(FILE, mode='w') as file:
    writer = csv.DictWriter(
        f=file,
        fieldnames=sorted(result[0].keys()),
        delimiter=',',
        quotechar='"',
        quoting=csv.QUOTE_ALL,
        lineterminator='\n')

    writer.writeheader()
    writer.writerows(result)
Output
>>> result  # doctest: +NORMALIZE_WHITESPACE
[{'firstname': 'Jan', 'lastname': 'Twardowski', 'missions': '1967,Apollo 1;1970,Apollo 13;1973,Apollo 18'},
 {'firstname': 'Ivan', 'lastname': 'Ivanovic', 'missions': '2023,Artemis 2;2024,Artemis 3'},
 {'firstname': 'Mark', 'lastname': 'Watney', 'missions': '2035,Ares 3'},
 {'firstname': 'Melissa', 'lastname': 'Lewis', 'missions': ''}]

5.6.3.3. OOP Relations Nested

  • Assignment name: OOP Relations Nested

  • Last update: 2020-10-01

  • Complexity level: medium

  • Lines of code to write: 45 lines

  • Estimated time of completion: 13 min

  • Solution: solution/oop_relations_nested.py

English
  1. Client can open a bank account

  2. Client can have many accounts

  3. Bank has many clients

  4. Each account has unique number generated when opening an account

  5. Client can ask about number of all of his accounts

  6. Client can add money to the account

  7. Client can withdraw money from the account

  8. Compare result with "Output" section (see below)

Polish
  1. Klient może otworzyć konto w banku

  2. Klient może mieć wiele kont

  3. Bank może mieć wielu klientów

  4. Każde konto ma unikalny numer, który jest generowany przy zakładaniu

  5. Klient może odpytać o numery wszystkich swoich kont

  6. Klient może wpłacić pieniądze na swoje konto

  7. Klient może wybrać pieniądze z bankomatu

  8. Porównaj wyniki z sekcją "Output" (patrz poniżej)