Задачи по теми 6 (type hints), 7 (exceptions) и 8 (files)

Задачи по теми 6 (type hints), 7 (exceptions) и 8 (files)#

Важно !: Във всяка задача е задължително използването на type hints в дефинициите на функциите.

Задача 1 (2 точки)#

Напишете функция validate_list, която приема път до списък за пазаруване. Списъкът е съставен от име на предмета, бройката която трябва да се закупи и единичната му цена. Функцията трябва да валидира списъка по зададени условия, и ако списъка е валиден, да върне общата сума, която е необходимо да похарчим. Ако списъка не отговаря на някое от условията, се хвърля специално дефинирана грешка.

Възможните грешки са:

  • InvalidLineError - приема реда, който не отговаря на условията

  • InvalidItemError - приема името на предмета, който не отговаря на условията

  • InvalidQuantityError - приема броя и предмета, който не отговаря на условията

  • InvalidPriceError - приема цената и предмета, който не отговаря на условията

  • ListFileError - приема пътя до файла, който не отговаря на условията

Правилата за валидиране на следните:

  • Ако файла не съществува, хвърлете ListFileError.

  • Ако файла не може да бъде прочетен, хвърлете ListFileError.

  • Всеки ред от файла започва с -. Ако не е така, хвърлете InvalidLineError.

  • Всеки ред има следната структура: име на предмета:брой:единична цена. Ако не е така, хвърлете InvalidLineError.

  • Ако името на предмета е празен низ, или е съставено само от цифри, хвърлете InvalidItemError.

  • Ако броят не е число, хвърлете InvalidQuantityError.

  • Ако броят не е цяло число, хвърлете InvalidQuantityError.

  • Ако броят е отрицателно, хвърлете InvalidQuantityError.

  • Ако единичната цена не е число, хвърлете InvalidPriceError.

  • Ако единичната цена е отрицателно число, хвърлете InvalidPriceError.

Примерен файл:

- мляко:2:2.50
- хляб:1:1.50
- банани:1:2.50
- ябълки:1:0.50
- круши:1:1.75

Примерни файлове може да намерите в папката lab04_files/task_1

# Write your code here:
assert abs(validate_list(os.path.join("lab04_files", "task_1", "list1.txt")) - 11.25) < 0.001

assert int(validate_list(os.path.join("lab04_files", "task_1", "list2.txt"))) == 0, "Empty files should return 0"

try:
    validate_list(os.path.join("lab04_files", "task_1", "list3.txt"))
    assert False, "Should raise InvalidLineError"
except InvalidLineError:
    pass

try:
    validate_list(os.path.join("lab04_files", "task_1", "list4.txt"))
    assert False, "Should raise InvalidLineError"
except InvalidLineError:
    pass

try:
    validate_list(os.path.join("lab04_files", "task_1", "list5.txt"))
    assert False, "Should raise InvalidLineError"
except InvalidItemError:
    pass

try:
    validate_list(os.path.join("lab04_files", "task_1", "list6.txt"))
    assert False, "Should raise InvalidLineError"
except InvalidQuantityError:
    pass

try:
    validate_list(os.path.join("lab04_files", "task_1", "list7.txt"))
    assert False, "Should raise InvalidLineError"
except InvalidQuantityError:
    pass

try:
    validate_list(os.path.join("lab04_files", "task_1", "list8.txt"))
    assert False, "Should raise InvalidLineError"
except InvalidQuantityError:
    pass

try:
    validate_list(os.path.join("lab04_files", "task_1", "list9.txt"))
    assert False, "Should raise InvalidLineError"
except InvalidPriceError:
    pass

try:
    validate_list(os.path.join("lab04_files", "task_1", "list10.txt"))
    assert False, "Should raise InvalidLineError"
except InvalidPriceError:
    pass

try:
    validate_list(os.path.join("lab04_files", "task_1", "list11.txt"))
    assert False, "Should raise InvalidLineError"
except InvalidLineError:
    pass

"✅ All OK! +2 points"

Задача 2 (4 точки)#

Напишете клас SimpleBackup, който да реализира следните функционалности:

  • Създаване на копие на данни по подаден списък с файлове за копиране (create). Създаваме копие на всички файлове, но не и на самите директории.

  • Възстановяване на копие на данни по подадено име на копие и локация, където да се възстанови (restore)

  • Връщане на сортиран списък с всички копия на данни, които са направени (show)

  • Изтриване на копие на данни по подадено име на копие (delete)

Име на копие дефинираме по следния начин: backup_<timestamp>, където <timestamp> е времето на създаване на копието във формат YYYYMMDD_HHMMSS. (За форматирането на датата и часа може да използвате strftime от модула time.)

Списъка с файлове за копиране е в следния формат - на всеки ред е описан един файл с релативния му път. Важно: пътищата на файловете са разделени с интервал, а не с /.

Копията трябва да се съхраняват в директорията backups, като всяко копие е в отделна директория.

Методите create, restore, show и delete на копие трябва да връщат True ако операцията е била успешна, и False в противен случай.

В директорията task_2 се намира backups (където трябва да бъдат създавани вашите копия), списъците с файлове за копиране - backup_1.txt, backup_2.txt и т.н., и директорията sample_files, в която се намират тестовите файлове, на които ще правим копия.

Бележки:

Изпълнявайте тетрадката/кода ви в директорията labs, за да може да работите с релативни пътища.

Независимо от структурата на входните ви данни, съдържанието на резервните копия трябва да е на едно ниво - т.е всички файлове за дадено копие трябва да се намират в една директория.

# Write your code here:
# Some helper code

import os
def get_folder_content(backup_dir: str) -> list[str]:
    result = []

    for content in os.listdir(backup_dir):
        with open(os.path.join(backup_dir, content), 'r') as f:
            result.append(f.read().strip())

    return result
import time
import shutil

backups = SimpleBackup()


# Arrange 
default_listing = backups.show()

# Act 
backup1_result = backups.create(os.path.join('lab04_files', 'task_2', 'backup_1.txt'))
time.sleep(1)  # to ensure that the backups will have a different timestamp
backup2_result = backups.create(os.path.join('lab04_files', 'task_2', 'backup_2.txt'))

listing1_result = backups.show()

backup1_path = listing1_result[-2]
backup2_path = listing1_result[-1]

backup1_content = get_folder_content(os.path.join('lab04_files', 'task_2', 'backups', backup1_path))
backup2_content = get_folder_content(os.path.join('lab04_files', 'task_2', 'backups', backup2_path))

time.sleep(1)  # to ensure that the backups will have a different timestamp

backup3_result = backups.create(os.path.join('lab04_files', 'task_2', 'backup_3.txt'))
time.sleep(1)  # to ensure that the backups will have a different timestamp

backup4_result = backups.create(os.path.join('lab04_files', 'task_2', 'backup_4.txt'))
time.sleep(1)  # to ensure that the backups will have a different timestamp

backup5_result = backups.create(os.path.join('lab04_files', 'task_2', 'backup_5.txt'))
time.sleep(1)  # to ensure that the backups will have a different timestamp

listing2_result = backups.show()

backup3_path = listing2_result[-2]
backup4_path = listing2_result[-1]

backup3_content = get_folder_content(os.path.join('lab04_files', 'task_2', 'backups', backup3_path))
backup4_content = get_folder_content(os.path.join('lab04_files', 'task_2', 'backups', backup4_path))

backup6_result = backups.create(os.path.join('lab04_files', 'task_2', 'backup_4.txt'))
listing3_result = sorted(backups.show())
time.sleep(1)  # to ensure that the backups will have a different timestamp

removal1_result = backups.delete(listing3_result[-1])
listing4_result = backups.show()


restore_results = []
restore_paths = [os.path.join('lab04_files', 'task_2', f'restored_{i}') for i in range(1, 5)]
backups_to_restore = listing4_result[-4:]
restore_contents = []

for backup_to_restore, restore_path in zip(backups_to_restore, restore_paths):
    os.makedirs(restore_path)
    restore_results.append(backups.restore(backup_to_restore, restore_path))
    restore_contents.append(get_folder_content(restore_path))
    shutil.rmtree(restore_path)

restore5_result = backups.restore('backup_20231225_144719', restore_paths[0])
restore6_result = backups.restore('asd', restore_paths[0])

for backup in backups.show():
    backups.delete(backup)

listing5_result = backups.show()

# Tests

# Create
assert backup1_result == True
assert backup2_result == True
assert backup3_result == True
assert backup4_result == True
assert backup5_result == False, 'Config file for backup_5 does not exist'

assert set(backup1_content) == set(['123'])
assert set(backup2_content) == set(['456'])
assert set(backup3_content) == set(['456', '4567'])
assert set(backup4_content) == set(['123', '456', 'Атака, чичо !', '4567'])

# Show
assert len(listing1_result) == len(default_listing) + 2
assert len(listing2_result) == len(default_listing) + 4
assert len(listing3_result) == len(default_listing) + 5
assert len(listing4_result) == len(default_listing) + 4
assert len(listing5_result) == len(default_listing)
assert listing5_result == default_listing

# Restore
assert restore_results[0] == True
assert restore_results[1] == True
assert restore_results[2] == True
assert restore_results[3] == True
assert restore5_result == False, 'Restore of non-existing backup should fail'
assert restore6_result == False, 'Restore of backup with invalid name should fail'


assert set(restore_contents[0]) == set(['123'])
assert set(restore_contents[1]) == set(['456'])
assert set(restore_contents[2]) == set(['456', '4567'])
assert set(restore_contents[3]) == set(['123', '456', 'Атака, чичо !', '4567'])

# Delete
for backup in listing3_result:
    assert os.path.exists(os.path.join('lab04_files', 'task_2', 'backups', backup)) == False


"✅ All OK! +4 points"