2012-12-22 7 views
23

Ostatnio pomyślałem, że domyślna Kolumna sqlalchemy nie działa jak mogę oczekiwać, że:Dlaczego domyślna wartość kolumny SQLAlchemy nie jest dostępna przed zatwierdzeniem obiektu?

>>> Base = declarative_base() 
>>> class TestModel(Base): 
    ...     __tablename__ = 'tmodel' 
...     id = sa.Column(sa.Integer, primary_key=True) 
...     foo = sa.Column(sa.Integer, default=0) 
...     
>>> tmodel_instance = TestModel() 
>>> print tmodel_instance.foo 
None 
>>> session.add(tmodel_instance) 
>>> print tmodel_instance.foo 
None 
>>> session.commit() 
>>> print tmodel_instance.foo 
0 

Chcę tmodel_instance.foo równa 0 zaraz po obiekcie instancji, ale wydaje się, że domyślna wartość jest używana tylko podczas wykonywania INSERT polecenie, a to naprawdę mnie myli. Dlaczego ktoś woli default ponad server_default? I jak osiągnąć to, czego chcę? Czy mam podać wszystkie domyślne argumenty w __init__? Wydaje się, że jest to powielanie kodu: aby zmienić domyślną wartość, muszę dwukrotnie ją zmienić i zachować równość tych wartości - czy jest jakiś sposób, aby tego uniknąć?

+2

+1 Zgadzam się. Nie ma sensu, aby akt utrzymywania obiektu zmieniał jego atrybuty. –

Odpowiedz

14

jeden woleliby domyślne nad domyślnie serwera dla jednego z czterech powodów:

  1. chcesz uruchomić funkcję Pythona, a nie funkcję SQL, dla domyślnego (lub wyrażenie SQL, który potrzebuje trochę dla stanu INSERT Python również).

  2. domyślnie jest częścią kolumny klucza podstawowego. ORM nie może wczytać wiersza bez klucza podstawowego, więc parametr server_default na ogół nie jest przydatny dla kolumny PK podczas korzystania z ORM.

  3. Wyrażenie SQL, które chcesz uruchomić, nie jest obsługiwane przez bazę danych jako "domyślne ustawienie serwera".

  4. Masz do czynienia ze schematem, którego nie możesz/nie chcesz zmienić.

W tym przypadku, gdy chcesz "foo" do "0" w swojej działalności niezależny aplikacji baz danych, do wyboru są:

  1. użycie __init__(). To pyton!

  2. używać zdarzeń.

Oto __init__():

class TestModel(Base): 
    # ... 

    def __init__(self): 
     self.foo = 0 

Oto wydarzenia (konkretnie init event):

from sqlalchemy import event 

@event.listens_for(Foo, "init") 
def init(target, args, kwargs): 
    target.foo = 0 
+3

Dlaczego jednak modele sqlAlchemy zostały zaprojektowane w ten sposób: Brak zamiast wartości domyślnej zaraz po utworzeniu? To zdecydowanie myli ludzi, którzy są przyzwyczajeni do innych bibliotek ORM – d9k

+3

"domyślnie" jest częściej niż wyrażeniem SQL lub pełnowartościową wartością po stronie serwera.Nie można go poznać, dopóki nie nastąpi INSERT, lub znacznie mniej, jeśli byłby niezręcznie wykonany przed wykonaniem obiektu (co samo w sobie pogarsza wartość będącą prawdziwym "INSERT defaut"). w związku z tym ustawienie tylko jednego podzbioru domyślnych wartości wstawiania na poziomie rdzenia działa zupełnie inaczej, byłoby niespójne. – zzzeek

4

Można użyć zdarzenia init wypełnić domyślne. Ten detektor zdarzeń będzie to zrobić:

from sqlalchemy import event 
from sqlalchemy.orm import mapper 
from sqlalchemy.inspection import inspect 


def instant_defaults_listener(target, args, kwargs): 
    for key, column in inspect(target.__class__).columns.items(): 
     if column.default is not None: 
      if callable(column.default.arg): 
       setattr(target, key, column.default.arg(target)) 
      else: 
       setattr(target, key, column.default.arg) 


event.listen(mapper, 'init', instant_defaults_listener) 
2

Można użyć force_instant_defaults słuchacza od sqlalchemy_utils zmienić to zachowanie:

from sqlalchemy_utils import force_instant_defaults 

force_instant_defaults() 

class TestModel(Base): 
    __tablename__ = 'tmodel' 
    id = sa.Column(sa.Integer, primary_key=True) 
    foo = sa.Column(sa.Integer, default=0) 

model = TestModel() 
assert model.foo == 0