CSV – это текстовый файл, который описывает табличное содержимое. Каждая строка в нем обозначает отдельную строку таблицы. Столбцы разделяются с помощью специальных разделителей. И уже программа, в которой открывается этот файл, переводит эту последовательность букв и символов в полноценную таблицу, с которой пользователь может работать.
Преимущество этого формата в том, что он универсальный, и с ним можно работать с помощью разных инструментов. В частности, и записывать/получать информацию с помощью синтаксиса Python. Сегодня рассмотрим подробно, как осуществляется работа с ним, а также с базой данных SQL. То есть, мы будем считывать данные из этой таблицы, а потом записывать их в базу данных.
Как осуществлять чтение записей из CSV-файла?
CSV-файлы очень удобно использовать для простых сценариев, если нет разрывов строк в текстовых полях. Чтение информации из таблиц этого формата осуществляется с помощью специального модуля csv, входящего в состав стандартной библиотеки Python.
После того, как мы загрузим данные, заполним ими другие элементы, которые были ранее подготовлены.
Давайте для начала их соберем. Конечный результат будет таким.
Также импортируем элементы ContactForm и ContactList из класса Contact. В результате, у нас получится следующий код.
import csv import tkinter as tk from lesson_12.structuring_data import Contact from lesson_12.widgets import ContactForm, ContactList class App(tk.Tk): def __init__(self): super().__init__() self.title("Контакты из CSV") self.list = ContactList(self, height=12) self.form = ContactForm(self) self.contacts = self.load_contacts() for contact in self.contacts: self.list.insert(contact) self.list.pack(side=tk.LEFT, padx=10, pady=10) self.form.pack(side=tk.LEFT, padx=10, pady=10) self.list.bind_doble_click(self.show_contact) def load_contacts(self): with open("contacts.csv", encoding="utf-8", newline="") as f: return [Contact(*r) for r in csv.reader(f)] def show_contact(self, index): contact = self.contacts[index] self.form.load_details(contact) if __name__ == "__main__": app = App() app.mainloop()
Принцип работы этого кода
Мы выполнили чтение данные из файла формата CSS с помощью функции load_contacts. После того, как информация была получена, она переводится в список элементов, относящихся к классу Contact. Каждая строка, которая считывается csv.reader, возвращается в форме кортежа, в состав которого входят строки файла. В качестве разделителя использовались запятые. Так, как кортеж предусматривает тот же порядок, что и в методе __init__ класса Contact, то для распаковки можно просто использовать символ *.
В результате, весь этот код упаковывается в одну строку, используя «list comprehension»:
def load_contacts(self): with open("contacts.csv", encoding="utf-8", newline="") as f: return [Contact(*r) for r in csv.reader(f)]
Не будет никаких сложностей с тем, чтобы использовать блок with для возврата списка. Все потому, что закрытие файла осуществляется автоматически после того, как метод заканчивает работу.
Как сохранить данные в базе данных SQLite
Так, как нам необходимо сохранять информацию из таблицы в программе, то необходимо разработать решение, позволяющее это делать. Причем оно должно работать не только с записью, но и чтением.
Конечно, после каждого изменения содержимого возможно банальное сохранение в файл. Правда, эффективность такого подхода оставляет желать лучшего. Особенно если необходимо изменить всего несколько строк. Приходится выполнять много лишних действий.
Так, как все данные хранятся локально, можно использовать SQLite для реализации этой задачи. Для этого необходимо использовать модуль sqlite3, который входит в стандартную библиотеку. Следовательно, нет лишних зависимостей.
Сегодня рассмотрим базовые моменты, касающиеся внедрения баз данных для сохранения информации из таблицы CSV.
Итак, перед тем, как создавать собственно базу данных, необходимо ее наполнить первоначальной информацией. Для этого пишем скрипт, позволяющий добавить данные из таблицы в базу.
Для этого выполняем связь с файлом contacts.db, который и будет содержать информацию на выходе. После этого создаем таблицу contacts, в которую входят поля last_name, first_name, email, phone.
Так, как csv.reader возвращает итерируемый объект, в состав которого входят кортежи, соответствующие порядку CREATE TALE, то можно сразу передать его в метод executemany. Так мы исполняем инструкцию INSERT для каждого кортежа, осуществляя замену вопросительных знаков теми значениями, которые реально имеются в наличии.
import csv import sqlite3 def main(): with open("contacts.csv", encoding="utf-8", newline="") as f, \ sqlite3.connect("contacts.db") as conn: conn.execute("""CREATE TABLE contacts ( last_name text, first_name text, email text, phone text )""") conn.executemany("INSERT INTO contacts VALUES (?,?,?,?)", csv.reader(f)) if __name__ == "__main__": main()
С помощью инструкции with мы выполняем автоматическое подтверждение операции и закрываем документ и соединение с базой данных после того, как фрагмент кода будет выполнен.
Работа с базой данных
Чтобы добавить новые контакты в базу данных, необходимо определить подкласс Toplevel, который будет повторно использовать контактную форму для того, чтобы создавать новые контакты.
class NewContact(tk.Toplevel): def __init__(self, parent): super().__init__(parent) self.contact = None self.form = ContactForm(self) self.btn_add = tk.Button(self, text="Подтвердить", command=self.confirm) self.form.pack(padx=10, pady=10) self.btn_add.pack(pady=10) def confirm(self): self.contact = self.form.get_details() if self.contact: self.destroy() def show(self): self.grab_set() self.wait_window() return self.contact
После того, как мы исполним этот код, у нас будет формироваться окно верхнего уровня, которое будет отображаться поверх.
Кроме этого, расширим класс ContactForm с помощью двух кнопок. Первая будет применяться для обновления данных, а вторая – для удаления контакта из базы.
class UpdateContactForm(ContactForm): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) self.btn_save = tk.Button(self, text="Сохранить") self.btn_delete = tk.Button(self, text="Удалить") self.btn_save.pack(side=tk.RIGHT, ipadx=5, padx=5, pady=5) self.btn_delete.pack(side=tk.RIGHT, ipadx=5, padx=5, pady=5) def bind_save(self, callback): self.btn_save.config(command=callback) def bind_delete(self, callback): self.btn_delete.config(command=callback)
С помощью методов bind_save и bind_delete мы можем реализовать события нажатия на кнопки.
И в конечном итоге, чтобы интегрировать все эти изменения, необходимо использовать такой код в классе App.
class App(tk.Tk): def __init__(self, conn): super().__init__() self.title("Контакты из SQLite") self.conn = conn self.selection = None self.list = ContactList(self, height=15) self.form = UpdateContactForm(self) self.btn_new = tk.Button(self, text="Добавить контакт", command=self.add_contact) self.contacts = self.load_contacts() for contact in self.contacts: self.list.insert(contact) self.list.pack(side=tk.LEFT, padx=10, pady=10) self.form.pack(padx=10, pady=10) self.btn_new.pack(side=tk.BOTTOM, pady=5) self.list.bind_doble_click(self.show_contact) self.form.bind_save(self.update_contact) self.form.bind_delete(self.delete_contact)
Помимо этого, необходимо выполнить изменение функции load_contacts, чтобы, основываясь на результатах запроса, создавать контакты.
def load_contacts(self): contacts = [] sql = "SELECT rowid, last_name, first_name, email, phone FROM contacts" for row in self.conn.execute(sql): contact = Contact(*row[1:]) contact.rowid = row[0] contacts.append(contact) return contacts def show_contact(self, index): self.selection = index contact = self.contacts[index] self.form.load_details(contact)