1 Востаннє редагувалося plusxx (26.12.2019 11:09:31)

Тема: Класс для роботи sqlite подивіться будьласка.

Прошу тих хто шарить. Подивіться будьласка на класс для роботи sqlite чи він може рахуватись адекватно написаним ?
Що треба поправити

# -*- coding: utf-8 -*-
import sqlite3

class SQLighte():

    def __init__(self, database = "users.db", telegram_id):
        self.connection = sqlite3.connect(database)
        self.cursor = self.connection.cursor()#open database
        self.table_name = "a"+telegram_id#Ім'я таблиці в sqlite мусить починатись злітери
    def create_table(self):#Створює таблицю в базі данних для кожного нового юзера де table_name id користувача в телеграм
        try:
            self.cursor.execute(f"CREATE TABLE {self.table_name} (id integer, name text, satiety integer, satiety_cykle integer, bag text, emotion text, despair integer, pers_history text)")
            return(True)
        except sqlite3.OperationalError: # ...якщо юзер є в базі данних то ...
            return(False)

    def deleted_table(self):#Видаляєм таблицю з бази
        try:
            self.cursor.execute(f"DROP TABLE {self.table_name}")
            return(True)
        except sqlite3.OperationalError: # таблиця з іменем не знайдена
            print("No such user was found")
            return(False)

    def write_starting_data_to_table(self, data):#Запис початкових данних для окремрго користувача до бази данних
        numbers = range(len(data))
        with self.connection:
            try:
                for number in numbers:
                    self.cursor.execute(f"INSERT INTO {self.table_name} Values(?,?,?,?,?,?,?,?)", ( data[number][0], data[number][1], data[number][2], data[number][3], data[number][4],data[number][5], data[number][6], data[number][7], ))
                    return True:
            except IndexError:
                return False)


    def select_single(self, rownumber, column, xdata):#Получаєм рядок з таблиці table_name в котрому в колонці column значення==xdata
        with self.connection:
            try:
                return self.cursor.execute(f"SELECT * FROM {self.table_name} WHERE {column} = ?", (xdata,)).fetchall()[0]
            except IndexError :
                return(None)

    def edit_user_data(self,replace_column, replace_data, condition_column, condition_data):#Невпевненмй що ця функція робоча
    """
        replace_column - стовбець в якому буде перезаписано дані
        replace_data - нові дані котрі треба записати в таблицю
        condition_column - стовбець уова має дорівнювати с condition_data(дані для порівняння)

    """
        with self.connection:
            try:
                self.cursor.execute(f"UPDATE {self.table_name} SET{replace_column}={replace_data} WHERE {condition_column}={condition_data}"
                return(True)
            except IndexError & sqlite3.OperationalError:
                return(False)

    def __del__(self):#Зберігаєм зміни та закриваєм поточне з'єднання з БД """
        self.connection.commit()
        self.connection.close()

.

2

Re: Класс для роботи sqlite подивіться будьласка.

Для початку - ви цей код запускали?

3

Re: Класс для роботи sqlite подивіться будьласка.

koala написав:

Для початку - ви цей код запускали?

Так крім функції edit_user_data() ще не дописав її виклик з основного коду.

4

Re: Класс для роботи sqlite подивіться будьласка.

pluszz написав:
koala написав:

Для початку - ви цей код запускали?

Так крім функції edit_user_data() ще не дописав її виклик з основного коду.

Дивно, бо в мене він просто не парситься - наприклад,

return True:

у write_starting_data_to_table.
Яка у вас версія Python? Може, якісь нестандартні розширення?

Подякували: plusxx1

5

Re: Класс для роботи sqlite подивіться будьласка.

koala написав:
pluszz написав:
koala написав:

Для початку - ви цей код запускали?

Так крім функції edit_user_data() ще не дописав її виклик з основного коду.

Дивно, бо в мене він просто не парситься - наприклад,

return True:

у write_starting_data_to_table.
Яка у вас версія Python? Може, якісь нестандартні розширення?

От блін я його запускав коли не було ексептів оце я нафігачив. Коли буду біля комп'ютера перезалью адекватний код. Якби сказав один пан але я й наркоман.

6

Re: Класс для роботи sqlite подивіться будьласка.

Далі - код не буває "адекватним" сам по собі; він може бути адекватним певним обставинам, а ви про ці обставини ніц не написали. Наприклад, якщо вся програма - це скриптик на колінці на 10 рядків, а задля нього ви створюєте такий клас - він, схоже, буде неадекватним. Так само якщо у вас є чітко виділений бізнес-код з певними вимогами до адаптера БД, він може бути неадекватним цим вимогам, але ж ви про це нічого не пишете.
Наступне питання - структура БД у вас неадекватна усім розумним вимогам до БД. Ви, як я зрозумів, замість зберігати однотипні дані в одній  таблиці, створюєте різні таблиці з однаковою структурою. Нащо ви це робите?

А тепер  по коду:
- що за назва "SQLighte"? Що вона означає? Просто випадковий набір символів, що, на вашу думку, має вимовлятися співзвучно з назвою БД? Назви мають відповідати змісту.
- про структуру БД вже писав;
- якщо CREATE впаде з іншої причини - наприклад, через втрату зв'язку, то чи це буде оброблено коректно? До речі, мануал каже, що коли таблиця вже існує, то це буде ProgrammingError. Коментар не відповідає реальності, помилка обробляється косо-криво.
- deleted_table - це минулий час. Функція перевіряє, чи було таблицю видалено? Ні. Назва має відповідати дії. До речі, ви пишете то "юзер", то "таблиця". Визначиться з термінологією - або ви працюєте в термінах вашої програми (тоді "юзер"), або в термінах БД (тоді "таблиця"), або ви робите перехідник-адаптер з однієї термінології на іншу, тоді в назвах функцій має бути внутрішня термінологія програми, а всередині функцій - зовнішня термінологія БД.
- select_single - ви певні, що знаєте, що саме робить with? Вам точно треба закривати з'єднання після виконання цієї функції?
А ще ви в курсі, як працює for у Python? Що range - це костиль для перебору чисел, спеціально введений, щоб списки так не перебирали? Елементи data точно правильного розміру, може, варто було б передавати не tuple-и, а якісь структури з внутрішньої логіки? А якщо правильного, то нащо ви розбираєте data[number] на шматки, з яких збираєте новий tuple? Ваш код можна записати так:
for sample in data:
    self.cursor.execute(f"INSERT INTO {self.table_name} Values(?,?,?,?,?,?,?,?)", sample)
і це - за умови, що return ви поставили неправильно, бо якщо правильно, то вам цикл взагалі не потрібен, він же лише один раз виконається.
Але це теж не дуже правильно, бо SQLite має також функцію executemany, що дозволяє взагалі уникнути циклу (точніше, вона сама містить цей цикл з додатковими оптимізаціями).

except IndexError & sqlite3.OperationalError:

Будь ласка, відкрийте підручник і прочитайте, що там написано. Або погугліть.

Подякували: plusxx, varkon, Eff1c3

7

Re: Класс для роботи sqlite подивіться будьласка.

pluszz написав:

Якби сказав один пан але я й наркоман.

Це той випадок, коли я б не робив йому зауваження.

Подякували: plusxx1

8

Re: Класс для роботи sqlite подивіться будьласка.

koala написав:
Прихований текст

Далі - код не буває "адекватним" сам по собі; він може бути адекватним певним обставинам, а ви про ці обставини ніц не написали. Наприклад, якщо вся програма - це скриптик на колінці на 10 рядків, а задля нього ви створюєте такий клас - він, схоже, буде неадекватним. Так само якщо у вас є чітко виділений бізнес-код з певними вимогами до адаптера БД, він може бути неадекватним цим вимогам, але ж ви про це нічого не пишете.
Наступне питання - структура БД у вас неадекватна усім розумним вимогам до БД. Ви, як я зрозумів, замість зберігати однотипні дані в одній  таблиці, створюєте різні таблиці з однаковою структурою. Нащо ви це робите?

А тепер  по коду:
- що за назва "SQLighte"? Що вона означає? Просто випадковий набір символів, що, на вашу думку, має вимовлятися співзвучно з назвою БД? Назви мають відповідати змісту.
- про структуру БД вже писав;
- якщо CREATE впаде з іншої причини - наприклад, через втрату зв'язку, то чи це буде оброблено коректно? До речі, мануал каже, що коли таблиця вже існує, то це буде ProgrammingError. Коментар не відповідає реальності, помилка обробляється косо-криво.
- deleted_table - це минулий час. Функція перевіряє, чи було таблицю видалено? Ні. Назва має відповідати дії. До речі, ви пишете то "юзер", то "таблиця". Визначиться з термінологією - або ви працюєте в термінах вашої програми (тоді "юзер"), або в термінах БД (тоді "таблиця"), або ви робите перехідник-адаптер з однієї термінології на іншу, тоді в назвах функцій має бути внутрішня термінологія програми, а всередині функцій - зовнішня термінологія БД.
- select_single - ви певні, що знаєте, що саме робить with? Вам точно треба закривати з'єднання після виконання цієї функції?
А ще ви в курсі, як працює for у Python? Що range - це костиль для перебору чисел, спеціально введений, щоб списки так не перебирали? Елементи data точно правильного розміру, може, варто було б передавати не tuple-и, а якісь структури з внутрішньої логіки? А якщо правильного, то нащо ви розбираєте data[number] на шматки, з яких збираєте новий tuple? Ваш код можна записати так:
for sample in data:
    self.cursor.execute(f"INSERT INTO {self.table_name} Values(?,?,?,?,?,?,?,?)", sample)
і це - за умови, що return ви поставили неправильно, бо якщо правильно, то вам цикл взагалі не потрібен, він же лише один раз виконається.
Але це теж не дуже правильно, бо SQLite має також функцію executemany, що дозволяє взагалі уникнути циклу (точніше, вона сама містить цей цикл з додатковими оптимізаціями).

except IndexError & sqlite3.OperationalError:

Будь ласка, відкрийте підручник і прочитайте, що там написано. Або погугліть.

Вдячний за розвернути відповідь прийму до уваги і виправлю.
P.S. Це не бізнес код але і не скрипник на 10 рядків.
Я не проф і навіть без освіти тому мене важко образити. Всю критику приймаю позитивно.

9 Востаннє редагувалося plusxx (26.12.2019 12:42:18)

Re: Класс для роботи sqlite подивіться будьласка.

Питання в основному до пана koaly, але якщо хтось ще може порадити буду вдячний.

До речі, мануал каже, що коли таблиця вже існує, то це буде ProgrammingError

я не читав мануал(поки що ,мені реально соромно)але в мене в консолі вибивало саме помилку OperationalError тому я її й перехоплював чи є сенс перехоплювати обі помилки? Які ще помилки варто лапати. Дякую.

10

Re: Класс для роботи sqlite подивіться будьласка.

# -*- coding: utf-8 -*-
import sqlite3

class DataBaseFunctional():

    def __init__(self, telegram_id, database = "users.db"):
        self.connection = sqlite3.connect(database)
        self.cursor = self.connection.cursor()#open database
        self.table_name = "a"+telegram_id#Ім'я таблиці в sqlite мусить починатись злітери

    def create_table(self):#Створює таблицю в базі данних для кожного нового юзера де table_name id користувача в телеграм
        try:
            self.cursor.execute(f"CREATE TABLE {self.table_name} (id integer, name text, satiety integer, satiety_cykle integer, bag text, emotion text, despair integer, pers_history text)")
            return(True)
        except sqlite3.OperationalError:
            return(False)

    def delete_table(self):#Видаляєм таблицю з бази
        try:
            self.cursor.execute(f"DROP TABLE {self.table_name}")
            return(True)
        except sqlite3.OperationalError:
            print("No such user was found")
            return(False)

    def write_starting_data_to_table(self, data):#Запис початкових данних для окремрго користувача до бази данних
            try:
                self.cursor.executemany(f"INSERT INTO {self.table_name} Values(?,?,?,?,?,?,?,?)", data)
                return (True)
            except sqlite3.OperationalError:
                return (False)


    def select_single(self, rownumber, column, xdata):#Получаєм рядок з таблиці table_name в котрому в колонці column значення==xdata
            try:
                return self.cursor.execute(f"SELECT * FROM {self.table_name} WHERE {column} = {xdata}.").fetchall()[0]
            except sqlite3.OperationalError:
                return(None)

    def edit_data_in_table(self, replace_column, replace_data, condition_column, condition_data):#Невпевненмй що ця функція робоча
        try:
            self.cursor.execute(f"UPDATE a979674210 SET {replace_column}={replace_data} WHERE {condition_column}={condition_data}")
            return(True)
        except sqlite3.OperationalError:
            return(False)

    def __del__(self):#Зберігаєм зміни та закриваєм поточне з'єднання з БД """
        self.connection.commit()
        self.connection.close()

Поправив. Лише неможу зрозуміти як правильно обробити помилки.

11

Re: Класс для роботи sqlite подивіться будьласка.

Назва така сама безглузда. "Функціонал", ага. DataBaseFunctionalObjectToDoSomething.
Структура БД лишилася тією ж.
select_single - насправді, занадто загальна функція. Вам, швидше за все, треба шукати за певними конкретними ознаками, а ви робите функцію, що шукає що завгодно і повертає лише перший результат, із тим самим успіхом можна прямо запити передавати без зайвого класу.

Як обробити помилки - залежить від іншого коду. Як ви плануєте використовувати цей клас? Можете навести приклад коду, що його застосовує?

Подякували: plusxx1

12 Востаннє редагувалося plusxx (26.12.2019 17:48:14)

Re: Класс для роботи sqlite подивіться будьласка.

koala написав:

Назва така сама безглузда. "Функціонал", ага. DataBaseFunctionalObjectToDoSomething.

над цим подумаю

koala написав:

Структура БД лишилася тією ж.

Проект сам по собі являє тексовий квест для телеграм https://replace.org.ua/topic/11448/
Спочатку я думав робити кілька таблиць в одну додавати  користувачів в іншу ігрові об'єкти потім записувати вних зміни. Потім подумав і вирішив що можна створювати окрему таблицю для кожного гравця. В котру будуть заноситись ігрові об'єкти, з рандомно згенерованими скриптом характеристиками. Я поки що непридумав іншого способу зберігання змін ігрового середовища. Якщо хтось може дайте ссилку де можна подивитись структур  баз данних для подібних задач. Буду вдячний.

koala написав:

select_single - насправді, занадто загальна функція. Вам, швидше за все, треба шукати за певними конкретними ознаками, а ви робите функцію, що шукає що завгодно і повертає лише перший результат, із тим самим успіхом можна прямо запити передавати без зайвого класу.

згоден на рахунок того, що можна робити прямі запити просто подумав, раз і так є такий класс то можна запхнути в нього таку функцію плюс зновуж обробка виключень.

koala написав:

Як обробити помилки - залежить від іншого коду. Як ви плануєте використовувати цей клас? Можете навести приклад коду, що його застосовує?

Наразі  пробую пробую розібратись чи можна обробляти повідомлення про помилку після двокрапки

sqlite3.OperationalError: table a979674210 already exists

13 Востаннє редагувалося plusxx (26.12.2019 17:56:30)

Re: Класс для роботи sqlite подивіться будьласка.

import telebot
from config import token
from data_base import DataBaseFunctional
from keyboards import KeyboardsForBot as keyboard
from data_to_create_pers import start_data
from gameplay_management import GamePlayManagement

bot = telebot.TeleBot(token)
@bot.message_handler(commands=["start"])
def reply_to_start_message(message):#Функція запускається по команді /start
    db = DataBaseFunctional(str(message.from_user.id), "users.db")
    if db.create_table()==True:#Якщо функція create_table повертає тру це значить що створено нов утаблицю в базі данних для користувача
        db.write_starting_data_to_table(start_data)#Запис початкових значень  до таблиці
        print (f"created database to user id {message.from_user.id}")
        bot.send_message(message.chat.id, f"Привіт {message.from_user.username}. Ти ввійшов в гру ...все має наслідки",reply_markup=keyboard.keyboard0)#Перше ігрове повідомлення
    else:#Якщо функція create_table повертає false значить користувач вже є в базі
        bot.send_message(message.chat.id, f"Привіт {message.from_user.username} продовжимо гру")

@bot.message_handler(commands=["delete_game"])
def delete_any_game(message):#Видалення ігровоих данних
    db = DataBaseFunctional(str(message.from_user.id), "users.db")
    if db.delete_table() == True:#Якщо тру то користувач видалений з гри
        print(f'user id {message.from_user.id} deleted game progress')
        bot.send_message(message.chat.id, f"{message.from_user.username} Ваш ігровий прогрес видалено ")
    else:
        bot.send_message(message.chat.id, "Вибачте. Запис невдалося видалити або запис з вашими даними не був знайдений")

@bot.message_handler(commands=["restart_game"])#Рестарт гри
def restart_game(message):
    db = DataBaseFunctional(str(message.from_user.id), "users.db")#
    if db.delete_table() == True:#Якщо тру то користувач видалений з гри
        print(f'user id {message.from_user.id} deleted game progress')
        bot.send_message(message.chat.id, f"{message.from_user.username} ваш гра перезапущена.")
        reply_to_start_message(message)
        print(f'user id {message.from_user.id} restarted game')

@bot.message_handler(content_types=["text"])
def repeat_all_messages(message): # Обробка текстових повідомленнь
    gpm = GamePlayManagement(message)
    gpm.message_hub()



if __name__== '__main__':
    bot.polling(none_stop = True)
    print("start long polling")


О так приблизно виглядає код для виклику функцій в обговорюваному классі решта в классі GamePlayManagement але він ще не дописаний

14

Re: Класс для роботи sqlite подивіться будьласка.

Скажіть, а якщо ви захочете дізнатися, скільки гравців пройшли квест, як ви це зробите з такою структурою БД?
Я так розумію, кожен гравець буде проходити власний квест без взаємодії з іншими гравцями, правильно? Тоді вам потрібні:
- таблиця(таблиці) для опису базових ігрових об'єктів, дуже умовно

ID   Назва         Тип                     Характеристика
 1  Василь         Людина               Спокійний
 2  Сірий камінь Неживий об'єкт   Нерухомий

- таблиця гравців (якщо вона тотожна, тобто кожен гравець представлений виключно своїм кодом, можна і не заводити, а використовувати код замість ID);

ID    Код         Ім'я      ХешПароля  Мова
 1    3482364  Петро    JASF#h347GDS   uk-UA

- таблиця взаємодій, наприклад

ID_гравця ID_об'єкта Взаємодія
     1                1             Посварився
     1                2             Нашкрябав своє ім'я

Але це залежить від конкретних ігрових правил.

В чому проблема вашого коду - ви зв'язуєте конкретні вхідні дані (Телеграм) із конкретними діями з базою (додавання таблиць). Якщо ви додасте ще один шар абстракції - власне, логіку гри - то зможете потім відносно легко переробити це на веб-сайт на Redis. Чи на консоль, що зберігає дані у файлі. Тобто бажано, щоб код виглядав більш схожим на таке:

@bot.message_handler(commands=["start"])
def reply_to_start_message(message):
    try:
        game = get_game()
        user = game.get_user(message.from_user.id)
        reply = user.start_game()
        bot.send_message(message.chat.id, reply)
    except Exception as e:
        print('reply_to_start_message failed:', str(e))
        bot.send_message(message.chat.id, 'Щось пішло не так, адміністрація вже працює над цим')

...

в іншому місці:
class Game:
   def __init__(self):
        self.db = ...
   def get_user(id):
        return self.db.get_user_by_id(id)
class User:
   def start_game():
       if self.new_user:
            self.game.db.create_new_user(self.id)
            self.new_user = False
       return self.game.db.start_game()
       

Таким чином, щоб всі зв'язки між інтерфейсом, моделлю та базою були чітко розмежовані - тоді вам буде значно легше вносити зміни.

Подякували: plusxx1

15 Востаннє редагувалося plusxx (26.12.2019 20:22:17)

Re: Класс для роботи sqlite подивіться будьласка.

Видалено в зв'язку з тим що повідомлення містило цілковиту нісенітницю.

З повагою pluszz.

16

Re: Класс для роботи sqlite подивіться будьласка.

koala написав:
@bot.message_handler(commands=["start"])
def reply_to_start_message(message):
    try:
        game = get_game()
        user = game.get_user(message.from_user.id)
        reply = user.start_game()
        bot.send_message(message.chat.id, reply)
    except Exception as e:
        print('reply_to_start_message failed:', str(e))
        bot.send_message(message.chat.id, 'Щось пішло не так, адміністрація вже працює над цим')

  

.

Може то і дурне питанняя але той except буде лапати всі виключення?

17

Re: Класс для роботи sqlite подивіться будьласка.

pluszz написав:
koala написав:
@bot.message_handler(commands=["start"])
def reply_to_start_message(message):
    try:
        game = get_game()
        user = game.get_user(message.from_user.id)
        reply = user.start_game()
        bot.send_message(message.chat.id, reply)
    except Exception as e:
        print('reply_to_start_message failed:', str(e))
        bot.send_message(message.chat.id, 'Щось пішло не так, адміністрація вже працює над цим')

  

.

Може то і дурне питанняя але той except буде лапати всі виключення?

Так буде причому дуже ефективно. Дуже вдячний пану koali, чере не розуміння процесу я понапихав 'except'ів в кожну функцію.

18 Востаннє редагувалося koala (26.12.2019 20:27:35)

Re: Класс для роботи sqlite подивіться будьласка.

А знаєте, щось я перебрав. У чат-бота інтерфейс досить близький до того, що буде у моделі, так що можна поки що обійтися без цього рівня. Але звернення до БД точно переробіть з точки зору гри - тобто якщо треба дізнатися, хто знаходиться в локації, то це має бути не

db.get_column("location",player_location)

а

db.get_characters_in_location(player_location)
pluszz написав:

Може то і дурне питанняя але той except буде лапати всі виключення?

Так, будь-яка виключна ситуація - це нащадок Exception, а форма "as e" дозволяє отримати не лише тип, а й конкретне повідомлення.

https://docs.python.org/3/library/excep … #Exception

exception Exception
All built-in, non-system-exiting exceptions are derived from this class. All user-defined exceptions should also be derived from this class.

Подякували: plusxx1

19

Re: Класс для роботи sqlite подивіться будьласка.

game = get_game()
        user = game.get_user(message.from_user.id)
        reply = user.start_game()
        bot.send_message(message.chat.id, reply)

це не наркоманія, це якась порнографія...
раджу вам взяти олівець, кілька аркушів паперу і намалювати об'ектну модель

Подякували: koala, varkon2

20

Re: Класс для роботи sqlite подивіться будьласка.

ur_naz написав:

game = get_game()
        user = game.get_user(message.from_user.id)
        reply = user.start_game()
        bot.send_message(message.chat.id, reply)

це не наркоманія, це якась порнографія...
раджу вам взяти олівець, кілька аркушів паперу і намалювати об'ектну модель

Але чому порнографія ? Для мене воно зрозуміле. І намалювати схему навіть дуже просто. Обгрунтуйте свою точку зору щоб і іншим стало ясно що не так.