Динамическое создание классов на python
Необходимо реализовать ядро СУБД, работающее по принципу ORM. Интерфейс-класс-БД. Соответственно, необходимо создавать экземпляры класса для каждой записи БД. Как это организовать?
Ответы (3 шт):
Хорошо. Вот у нас есть драйвер для работы с sqlite. Опустим проблемы с созданием схемы БД и миграциями. Пусть на данном этапе все это делается вручную.
import sqlite3
from dataclasses import dataclass
@dataclass
class User:
username: str
password: str
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute("create table users (username varchar(512), password varchar(512));")
cursor.execute("insert into users (username, password) values ('test', 'test_pass');")
cursor.execute("select * from users;")
users = [User(*x) for x in cursor]
cursor.close()
conn.close()
print(users) # [User(username='test', password='test_pass')]
Кажется это все. Делаете выборку и передаете результаты выборки в конструктор класса. Если я правильно понял вопрос.
Ну и на скорую руку набросал с созданием схемы)
import sqlite3
from dataclasses import dataclass, field
@dataclass
class User:
username: field(default_factory=str, metadata={"sqltype": "varchar(512)"})
password: field(default_factory=str, metadata={"sqltype": "varchar(512)"})
@classmethod
def to_sql_query(cls):
query = f"""
create table {cls.__name__} (
"""
fields = ",".join(f"{x[0]} {x[1].type.metadata['sqltype']}" for x in cls.__dataclass_fields__.items())
query += fields
query += ")"
return query
@classmethod
def create_table(cls):
return conn.execute(cls.to_sql_query())
@classmethod
def all(cls):
cursor.execute(f"select * from {cls.__name__}")
return [User(*x) for x in cursor]
@classmethod
def create(cls, username, password):
return cursor.execute(f"insert into {cls.__name__} (username, password) values ('{username}', '{password}');")
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
User.create_table()
User.create("test", "test_pass")
users = User.all()
cursor.close()
conn.close()
print(users)
Настоятельно рекомендую использовать только в качестве примера.
Стало любопытно и накидал простенький ORM. Но это именно пример, т.к. всего 2 типа поддерживается int и str, у полей нет всяких флагов типа уникальности или значения по умолчанию. Нет и связи между таблицами
Пока писал думал о peewee, поэтому сделал базовую модель с Meta классом
Пример:
import sqlite3
from dataclasses import dataclass
from typing import List
con = sqlite3.connect('test.sqlite')
con.row_factory = sqlite3.Row # Чтобы получать из SELECT элементы как словари
class BaseModel:
class Meta:
db: sqlite3.Connection = con
@classmethod
def create_table(cls):
con = cls.Meta.db
fields = [f'{f.name} {"INTEGER" if f.type == int else "TEXT"}' for f in cls.__dataclass_fields__.values()]
sql = f"CREATE TABLE IF NOT EXISTS {cls.__name__} ({', '.join(fields)})"
print(f'[SQL]: {sql}')
con.execute(sql)
con.commit()
def insert_db(self):
self.create_table()
cls = self.__class__
con = cls.Meta.db
fields = list(cls.__dataclass_fields__)
values = [getattr(self, f) for f in fields]
placeholders = ['?'] * len(fields)
sql = f"INSERT INTO {cls.__name__} ({', '.join(fields)}) VALUES ({', '.join(placeholders)})"
print(f'[SQL]: {sql}', values)
con.execute(sql, values)
con.commit()
@classmethod
def select_db(cls) -> List['BaseModel']:
cls.create_table()
con = cls.Meta.db
sql = f"SELECT * FROM {cls.__name__}"
print(f'[SQL]: {sql}')
return [cls(**dict(row)) for row in con.execute(sql).fetchall()]
@dataclass
class Foo(BaseModel):
name: str
value: int
Foo(name='foo', value=123).insert_db()
print(Foo.select_db())
print()
Foo(name='bar', value=456).insert_db()
print(Foo.select_db())
Результат:
[SQL]: CREATE TABLE IF NOT EXISTS Foo (name TEXT, value INTEGER)
[SQL]: INSERT INTO Foo (name, value) VALUES (?, ?) ['foo', 123]
[SQL]: CREATE TABLE IF NOT EXISTS Foo (name TEXT, value INTEGER)
[SQL]: SELECT * FROM Foo
[Foo(name='foo', value=123)]
[SQL]: CREATE TABLE IF NOT EXISTS Foo (name TEXT, value INTEGER)
[SQL]: INSERT INTO Foo (name, value) VALUES (?, ?) ['bar', 456]
[SQL]: CREATE TABLE IF NOT EXISTS Foo (name TEXT, value INTEGER)
[SQL]: SELECT * FROM Foo
[Foo(name='foo', value=123), Foo(name='bar', value=456)]
Экземпляр класса в основном нужно создавать для базы данных. Условно будет class Database. Внутри него будет инициализироваться подключение к базе данных проводить операции по занесению данных и исполнению скриптов, а также, что важно, корректное закрытие подключения.
Дальше у Вас будут классы или экземпляры моделей, это как реализуете. К примеру, пусть будет class Model. В нем у Вас будут храниться методы создания скриптов и работы с моделью типа query, insert, delete, update, filter и т.д., и т.п.
Дальше нужны классы class Column для создания колонок в таблице с различными опциями, а также классы типов полей (Integer, Real, Text и т.д.).
В качестве примера могу предложить вариант, который когда-то на курсах делал https://github.com/ndrwpvlv/hatter_orm