SQLAlchemy, Elixir i Pylons - przypadkowe starcie.
Wszedłem dziś na strone projektu Elixir by sprawdzić postępy i bardzo się zdziwiłem. Projekt jest w bardzo wczesnej (0.2) fazie beta więc zakładałem, że funkcjonalność jest bardzo ograniczona, a działanie niestabilne. Po przeczytaniu tutoriala i przykładów stwierdziłem, że projekt ma już wszystko czego potrzebuję i mogę spróbować go użyć do aplikacji opisanej w poprzednim wpisie o SQLAlchemy.
Teraz moje modele wyglądają tak:
Kanały RSS(Feed):
Zamiast tego:
-
feeds_table = Table("feeds", meta,
-
Column("id", Integer(),primary_key=True),
-
Column("title", String(40)),
-
Column("feed_url", String(), default=""),
-
Column("public_url", String(), default=""),
-
Column("is_defunct", Boolean(), default=False),
-
)
-
class Feed(object):
-
def __str__(self):
-
return self.title
-
def __repr__(self):
-
return self.title
-
-
feeds_mapper = assign_mapper(session_context, Feed, feeds_table, properties = {
-
"feeditems" : relation(FeedItem, backref="feed")
-
}
-
)
Dostajemy moim zdaniem dużo bardziej przyjazny kod:
-
class Feed(Entity):
-
has_field("title", String(40))
-
has_field("feed_url", String, default="")
-
has_field("public_url", String, default="")
-
has_field("is_defunct", Boolean, default=False)
-
has_many("feeditems", of_kind="FeedItem")
-
has_and_belongs_to_many("categories", of_kind="Category", inverse="feeds")
-
using_options(tablename="feeds")
-
def __str__(self):
-
return self.title
-
def __repr__(self):
-
return self.title
Zmieniamy Wpisy RSS (FeedItem):
-
feeditems_table = Table("feed_items", meta,
-
Column("id", Integer(), primary_key=True),
-
Column("feed_id", Integer(), ForeignKey("feeds.id")),
-
Column("guid", String(250)),
-
Column("date_modified", DateTime()),
-
Column("title", String(40)),
-
Column("link", String()),
-
Column("summary", String()),
-
)
-
-
class FeedItem(object):
-
@classmethod
-
def get_by_category(self, category_name):
-
join = FeedItem.join_via(["feed", "categories"])
-
return FeedItem.select_by(Category.c.name==category_name, join)
-
def __str__(self):
-
return self.title
-
def __repr__(self):
-
return self.title
-
def _get_categories(self):
-
return self.feed.categories
-
categories = property(_get_categories)
-
-
feeditems_mapper = assign_mapper(session_context, FeedItem, feeditems_table,
-
order_by=desc(feeditems_table.c.date_modified)
-
)
w
-
class FeedItem(Entity):
-
has_field("guid", String(250))
-
has_field("date_modified", DateTime())
-
has_field("title", String(40))
-
has_field("link", String())
-
has_field("summary", String())
-
belongs_to("feed", of_kind="Feed")
-
has_and_belongs_to_many("tags", of_kind="Tag", inverse="feeditems")
-
using_options(tablename="feeditems", order_by="-date_modified")
-
-
@classmethod
-
def get_by_category(self, category_name):
-
join = FeedItem.join_via(["feed", "categories"])
-
return FeedItem.select_by(Category.c.name==category_name, join)
-
def __str__(self):
-
return self.title
-
def __repr__(self):
-
return self.title
-
def _get_categories(self):
-
return self.feed.categories
-
categories = property(_get_categories)
Poprzednia definicja kategorii:
-
categories_table = Table("categories", meta,
-
Column("id", Integer(), primary_key=True),
-
Column("name", String(100)),
-
Column("description", String())
-
)
-
-
categoriesfeeds_table = Table("categories_feeds", meta,
-
Column("feed_id", Integer(), ForeignKey("feeds.id")),
-
Column("category_id", Integer(), ForeignKey("categories.id"))
-
)
-
class Category(object):
-
def __str__(self):
-
return self.name
-
def __repr__(self):
-
return self.name
-
def _get_feeditems(self):
-
join = Category.join_via(["feeds", "feeditems"])
-
return FeedItem.select_by(Category.c.id==self.id, join)
-
feeditems=property(_get_feeditems)
-
-
categories_mapper = assign_mapper(session_context, Category, categories_table, properties = {
-
"feeds" : relation(Feed, secondary=categoriesfeeds_table, lazy=False, backref="categories")
-
}
-
)
zmienia się w:
-
class Category(Entity):
-
has_field("name", String(100))
-
has_field("description", String())
-
has_and_belongs_to_many("feeds", of_kind="Feed", inverse="categories")
-
using_options(tablename="categories")
-
def __str__(self):
-
return self.name
-
def __repr__(self):
-
return self.name
-
def _get_feeditems(self):
-
join = Category.join_via(["feeds", "feeditems"])
-
return FeedItem.select_by(Category.c.id==self.id, join)
-
feeditems=property(_get_feeditems)
Jak widać w definicji FeedItem wzbogaciłem nasze modele o klasę Tag, która wygląda tak:
-
class Tag(Entity):
-
has_field("name", String(100), unique=True, index=True)
-
has_and_belongs_to_many("feeditems", of_kind="FeedItem", inverse="tags")
-
using_options(tablename="tags")
Używam has_field() do definiowania pól, zamiast tego można używać takiej formy:
-
class Feed(Entity):
-
with_fields(
-
title = Field(String(40)),
-
feed_url = Field(String(), default=""),
-
#etc…
-
)
Dodatkowo Elixir dodaje nam pole id, które staje się kluczem podstawowym, jeżeli w definicji żaden z atrybutów kluczem podstawowym nie jest (primary_key=True).
Nie musimy już w definicji tabeli podawać pola jako klucz obcy, definicje relacji zajmą się tym za nas. Dodatkowo stworzona zostanie też tabela łącząca dla relacji wiele-do-wielu.
Jest tylko jeden problem. Używając schematów SQLAlchemy, “spinaliśmy” wszystkie mapowania z sesją SQLAlchemy przez session_context i meta Elixir automatycznie wyszukuje w przestrzeni nazw, w której znajdują się definicje 2 zmiennych (modułów) metadata i session. Jest to chyba związane z ułatwieniem używania w TyrboGears. metadata może być zainicjowana przez samego Elixira ale zostaje problem spięcia go z sesją. Wyszukałem, że można to zrobić umieszczając połączenie z bazą w /lib/base.py w metodzie __call__() ale nie wydało mi się to najładniejszym rozwiązaniem. Po kilku próbach wydedukowałem, że Elixir z session potrzebuje tylko session.context a ten kontekst mamy dostępny przez pylons.database.session_context. W skrócie: problem rozwiązałem następującym “trikiem”(tak powinien wyglądać nagłówek pliku models/__init__.py:
-
from elixir import *
-
from pylons.database import session_context
-
class FakeSession:
-
def __init__(self, context):
-
self.context = context
-
session = FakeSession(session_context)
Jeżeli ktoś zna inne rozwiązanie tego problemu, chętnie je poznam.
W moim przypadku wszystko działa bez najmniejszego problemu.
Jeszcze jedna uwaga. Jeżeli powyższy nagłówek zamienimy na :
-
from elixir import *
-
from turbogears.database import metadata, session
Powyższe modele powinny bez problemu działać w TurboGears.



19. March, 2007 at 14:50
Mój ty zbawco :P
Nie jestem python wyjadaczem, ale troche w nim pisze różnych skryptow/programow (administracyjne itp).
Potrzebowalem w koncu WWW kilka stron zrobić, po kolei odrzucałem nie odpowiadające mi technologie (django, tg) by skończyć na pylons+sqlalchemy+elixir. Ale miałem właśnie ten problem spięcia obiektów z sesją, widziałem 2a rozwiązania, ale jakoś mi się nie podobały i na tydzień zarzuciłem prace.
A tu nagle - krótkie i zwięzłe rozwiązanie ;)
Dzięki
19. March, 2007 at 15:20
Niezmiernie cieszę się, że komuś pomogłem.
Sporo szukałem jak ładnie zintegrowac Elixira z Pylons bez większej rzeźby używając dostępnych w Pylons rozwiązań.
Szczerze mówiac to myślałem, że Elixir jest w takiej fazie, że nie ma sensu nawet myśleć o jego używaniu a tu taka niespodzianka. Do tego nie zauważyłem aby spadła wydajność czy pojawiały się błedy.
Już niedługo coś o AuthKit (jestem w trakcie rozgryzania), pozniej o XLM-RPC