Задачи по теми 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"