-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsqlite_task.py
More file actions
732 lines (636 loc) · 40.3 KB
/
Copy pathsqlite_task.py
File metadata and controls
732 lines (636 loc) · 40.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
import sqlite3
import logging
import sys
from typing import Any
from dataclasses import dataclass
from datetime import datetime
import re # для проверки автора
# Импортируем функцию настройки логирования из файла с конфигурацией
from my_logger_config import setup_logging
# Вызов функции настройки логирования (ОДИН РАЗ) при запуске программы
setup_logging()
# Получение экземпляра логгера
logger = logging.getLogger('my_app')
# 1. Создайте базу данных с именем library.db
DATABASE_NAME = 'library.db'
# Псевдоним типа для параметров запроса
QueryParams = tuple[Any, ...]
@dataclass
class Year:
"""
Класс представляющий год издания книги.
Первая в мире печатная книга была издана в Китае 11 мая 868 года ('Алмазная сутра').
"""
value: int
def __post_init__(self) -> None:
"""
Проверяет указанное пользователем значение на допустимый диапазон и тип.
:raise ValueError: Год издания должен быть целым числом.
:raise ValueError: Год издания должен быть в диапазоне от 868 до 2025.
>>> year = Year(value=1999)
>>> year.value
1999
>>> non_min_year = Year(value=800)
Traceback (most recent call last):
...
ValueError: Год издания должен быть в диапазоне от 868 до 2025. Получено: 800.
>>> in_excess_max_year = Year(value=2030)
Traceback (most recent call last):
...
ValueError: Год издания должен быть в диапазоне от 868 до 2025. Получено: 2030.
>>> type_year_non_int = Year(value='1875')
Traceback (most recent call last):
...
ValueError: Год издания должен быть целым числом. Получено: str.
"""
min_year = 868
max_year = datetime.now().year
if not isinstance(self.value, int):
logger.error(f'Год издания должен быть целым числом. Получено: {type(self.value).__name__}.')
raise ValueError(f'Год издания должен быть целым числом. Получено: {type(self.value).__name__}.')
if not min_year <= self.value <= max_year:
logger.error(f'Год издания должен быть в диапазоне от {min_year} до {max_year}. Получено: {self.value}.')
raise ValueError(
f'Год издания должен быть в диапазоне от {min_year} до {max_year}. Получено: {self.value}.')
def create_connection(db_path: str) -> sqlite3.Connection | None:
"""
Создает и возвращает подключение к базе данных SQLite.
:param db_path: str: Путь к базе данных.
:return: sqlite3.Connection | None: Объект подключения к базе данных, если подключение успешно, иначе None.
>>> conn = create_connection(":memory:")
>>> isinstance(conn, sqlite3.Connection)
True
"""
try:
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
logger.info(f'Успешное подключение к базе данных: {db_path}')
return conn
except sqlite3.Error as e:
logger.error(f'Ошибка при подключении к базе данных "{db_path}": {e}', exc_info=True)
return None
def initialize_database(db_path: str, conn: sqlite3.Connection | None = None) -> bool:
"""
Инициализирует базу данных и создает таблицу books.
:param db_path: str: Путь к базе данных.
:param conn: sqlite3.Connection | None: Существующее соединение, если передали с вызовом функции, иначе None.
"""
_conn = conn
close_conn = False
if _conn is None:
_conn = create_connection(db_path)
close_conn = True
if _conn:
try:
cursor = _conn.cursor()
# SQL-запрос для создания таблицы
create_table_books: str = '''
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
author TEXT NOT NULL,
year INTEGER
);
'''
# Выполнение SQL-запроса
cursor.execute(create_table_books)
# CREATE TABLE требует commit()
if close_conn:
_conn.commit()
logger.info(f'Таблица "books" проверена/создана в {db_path}.')
return True
except sqlite3.Error as e:
logger.error(f'Ошибка при инициализации базы данных {db_path}: {e}', exc_info=True)
# Откатываем изменения в случае ошибки, если мы открывали соединение
if close_conn and _conn:
_conn.rollback()
return False
# Закрываем соединение с БД, если мы его открывали, независимо от результата выполнения функции
finally:
if close_conn and _conn:
_conn.close()
return False
def execute_query(
conn_or_path: sqlite3.Connection | str,
query: str,
params: QueryParams = ()) -> list[sqlite3.Row] | int | bool | None:
"""
Выполняет SQL-запрос, имеет возможность передавать существующее соединение
:param conn_or_path: sqlite3.Connection | str: Объект подключения к базе данных SQLite или путь к базе данных.
:param query: str: Строка SQL-запроса.
:param params: QueryParams: Кортеж параметров для безопасной подстановке в запрос.
:return: list[sqlite3.Row] | int | bool | None: Список объектов Row, ID добавленной книги, True для DDL(и др.)
или количество затронутых строк удалением или обновлением книги,
после выполнения запроса, если запрос успешен.
Возвращает None в случае ошибки выполнения запроса или отсутствия соединения.
>>> conn = create_connection(":memory:")
>>> create_table_query = '''
... CREATE TABLE IF NOT EXISTS test_books (
... id INTEGER PRIMARY KEY AUTOINCREMENT,
... title TEXT NOT NULL,
... author TEXT NOT NULL,
... year INTEGER
... );
... '''
>>> cursor = execute_query(conn, create_table_query)
>>> cursor is not None
True
>>> insert_query = "INSERT INTO test_books (title, author, year) VALUES (?, ?, ?)"
>>> cursor = execute_query(conn, insert_query, ('Book_1', 'Author_1', 1789))
>>> cursor is not None
True
>>> conn.commit() # commit после INSERT для сохранения и видимости изменений
>>> select_query = "SELECT title, author, year FROM test_books WHERE id = ?"
>>> cursor = execute_query(conn, select_query, (1, ))
>>> cursor is not None
True
>>> row = [dict(row) for row in cursor]
>>> row[0] # sqlite3.Row ведет себя как dict
{'title': 'Book_1', 'author': 'Author_1', 'year': 1789}
>>> # Пример ошибочного запроса
>>> error_query = "SELECT * FROM non_exists_table"
>>> cursor = execute_query(conn, error_query)
>>> cursor is None
True
>>> conn.close()
"""
_conn = None
# Флаг, чтобы знать, нужно ли закрыть соединение
close_conn = False
if isinstance(conn_or_path, sqlite3.Connection):
# Соединение получено извне, commit/rollback/close - выполняется в основной функции main()
_conn = conn_or_path
else:
# Передан путь к БД, а не существующее соединение, поэтому создаем соединение самостоятельно
_conn = create_connection(conn_or_path)
# Мы открыли соединение, поэтому мы и закрываем
close_conn = True
if _conn:
try:
cursor = _conn.cursor()
logger.debug(f'Выполнение запроса: {query} с параметрами: {params}.')
cursor.execute(query, params)
# Если cursor.description не None, это SELECT запрос
if cursor.description:
result_data = cursor.fetchall()
logger.debug('SQL SELECT запрос выполнен, данные получены.')
return result_data
# commit нужен, если запрос не SELECT и мы самостоятельно открыли соединение
else:
if close_conn:
_conn.commit()
logger.info('Запрос успешно выполнен и сохранен.')
# Определяем тип запроса для возврата соответствующей информации
type_query = query.strip().upper()
if type_query.startswith('INSERT'):
# Возвращаем int ID добавленной книги
return cursor.lastrowid
elif type_query.startswith(('UPDATE', 'DELETE')):
# Возвращает int количество затронутых строк
return cursor.rowcount
# DDL или другие запросы без специфического возвращаемого значения
else:
logger.debug(f'SQL DDL или другой запрос выполнен: {query}.')
return True
except sqlite3.Error as e:
logger.error(f'Ошибка при выполнении запроса: {query} с параметрами: {params}: {e}', exc_info=True)
# Откатываем изменения в случае ошибки, если мы открывали соединение
if close_conn and _conn:
_conn.rollback()
return None
# Закрываем соединение с БД, если мы его открывали, независимо от результата выполнения функции
finally:
if close_conn and _conn:
_conn.close()
return None
# 3. Реализуйте функции для выполнения следующих операций:
# Добавление новой книги:
# Функция должна принимать название книги, автора и год издания и добавлять новую запись в таблицу books.
def add_book(
db_path: str,
title: str,
author: str,
year: int | None = None,
conn: sqlite3.Connection | None = None) -> int | None:
"""
Добавляет новую книгу в таблицу books.
:param db_path: str: Путь к базе данных.
:param title: str: Название книги.
:param author: str: Автор книги.
:param year: int | None: Год издания книги (опционально).
:param conn: sqlite3.Connection | None: Существующее соединение, если передали с вызовом функции, иначе None.
:return: int | None: ID только что добавленной книги, если успешно, иначе None.
"""
new_book_id: int | None = None
# SQL-запрос для добавления книги в таблицу
sql_query: str = '''INSERT INTO books (title, author, year) VALUES (?, ?, ?)'''
try:
new_book_id = execute_query(conn if conn else db_path, sql_query, (title, author, year))
# Проверка полученного значения
if new_book_id is None:
logger.error(f'Не удалось выполнить запрос на добавление книги "{title}" (автор : {author}).')
return None
logger.info(
f'Книга "{title}" (автор : {author}, год издания : {year}) успешно добавлена с ID: {new_book_id}.')
except sqlite3.Error as e:
logger.error(f'Ошибка при добавлении книги в таблицу: {e}.', exc_info=True)
return None
return new_book_id
# Получение всех книг:
# Функция должна возвращать список всех книг, хранящихся в базе данных.
def get_all_books(db_path: str, conn: sqlite3.Connection | None = None) -> None | list[sqlite3.Row]:
"""
Возвращает список всех книг из таблицы books.
:param db_path: str: Путь к базе данных.
:param conn: sqlite3.Connection | None: Существующее соединение, если передали с вызовом функции, иначе None.
:return: list[sqlite3.Row] | None: Список всех книг из таблицы, или None.
"""
books: list[sqlite3.Row] | None = None
# SQL-запрос для получения списка всех книг
sql_query: str = "SELECT id, title, author, year FROM books"
try:
books = execute_query(conn if conn else db_path, sql_query)
# Проверка полученных данных
if books is None:
logger.warning('Не удалось выполнить запрос на получение всех книг.')
return None
num_books = len(books)
if num_books == 0:
logger.info(f'На данный момент в таблице 0 книг.')
else:
logger.info(f'Получено {num_books} книг из базы данных.')
except sqlite3.Error as e:
logger.error(f'Ошибка при получении списка книг: {e}.', exc_info=True)
return None
return books
# Обновление информации о книге:
# Функция должна принимать идентификатор книги и новые данные (название, автора, год издания)
# и обновлять соответствующую запись в таблице.
def update_book(
db_path: str,
book_id: int,
title: str | None = None,
author: str | None = None,
year: int | None = None,
conn: sqlite3.Connection | None = None) -> bool:
"""
Обновляет информацию о книге по ее ID.
:param db_path: str: Путь к базе данных.
:param book_id: int: Идентификатор книги.
:param title: str | None: Новое название книги (опционально).
:param author: str | None: Новый автор книги (опционально).
:param year: int | None: Новый год издания книги (опционально).
:param conn: sqlite3.Connection | None: Существующее соединение, если передали с вызовом функции, иначе None.
:return: bool: True - если книга успешно обновлена, False - если произошла ошибка или книга не найдена.
"""
updates: list[str] = []
params: list[str | int] = []
updated_successfully: bool = False
if title is not None:
updates.append('title = ?')
params.append(title)
if author is not None:
updates.append('author = ?')
params.append(author)
if year is not None:
updates.append('year = ?')
params.append(year)
if not updates:
logger.warning(f'Попытка обновления книги с ID {book_id} не удалась. Не переданы новые данные.')
return False
# SQL-запрос для обновления информации об указанной книге
sql_query: str = f"UPDATE books SET {', '.join(updates)} WHERE id = ?"
params.append(book_id)
try:
rows_affected = execute_query(conn if conn else db_path, sql_query, tuple(params))
# Проверка полученного значения
if rows_affected is None:
logger.error(f'Не удалось выполнить запрос на обновление информации о книге с ID {book_id}.')
return False
# Проверяем, сколько строк было затронуто обновлением
if rows_affected > 0:
logger.info(f'Информация о книге с ID {book_id} успешно обновлена.')
updated_successfully = True
else:
logger.warning(f'Попытка обновления книги с ID {book_id} не удалась. Книга не найдена.')
except sqlite3.Error as e:
logger.error(f'Ошибка при обновлении информации о книге с ID {book_id}: {e}', exc_info=True)
return False
return updated_successfully
# 5. Реализуйте функцию для удаления книги по ее идентификатору и добавьте соответствующий тест в основной код.
def delete_book(db_path: str, book_id: int, conn: sqlite3.Connection | None = None) -> bool:
"""
Удаляет книгу из таблицы book по ID.
:param db_path: str: Путь к базе данных.
:param book_id: int: Идентификатор книги.
:param conn: sqlite3.Connection | None: Существующее соединение, если передали с вызовом функции, иначе None.
:return: bool: True - если книга успешно удалена, False - если произошла ошибка или книга не найдена.
"""
deleted_successfully: bool = False
sql_query: str = "DELETE FROM books WHERE id = ?"
try:
rows_affected = execute_query(conn if conn else db_path, sql_query, (book_id,))
# Проверка полученного значения
if rows_affected is None:
logger.error(f'Не удалось выполнить запрос на удаление книги с ID {book_id}.')
return False
# Проверяем, была ли удалена книга
if rows_affected > 0:
logger.info(f'Книга с ID {book_id} успешно удалена.')
deleted_successfully = True
else:
logger.warning(f'Попытка удаления книги с ID {book_id} не удалась. Книга не найдена.')
except sqlite3.Error as e:
logger.error(f'Ошибка при удалении книги с ID {book_id}: {e}.')
return False
return deleted_successfully
# Вспомогательная функция для получения валидированного года издания книги от пользователя
def get_year_input(prompt: str) -> Year | None:
"""
Запрашивает у пользователя год издания книги, используя класс Year для валидации.
Поле года издания может быть пустым.
:param prompt: Сообщение для пользователя.
:return: Year | None: Валидированный объект Year или None, если ввод был пустым.
:raise ValueError: Год издания должен быть целым числом.
>>> # Корректный ввод
>>> get_year_input('Введите год издания книги: ') # doctest: +SKIP
Введите год издания книги: 1789
Year(value=1789)
>>> # Введена пустая строка
>>> get_year_input('Введите год издания книги: ') # doctest: +SKIP
Введите год издания книги:
None
>>> # Введено нецелочисленное значение
>>> try:
... get_year_input('Введите год издания книги: ') # doctest: +SKIP
... except ValueError as e:
... print(e)
Введите год издания книги: abc
Год издания должен быть целым числом. Получено: abc
>>> # Введен год не соответствующий диапазону
>>> try:
... get_year_input('Введите год издания книги: ') # doctest: +SKIP
... except ValueError as e:
... print(e)
Введите год издания книги: 2029
Год издания должен быть в диапазоне от 868 до 2025. Получено: 2029
"""
year_str = input(prompt).strip()
# Если год издания не указан, будет None
if not year_str:
return None
# Год издания должен быть целым числом, чтобы попасть в Year,
# если это не так, int() выбросить ValueError
try:
year_int = int(year_str)
except ValueError:
raise ValueError(f'Год издания должен быть целым числом. Получено: {year_str}')
# Пробуем создать объект Year, позволяя __post_init__ обработать валидацию диапазона
# Если Year выбросит ValueError (из-за диапазона), main его перехватит
year_obj = Year(value=year_int)
return year_obj
# Вспомогательная функция для получения валидированного ID книги от пользователя
def get_id_input(prompt: str) -> int:
"""
Запрашивает у пользователя ID книги и преобразовывает в тип int.
:param prompt: Сообщение для пользователя.
:return: int: ID книги.
:raise ValueError: Введена пустая строка.
:raise ValueError: ID книги должен быть целым числом.
>>> # Корректный ввод ID
>>> get_id_input('Введите ID книги: ') # doctest: +SKIP
Введите ID книги: 4
4
>>> # Введена пустая строка
>>> try:
... get_id_input('Введите ID книги: ') # doctest: +SKIP
... except ValueError as e:
... print(e)
Введите ID книги:
Введена пустая строка
>>> # Введено нецелочисленное значение
>>> try:
... get_id_input('Введите ID книги: ') # doctest: +SKIP
... except ValueError as e:
... print(e)
Введите ID книги: abc
ID книги должен быть целым числом. Получено: abc
"""
# id пользователя не может быть пустым, выбрасываем исключение в случае пустой строки
id_str = input(prompt).strip()
if not id_str:
raise ValueError('Введена пустая строка')
# id пользователя должен быть целым числом, если преобразовать int() невозможно, выбрасывается исключение
try:
id_int = int(id_str)
return id_int
except ValueError:
raise ValueError(f'ID книги должен быть целым числом. Получено: {id_str}')
# 4. Напишите тестовый код, который демонстрирует работу всех функций.
def main():
"""
Основная функция программы.
Взаимодействие с пользователем через запросы на выбор действия, названия книги, автора, года издания.
"""
logger.info('Программа "sqlite_task" запущена.')
conn = None
try:
conn = create_connection(DATABASE_NAME)
if conn is None:
print('Не удалось установить соединение с БД для этой сессии.')
# 2. Создаем таблицу books и подключение к БД
initialize_database(DATABASE_NAME, conn=conn)
while True:
print('\nВыберите действие:')
print('1 - Добавить книгу')
print('2 - Получить список всех книг')
print('3 - Обновить информацию о книге')
print('4 - Удалить книгу по ID')
print('0 - Выход из программы.')
choice = input('Введите номер действия: ')
if choice == '0':
print('Вы выбрали выход из программы.')
logger.info(" Программа 'sqlite_task' завершена по желанию пользователя.")
break
# Добавление новой книги
elif choice == '1':
print('Вы выбрали добавить книгу.')
# Запрос и проверка названия книги
title = input('Введите название книги: ').strip()
if not title:
print('Название книги не может быть пустым.')
logger.warning('Поле "title" не может быть пустым. Функция "Добавить книгу" не выполнена.')
continue
# Запрос и проверка автора книги
author = input('Введите автора книги: ').strip()
if not author:
print('Автор не может быть пустым.')
logger.warning('Поле "author" не может быть пустым. Функция "Добавить книгу" не выполнена.')
continue
# Проверка на отсутствие цифр с использованием регулярных выражений.
# Ищет любую цифру в строке
if re.search(r'\d', author):
print('Автор не может содержать цифры.')
logger.warning('Поле "author" не может содержать цифры. Функция "Добавить книгу" не выполнена.')
continue
# Запрос и проверка года издания книги (опционально)
try:
year_obj = get_year_input('Введите год издания книги (оставьте пустым для пропуска): ')
except ValueError as v:
print(f'Ошибка ввода года издания: {v}')
logger.warning(f'Ошибка в получении года издания: {v}. Функция "Добавить книгу" не выполнена.')
continue
except Exception as e:
print(f'Непредвиденная ошибка при вводе года издания: {e}')
logger.error(f'Непредвиденная ошибка в получении года издания: {e}.'
f' Функция "Добавить книгу" не выполнена.')
continue
# Извлекаем int значение из объекта Year, если не None, иначе None
year = year_obj.value if year_obj else None
# 'Мастер и Маргарита', 'Михаил Булгаков', 1967
book_id = add_book(DATABASE_NAME, title, author, year, conn=conn)
if book_id is not None:
print('Книга успешно добавлена!')
# Сохраняем изменения, если соединение было открыто в main()
if conn:
conn.commit()
logger.info(" Функция 'Добавить книгу' выполнена успешно.")
else:
print('Не удалось добавить книгу. Проверьте логи для подробной информации.')
# Откатываем в случае ошибки, если соединение было открыто в main()
if conn:
conn.rollback()
logger.warning(f'Ошибка при добавлении книги. Функция "Добавить книгу" не выполнена.')
# Получение списка всех книг
elif choice == '2':
print('Вы выбрали получить список всех книг.')
# Получаем список книг
books = get_all_books(DATABASE_NAME, conn=conn)
# Проверяем результат выполнения функции
if books is None:
print('Произошла ошибка в получении списка книг. Проверьте логи для подробной информации.')
logger.warning('Произошла ошибка в получении списка книг.'
' Функция "Получить список всех книг" не выполнена.')
continue
# Если книги найдены, выводим их количество и сами книги
elif len(books) > 0:
print(f'Найдено книг: {len(books)}.')
for book in books:
print(f'Книга с ID {book['id']}: {book['title']} {book['author']} {book['year']}')
logger.info(
'Функция "Получить список всех книг" выполнена успешно (найдено книг: {}).'.format(len(books)))
# Если книг нет, выводим соответствующее сообщение, функция выполнена, поэтому continue не требуется
else:
print('В базе данных пока нет ни одной книги.')
logger.info('Функция "Получить список всех книг" выполнена, но книг в базе данных не найдено.')
# Обновление информации о книге
elif choice == '3':
print('Вы выбрали обновить информацию о книге.')
# Запрос данных для обновления информации о книге
try:
book_id_update = get_id_input('Введите ID книги для обновления: ')
except ValueError as v:
print(f'Ошибка ввода ID книги: {v}')
logger.warning(f'Ошибка в получении ID книги: {v}.'
f' Функция "Обновить информацию о книге" не выполнена.')
continue
except Exception as e:
print(f'Непредвиденная ошибка при вводе ID книги: {e}')
logger.error(f'Непредвиденная ошибка в получении ID книги: {e}.'
f' Функция "Обновить информацию о книге" не выполнена.')
continue
title_update = input('Введите новое название книги (оставьте пустым для пропуска): ').strip()
author_update = input('Введите нового автора (оставьте пустым для пропуска): ').strip()
if author_update and re.search(r'\d', author_update):
print('Автор не может содержать цифры.')
logger.warning('Поле "author" не может содержать цифры. Функция "Добавить книгу" не выполнена.')
continue
try:
year_obj_update = get_year_input('Введите новый год издания книги (оставьте пустым для пропуска): ')
except ValueError as v:
print(f'Ошибка ввода года издания: {v}')
logger.warning(f'Ошибка в получении года издания: {v}.'
f' Функция "Обновить информацию о книге" не выполнена.')
continue
except Exception as e:
print(f'Непредвиденная ошибка при вводе года издания: {e}')
logger.error(
f'Непредвиденная ошибка в получении года издания: {e}.'
f' Функция "Обновить информацию о книге" не выполнена.')
continue
year_update = year_obj_update.value if year_obj_update else None
title_update = title_update if title_update else None
author_update = author_update if author_update else None
updated_successfully = update_book(DATABASE_NAME, book_id_update, title_update, author_update,
year_update, conn=conn)
if not updated_successfully:
print(f'Произошла ошибка при обновлении информации о книге с ID {book_id_update}.'
f' Проверьте логи для подробной информации.')
# Откатываем в случае ошибки, если соединение было открыто в main()
if conn:
conn.rollback()
logger.warning('Произошла ошибка при обновлении информации о книге.'
' Функция "Обновить информацию о книге" не выполнена.')
continue
print(f'Информация о книге с ID {book_id_update} успешно обновлена.')
# Сохраняем изменения, если соединение было открыто в main()
if conn:
conn.commit()
logger.info('Функция "Обновить информацию о книге" выполнена успешно.')
elif choice == '4':
print('Вы выбрали удалить книгу по ID.')
# Запрос ID книги для удаления
try:
book_id_delete = get_id_input('Введите ID книги для удаления: ')
except ValueError as v:
print(f'Ошибка ввода ID книги: {v}')
logger.warning(f'Ошибка в получении ID книги: {v}.'
f' Функция "Удалить книгу по ID" не выполнена.')
continue
except Exception as e:
print(f'Непредвиденная ошибка при вводе ID книги: {e}')
logger.error(f'Непредвиденная ошибка в получении ID книги: {e}.'
f' Функция "Удалить книгу по ID" не выполнена.')
continue
deleted_successfully = delete_book(DATABASE_NAME, book_id_delete, conn=conn)
if not deleted_successfully:
print(f'Произошла ошибка при удалении книги с ID {book_id_delete}.'
f' Проверьте логи для подробной информации.')
# Откатываем в случае ошибки, если соединение было открыто в main()
if conn:
conn.rollback()
logger.warning('Произошла ошибка при удалении книги.'
' Функция "Удалить книгу по ID" не выполнена.')
continue
print(f'Книга с ID {book_id_delete} успешно удалена.')
# Сохраняем изменения, если соединение было открыто в main()
if conn:
conn.commit()
logger.info('Функция "Удалить книгу по ID" выполнена успешно.')
else:
print('Неверный выбор, попробуйте снова.')
logger.warning('Неверный выбор, неизвестный номер действия. Попробуйте снова.')
except ValueError as v:
print(f'Ошибка ввода данных: {v}')
# Откатываем все незафиксированные изменения в случае глобальной ошибки
if conn:
conn.rollback()
logger.error(f'Ошибка ввода данных: {v}')
logger.info(" Программа 'sqlite_task' завершена с ошибкой.")
sys.exit(1)
except Exception as e:
print(f'Произошла критическая ошибка: {e}')
print("Подробности ошибки записаны в файл 'error.log'")
# Откатываем все незафиксированные изменения в случае глобальной ошибки
if conn:
conn.rollback()
logger.critical(f'Критическая ошибка в работе программы: {e}', exc_info=True)
logger.info(" Программа 'sqlite_task' завершена с ошибкой.")
sys.exit(1)
finally:
if conn:
conn.close()
print('Соединение с базой данных закрыто.')
logger.info(f'Соединение с базой данных {DATABASE_NAME} закрыто.')
if __name__ == '__main__':
main()