Сам фреймворк Pyramid не имеет встроенных возможностей работы с базами данных, в отличии от таких фреймворков как Django (Django ORM) и Ruby on Rails (Active Record). Хорошим выбором для реляционных БД будет ORM SQLAlchemy.
Организация БД в пирамиде не зависит от фреймворка, поэтому можно использовать любую структуру, которая вам удобна. Ниже я приведу один из вариантов, более подробно про SQLAlchemy можно прочитать в разделе SQLAlchemy ORM.
Вынесем модели и то что касается соединения с БД в отдельный файл models.py
.
# models.py
from sqlalchemy import Column, Integer, Text, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///foo.db')
Session = sessionmaker()
Base = declarative_base(bind=engine)
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(Text)
def __repr__(self):
return self.name
В представлениях мы просто создаем объект
sqlalchemy.orm.session.Session
и работаем с объектами, как описано в
документации SQLAlchemy. При этом в каждом представлении нам необходимо
создавать новую SQLAlchemy сессию, а если были изменения подтверждать их при
помощи метода sqlalchemy.orm.session.Session.commit()
.
# __init__.py
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from models import User, Session, Base, engine
def hello(request):
DBSession = Session(bind=engine)
result = DBSession.query(User).all()
import time
timestamp = int(time.time())
new_user = User(name=str(timestamp))
DBSession.add(new_user)
DBSession.commit()
return Response(str(result))
if __name__ == '__main__':
Base.metadata.create_all()
DBSession = Session(bind=engine)
DBSession.add(User(name='Vasya'))
DBSession.add(User(name='Petya'))
DBSession.commit()
config = Configurator()
config.add_route('hello_world', '/')
config.add_view(hello, route_name='hello_world')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8000, app)
server.serve_forever()
Данный пример при каждом обновлении делает новую запись в БД и отдает их браузеру.
См.также
ZopeTransactionExtension
это расширение для SQLAlchemy, которое привязывает
сессии к универсальному менеджеру транзакций transaction.
Добавим его в наш пример:
# models.py
from sqlalchemy import Column, Integer, Text, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from zope.sqlalchemy import ZopeTransactionExtension
engine = create_engine('sqlite:///foo.db')
Session = sessionmaker(bind=engine,
extension=ZopeTransactionExtension())
Base = declarative_base(bind=engine)
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(Text)
def __repr__(self):
return self.name
Теперь вместо DBSession.commit
, нужно использовать transaction.commit()
.
# __init__.py
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from models import User, Session, Base, engine
import transaction
def hello(request):
DBSession = Session(bind=engine)
result = str(DBSession.query(User).all())
import time
timestamp = int(time.time())
new_user = User(name=str(timestamp))
DBSession.add(new_user)
transaction.commit()
return Response(result)
if __name__ == '__main__':
Base.metadata.create_all()
DBSession = Session(bind=engine)
DBSession.add(User(name='Vasya'))
DBSession.add(User(name='Petya'))
transaction.commit()
config = Configurator()
config.add_route('hello_world', '/')
config.add_view(hello, route_name='hello_world')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8000, app)
server.serve_forever()
Теперь мы используем общий, глобальный менеджер транзакций, который работает не только с SQLAlchemy но и со всеми модулями которые его поддерживают. Ниже пример сессии в которой одновременно участвуют SQLAlchemy и pyramid_mailer.
Внимание
Пример ниже работает с версией repoze.sendmail==4.1
. Установить ее можно
припомощи команды:
pip install repoze.sendmail==4.1
Если возникает ошибка raise ValueError("TPC in progress")
, cмотри
https://github.com/repoze/repoze.sendmail/issues/31
# __init__.py
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from models import User, Session, Base, engine
import transaction
from pyramid_mailer.message import Message
message = Message(subject="hello world",
sender="example@yandex.ru",
recipients=["me@uralbash.ru"],
body="hello, uralbash")
def hello(request):
DBSession = Session(bind=engine)
result = str(DBSession.query(User).all())
import time
timestamp = int(time.time())
new_user = User(name=str(timestamp),
id=100500)
DBSession.add(new_user)
from pyramid_mailer import get_mailer
mailer = get_mailer(request)
mailer.send(message)
try:
transaction.commit()
except Exception as e:
transaction.abort()
return Response(str(e))
return Response(result)
if __name__ == '__main__':
Base.metadata.create_all()
DBSession = Session(bind=engine)
DBSession.add(User(name='Vasya'))
DBSession.add(User(name='Petya'))
transaction.commit()
settings = {'mail.host': 'smtp.yandex.ru',
'mail.port': '465',
'mail.ssl': True,
'pyramid_mailer.prefix': 'mail.',
'mail.username': 'example@yandex.ru',
'mail.password': 'example password'}
config = Configurator(settings=settings)
config.include('pyramid_mailer')
config.add_route('hello_world', '/')
config.add_view(hello, route_name='hello_world')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8000, app)
server.serve_forever()
В этом примере вьюха hello
записывает нового пользователя с id=100500
в
БД и отправляет письмо на адрес me@uralbash.ru
. При первом обновлении
страницы пользователь добавится в БД и отправится письмо. При последующих
обновлениях произойдет ошибка т.к. пользователь с таким id
уже существует,
при этом transaction.abort()
откатит изменения как в сессии
SQLAlchemy, так и в сессии pyramid_mailer, поэтому письмо не
отправится.
pyramid_tm автоматически подтверждает транзакцию в каждом запросе. Т.е.
если мы забыли написать transaction.commit()
, то он все равно вызовется,
при этом мы также можем вызывать его явно.
# __init__.py
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from models import User, Session, Base, engine
def hello(request):
DBSession = Session(bind=engine)
result = str(DBSession.query(User).all())
import time
timestamp = int(time.time())
new_user = User(name=str(timestamp))
DBSession.add(new_user)
return Response(result)
if __name__ == '__main__':
Base.metadata.create_all()
DBSession = Session(bind=engine)
DBSession.add(User(name='Vasya'))
DBSession.add(User(name='Petya'))
config = Configurator()
config.include('pyramid_tm')
config.add_route('hello_world', '/')
config.add_view(hello, route_name='hello_world')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8000, app)
server.serve_forever()
pyramid_sqlalchemy создает объект базового класса Base
и сессии
Session
автоматически. Мы просто указываем строку подключения к БД в
настройках и включаем модуль pyramid_sqlalchemy
в проект.
# __init__.py
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from models import User
from pyramid_sqlalchemy import BaseObject, Session
import transaction
def hello(request):
result = str(Session.query(User).all())
import time
timestamp = int(time.time())
new_user = User(name=str(timestamp))
Session.add(new_user)
return Response(result)
if __name__ == '__main__':
settings = {'sqlalchemy.url': 'sqlite:///:memory:'}
config = Configurator(settings=settings)
config.include('pyramid_tm')
config.include('pyramid_sqlalchemy')
BaseObject.metadata.create_all()
Session.add(User(name='Vasya'))
Session.add(User(name='Petya'))
transaction.commit()
config.add_route('hello_world', '/')
config.add_view(hello, route_name='hello_world')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8000, app)
server.serve_forever()
Файл с моделями теперь выглядит значительно проще.
# models.py
from sqlalchemy import Column, Integer, Text
from pyramid_sqlalchemy import BaseObject
class User(BaseObject):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(Text)
def __repr__(self):
return self.name
Несмотря на то, что фреймворк Pyramid не предоставляет инструментов для работы с базами дынных, есть большое количество сторонних модулей и расширений (написанных специально для Pyramid) которые реализуют этот функционал.
Для быстрого старта существует шаблон проекта alchemy
, по которому можно
быстро начать использовать пирамиду вместе с SQLAlchemy.
$ pcreate --scaffold alchemy sqla_demo
$ cd sqla_demo
$ python setup.py develop
Дополнительную информацию можно найти в Pyramid Cookbook.