Вы можете добавить функциональность, создав подкласс Table
.В SQLAlchemy Table
специально переопределяет Table.__init__()
, чтобы сделать его недоступным:
def __init__(self, *args, **kw):
"""Constructor for :class:`~.schema.Table`.
This method is a no-op. See the top-level
documentation for :class:`~.schema.Table`
for constructor arguments.
"""
# __init__ is overridden to prevent __new__ from
# calling the superclass constructor.
Ключ в том, что он не вызывает super().__init__()
, так что sqlalchemy можетпринять команду создания экземпляра и что бы вы ни делали, это необходимо сохранить.
from sqlalchemy.sql.schema import Table
class MyTable(Table):
def __init__(self, *args, **kwargs):
self._where_am_i = __file__
my_table = MyTable(
"my_table",
metadata,
Column("my_id", BigInteger(), primary_key=True)
)
В этом случае MyTable.__init__()
все еще блокирует конструктор суперкласса, но также добавляет атрибут к экземпляру, который будетимя модуля, в котором создается экземпляр класса.Я специально выбрал непонятное имя атрибута (_where_am_i
), которое вряд ли будет перезаписано sqlalchemy, и использование __file__
возвращает путь к модулю (но вы можете сделать это как угодно).
Я тестировалкоторый вставляет и выбирает все еще работает:
import logging
from sqlalchemy.sql import select
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
logging.basicConfig(level=logging.INFO)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
conn = engine.connect()
conn.execute(my_table.insert(), [{"my_id": i} for i in range(1, 6)])
s = select([my_table])
result = conn.execute(s)
for row in result:
print(row)
# (1,)
# (2,)
# (3,)
# (4,)
# (5,)
И расположение экземпляра:
print(my_table._where_am_i) # 53302898.py (that's the name of my module).
Внешний модуль:
# external_module.py
from sqlalchemy_app import Base
from sqlalchemy.sql.schema import Table
from sqlalchemy import Column, BigInteger
metadata = Base.metadata
class MyTable(Table):
def __init__(self, *args, **kwargs):
self._where_am_i = __file__
my_table = MyTable(
"my_table",
metadata,
Column("my_id", BigInteger(), primary_key=True)
)
И:
# 53302898.py
from external_module import my_table
if __name__ == '__main__':
print(my_table._where_am_i) # prints C:\Users\peter_000\OneDrive\git\test\external_module.py
Обратите внимание, как он возвратил относительный путь к файлу в первом тесте и абсолютный путь к файлу в тесте внешнего модуля.Вы можете прочитать об этом здесь: Атрибут Python __file__ абсолютный или относительный? , но вы можете сделать так, чтобы этот атрибут _where_am_i
возвращал все, что вам нужно для вашего приложения.
РЕДАКТИРОВАТЬ Приведенное выше решение требует создания подкласса Table
class внутри модуля, в котором формируются экземпляры, в противном случае оно будет привязывать модуль, в котором создается экземпляр Class, а не экземпляры.Если вы хотите создать подкласс Table
один раз в своем проекте, вам нужно передать местоположение конструктору.
Это работает:
class MyTable(Table):
def __init__(self, *args, _where_am_i=None, **kwargs):
self._where_am_i = _where_am_i
... но вы получитепредупреждение при создании экземпляра:
SAWarning: Can't validate argument '_where_am_i'; can't locate any SQLAlchemy dialect named '_where'
.
Чтобы избежать этого, вам придется переопределить альтернативный конструктор sqlalchemy, Table._init()
, удалить параметр location и затем делегировать обратно по цепочке:
class MyTable(Table):
def _init(self, *args, _where_am_i=None, **kwargs):
self._where_am_i = _where_am_i
super()._init(*args, **kwargs)
Импорт из внешнего модуля:
# 53302898.py
from external_module import MyTable
my_table = MyTable(
"my_table",
metadata,
Column("my_id", BigInteger(), primary_key=True),
_where_am_i = __file__
)
if __name__ == '__main__':
print(my_table._where_am_i) # 53302898.py
Все вышеперечисленные тесты все еще проходят.