Closed LecronRu closed 1 month ago
Не обещаю. Беру паузу на подумать.
OK. Буду ждать. Только сейчас вспомнил, что мне напоминает данная ситуация — внедрение зависимостей в FastAPI. Правда для данной библиотеки считаю это оверинжинирингом. В идеале, достаточно параметра по умолчанию. Жаль это поломает обратную совместимость. Соединение должно передаваться не первым, обязательным параметром, а последним — опциональным.
default_db = sqlite3.connect("data.sqlite")
@nm.sql_one_or_none(
DbUser,
"SELECT rowid AS id, username, email FROM users WHERE rowid = :id",
default_db
)
def get_user_by_id(id_: int):
return nm.params(id=id_)
default_value = get_user_by_id(1)
with sqlite3.connect("mock.sqlite") as conn:
mock_value = get_user_by_id(1, conn)
Пока что мне больше нравится сделать "default_db" декоратором. Как-то так:
@nm.default_db
@nm.sql_one_or_none(
DbUser,
"SELECT rowid AS id, username, email FROM users WHERE rowid = :id",
)
def get_user_by_id(id_: int):
return nm.params(id=id_)
with sqlite3.connect("mock.sqlite") as conn:
nm.default_db.set(conn) # <<<< After this @nm.default_db decorator knows what to use
mock_value = get_user_by_id(1)
Годится такое?
Идея фактически реализует два юзкейса.
Недостатки вашей версии:
И думаю вы понимаете, что работа может идти с разными базами (файлами). То есть декоратор nm.default_word_db будет отличаться от nm.default_morph_db
Недостатки моей версии:
Сделано в версии 0.1.4. Docstring для декоратора @nm.default_db
:
The @nm.default_db
decorator makes your DB API functions easier to use by
removing the first mandatory ConnectionOrCursor
argument.
Use this decorator before other @nm.sql_...
decorators.
Example:
import sqlite3
import noorm.sqlite3 as nm
@nm.default_db
@nm.sql_scalar_or_none(int, "select count(*) from users")
def get_users_count():
pass
with sqlite3.connect("my_db.sqlite") as conn, nm.set_default_db(conn):
users_count = get_users_count() # <<< Consider no "conn" parameter
print(f"{users_count=}")
Without nm.set_default_db(conn)
any call to get_users_count
would fail with a
runtime error "default_db is not set".
You can use nm.set_default_db
as a function. This code also works:
conn = sqlite3.connect("my_db.sqlite")
nm.set_default_db(conn)
users_count = get_users_count()
The nm.set_default_db
context can be nested:
conn1 = sqlite3.connect("my_db_1.sqlite")
conn2 = sqlite3.connect("my_db_2.sqlite")
with nm.set_default_db(conn1):
print(f"Users count in my_db_1: {get_users_count()}")
with nm.set_default_db(conn2): # <<< Temporary set conn2 as a default_db
print(f"Users count in my_db_2: {get_users_count()}")
print(f"Check again users count in my_db_1: {get_users_count()}")
Есть ли возможность ограничить область действия set_default_db выполняемой как функция модулем, в котором она выполняется?
Есть два модуля разных баз. Из одного модуля (db1) запрашивается функция второго модуля (db2). Базы логически связанные. Возник конфликт, который пришлось решать через with nm.set_default_db(db2.conn)
, несмотря на то, что в db2 коннект conn уже установлен по умолчанию.
То есть контекстный менеджер имеет приоритет над функцией, но функции друг друга не перекрывают.
Надо подумать...
Пофантазируем.
В принципе, можно сделать декоратор @nm.default_db
параметризуемым. Параметр называется tag
, который может быть строкой или None. По умолчанию None, то есть
@nm.default_db
равно @nm.default_db(None)
равно @nm.default_db(tag=None)
Соответственно, nm.set_default_db
получает второй необязательный параметр tag
. В результате можно делать как-то так:
@nm.default_db("src")
@nm.sql_fetch_all(...
... # this function works with default "src" database
@nm.default_db("dst")
@nm.sql_execute(...
... # this function works with default "dst" database
nm.set_default_db(conn_source, "src")
nm.set_default_db(conn_destination, "dst")
Контекст-менеджеры и их нестинг, естественно, тоже будут работать.
Вариант решает совсем другую проблему. Позволит реализовать доступ к двум базам в одном модуле. А в остальном вижу минусы.
Моя же проблема (если ее можно так назвать), конфликт областей видимости, при существовании в одном модуле, как обявления функций доступа к базе, так и вызова функции объявленной в другом модуле. set_default влияет на обе сущности, а хотелось влияния только на объявления.
Если не получается реализовать без изменения интерфейса, лучше не надо. Описанных мной случаев, не так много. И явное переопределение контекстным менеджером, нормальная практика.
ОК, тогда пока оставляем как есть. Закрываю эту issue.
Работа c SQLite, за редчайшим исключением, подразумевает одно, постоянное соединение. Создаваемое в том же модуле, где описывется DB API. Указание коннекта в клиентском коде, выглядит как бойлерплейт. Работаю сразу с несколькими базами. поэтому импортирую сам DB API модуль. Отчего полный вызов выглядит как
word_db.sents_by_form(word_db.db, 'параметр')
Когда он один, вроде не заметно, но когда десяток...С одной стороны, его несложно замкнуть в DB API
sents_by_form = partial(sents_by_form, db)
Но теряем подсказку типов в IDE. Если указывать типы для partial функции, опять получаем бойлерплейт. Только другой и в другом месте.Хотелось бы иметь функцию nm.register_connection, после выполнения которой, остальные вызовы будут использовать это зарегистрированное соединение. Функция может быть использована как в DB API модуле (постоянное соединение), так и в клиентском коде (временное соединение). Что-то наподобии: