Домой Edit me on GitHub

2020-12-05

Каналы передачи данных | Сетевое программирование | Базы данных | Основы Веб-программирования

Базы данных (Models)

Сам фреймворк Pyramid не имеет встроенных возможностей работы с базами данных, в отличии от таких фреймворков как Django (Django ORM) и Ruby on Rails (Active Record). Хорошим выбором для реляционных БД будет ORM SQLAlchemy.

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()

Данный пример при каждом обновлении делает новую запись в БД и отдает их браузеру.

../../../_images/sqlalchemy_example.png

ZopeTransactionExtension

transaction

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()

transaction.abort

Теперь мы используем общий, глобальный менеджер транзакций, который работает не только с 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

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

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.

Previous: Настройки Next: Диспетчеризация URL