Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions Practice/KharinB/Lec 13/Task 1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Написать класс-обертку над SQLite (с возможностями менеджера контекста),
# которая может на вход принимать строки SQL запросов и возвращать данные в формате json.
# Класс должен иметь, как минимум, методы select и execute.
import sqlite3


class MySQLite:

def __init__(self, path):
self._path = path
self._name_table = None

# Методы my_execute_values, enter, exit и все методы связанные с ними были написаны мной до того как я задал Вам вопрос.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enter и exit - нужны, т.к. в задании требовался менеджер контекста.
Что касается my_execute_values, choice_tabl (да и exit в какой-то мере) - мне в них категорически не нравится ввод данных. Только представьте, как писать автотесты к таким методам!
Ввод данных в подавляющем большинстве случаев стоит отделять от обработки этих данных. Это разные задачи. Можно запрашивать данные в основном коде, в отдельных функциях и затем передавать в методы класса в качестве параметров.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enter и exit - нужны, т.к. в задании требовался менеджер контекста.
Что касается my_execute_values, choice_tabl (да и exit в какой-то мере) - мне в них категорически не нравится ввод данных. Только представьте, как писать автотесты к таким методам!
Ввод данных в подавляющем большинстве случаев стоит отделять от обработки этих данных. Это разные задачи. Можно запрашивать данные в основном коде, в отдельных функциях и затем передавать в методы класса в качестве параметров.

# Мне было просто очень жаль удалять их, поэтому я их оставил...
# Тем более, что до этого написал неправильный, но рабочий код на 100+ строк и удалил :(
# Можете, пожалуйста, глянуть эти методы и сказать что с ними не так? Они не избыточны?
# Стоит избегать таких длинных методов в практике?

def my_execute_values(self):
self._name_table = self._choice_tabl()
if not self._name_table:
print("В базе данных не существует таблиц. Невозможно установить значения.")
return None
columns = self._cur.execute(f"SELECT * FROM {self._name_table}").description
columns = tuple([i[0] for i in columns])
print(f"В таблице {self._name_table} содержатся столбцы: {columns}")
values = []
for i in columns:
value = input(f"Введите значение для столбца {i}")
values.append(value)
values = tuple(values)
self._database.execute(f"INSERT INTO {self._name_table}{str(columns)}VALUES {str(values)}")
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Код уязвим для SQL-инъекций.


def _choice_tabl(self):
# скрипт выбирает таблицу по умолчанию, для работы с ней внутри my_execute_values()
name_table = self._cur.execute("SELECT name from sqlite_master where type= 'table'").fetchall()
len_list_name = len(name_table)
match len_list_name:
case 0:
return None
case 1:
return name_table[0][0]
case _:
while True:
name = input(f"В базе данных более одной таблицы. Список таблиц: {name_table}\n"
f"Выберете таблицу с которой будут происходить дальнейшие операции: ")
if name in name_table:
return name
else:
print("Выбор непонятен")

def my_execute(self, s):
if not self.proverka(s, ["INSERT", "UPDATE", "DELETE"]):
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь и в стр.60: под неизменяемый набор команд, к которому мы применяем операцию in лучше другой тип данных использовать, не список!
И вообще этот набор команд стоит сделать константным атрибутом класса.

print("Неверная команда. Метод my_execute не возвращает значение")
return None
self._cur.execute(s)
self._database.commit()

def my_select(self, s, mod=None):
if not self.proverka(s, ["SELECT"]):
print("Неверная команда. Метод my_select возвращает значение и не сохраняет изменения в базе данных")
return None
res = self._cur.execute(s)
if mod:
res = self._mod(mod, res)
if type(res) is sqlite3.Cursor:
res = res.description
return res

@staticmethod
def _mod(mod, res):
match mod:
case "fa":
res = res.fetchall()
case "fo":
res = res.fetchone()
case "fm":
while True:
size = input("Введите максимальный размер выводимых данных, "
"или нажмите Enter для значения по умолчанию(1): ")
if size.isdecimal():
size = int(size)
break
elif size == "":
size = 1
break
else:
continue
res = res.fetchmany(size)
case _:
print("Неверно указан модификатор")
return None
return res

@staticmethod
def proverka(com, lst_com):
for i in lst_com:
if i in com:
break
else:
return False
return True

def __enter__(self):
self._database = sqlite3.connect(self._path)
self._cur = self._database.cursor()

def __exit__(self, exc_type, exc_val, exc_tb):
while True:
reply = input("Сохранить базу данных?(y/n)")
match reply:
case "y":
self._database.commit()
break
case "n":
break
case _:
print("Выбор непонятен")
self._database.close()


base = MySQLite("MyDataBase.db")
with base:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Стоит объединить строки 122 и 123 (with MySQLite("MyDataBase.db") as base:), чтоб кто-нибудь не рискнул по случайности, вставить между ними код, переопределяющий переменную base для каких-то своих целей.

base.my_execute("SELECT * FROM l")# Не выполнится
print(base.my_select("SELECT name from sqlite_master where type= 'table'", mod="fa"))
tabl_name = base.my_select("SELECT name from sqlite_master where type= 'table'", mod="fo")
print(base.my_select("SELECT name from sqlite_master where type= 'table'", mod="fm"))
if tabl_name:
print(base.my_select(f"SELECT * FROM {tabl_name[0]}"))


94 changes: 94 additions & 0 deletions Practice/KharinB/Lec 13/Task 2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Написать скрипт, работающий под SQLite/MySQL/PostgreSQL, который создает 3 сущности:
# производители, покупатели, товары. Необходимо добавить демо-данные и выполнить следующие выборки:
# вывести все товары с указанием информации о производителе
# найти все товары, которые никто не покупал
import sqlite3
import random
import os


class MyDB:

def __init__(self, path):
self._path = path
self._company = ["Кштымский Автозавод", "Autodesk", "HP", "Xaomi"]
self._name = ["Леопольд", "Матильда", "Иван", "Элеонора"]
self._title = [("Подшипники", "Пятое колесо"), ("3D max", "AutoCAD"), ("Принтер", "Ноутбук"),
("Смартфон", "Самокат")]
self._city = ["Кштымск", "Сан_Рафаэль", "Пало-Альто", "Пекин"]

def my_select(self, s):
return self._cur.execute(s).fetchall()

def company_product(self):
self._cur.execute("SELECT product.id, title, manufacturer.company, manufacturer.city FROM product "
"JOIN manufacturer ON manufacturer.company=product.company")
a = self._cur.fetchone()
while a:
print(a)
a = self._cur.fetchone()

def not_bought(self):
res = self._cur.execute("select title from product except select product from buyer").fetchall()
return res

def __enter__(self):
self._database = sqlite3.connect(self._path)
self._cur = self._database.cursor()
self._create_db()
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Код, изменяющий структуру БД (DDL) всё-таки стоит держать отдельно от DML. И уж точно не стоит его вызывать безусловно.
Понятно, что это демонстрационное решение, но даже в нем стоит соблюдать такое требование, чтоб, если что, было легко сделать его рабочим.


def _create_db(self):
self._database.execute("CREATE TABLE manufacturer"
" (id INT PRIMARY KEY NOT NULL,"
" company TEXT NOT NULL,"
" city TEXT );")
self._database.execute("CREATE TABLE buyer"
" (id INT PRIMARY KEY NOT NULL,"
" product TEXT NOT NULL,"
" name TEXT NOT NULL);")
self._database.execute("CREATE TABLE product"
" (id INT PRIMARY KEY NOT NULL,"
" title TEXT NOT NULL,"
" company TEXT NOT NULL);")
self._data_choice()

def _data_choice(self):
id = 1
for id_com in range(len(self._company)):
value = str(id_com+1), self._company[id_com], self._city[id_com]
self._database.execute(f"INSERT INTO manufacturer (id, company, city)VALUES( ?, ?, ?);", value)
for product_n in range(2):
value = str(id), self._title[id_com][product_n], self._company[id_com]
self._database.execute(f"INSERT INTO product (id, title, company)""VALUES (?, ?, ?);", value)
id += 1
for sales in range(1, random.randint(3,8)):
product = random.choice(self._title)[random.choice((0, 1))]
name = random.choice(self._name)
value = str(sales), product, name
self._database.execute("INSERT INTO buyer (id, product, name)VALUES (?, ?, ?);", value)

def __exit__(self, exc_type, exc_val, exc_tb):
while True:
reply = input("Сохранить базу данных?(y/n)")
match reply:
case "y":
self._database.commit()
self._database.close()
break
case "n":
self._database.close()
os.remove(self._path)
break
case _:
print("Выбор непонятен")




base = MyDB("MyData.db")
with base:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Строки 88 и 89 стоит объединить.

print("Сводная таблица продуктов и информации о производителе:")
base.company_product()
print("=================================================")
print(f"Товары небыли куплены: {base.not_bought()}")
pass
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass лишний