6:在ZODB中存储资源

在数据库中存储和检索资源树容器和项目。

背景

我们现在有了一个资源树,可以无限深入,一路添加项目和子容器。我们显然需要一个数据库,一个可以支持层次结构的数据库。zodb是一个基于事务的python数据库,支持透明持久性。我们将修改我们的应用程序以使用ZODB。

在此过程中,我们将增加 pyramid_tm , a system for adding transaction awareness to our code. With this we don't need to manually manage our transaction begin/commit cycles in our application code. Instead, transactions are setup transparently on request/response boundaries, outside our application code.

目标

  • 创建一个将记录添加到持久存储的CRUD应用程序。

  • 安装程序 pyramid_tmpyramid_zodbconn .

  • 使我们的“内容”类继承自 Persistent .

  • 在应用程序中设置数据库连接字符串。

  • 建立一个根工厂,从zodb而不是从内存为根提供服务。

步骤

  1. 我们将使用前面的步骤作为起点:

    $ cd ..; cp -r addcontent zodb; cd zodb
    
  2. 在中引入一些新的依赖项 zodb/setup.py

     1from setuptools import setup
     2
     3requires = [
     4    'pyramid',
     5    'pyramid_jinja2',
     6    'ZODB3',
     7    'pyramid_zodbconn',
     8    'pyramid_tm',
     9    'pyramid_debugtoolbar'
    10]
    11
    12setup(name='tutorial',
    13      install_requires=requires,
    14      entry_points="""\
    15      [paste.app_factory]
    16      main = tutorial:main
    17      """,
    18)
    
  3. We can now install our project:

    $ $VENV/bin/python setup.py develop
    
  4. 修改我们的 zodb/development.ini 要包括一些配置并提供数据库连接参数:

     1[app:main]
     2use = egg:tutorial
     3pyramid.reload_templates = true
     4pyramid.includes =
     5    pyramid_debugtoolbar
     6    pyramid_zodbconn
     7    pyramid_tm
     8zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
     9
    10[server:main]
    11use = egg:pyramid#wsgiref
    12host = 0.0.0.0
    13port = 6543
    14
    15# Begin logging configuration
    16
    17[loggers]
    18keys = root, tutorial
    19
    20[logger_tutorial]
    21level = DEBUG
    22handlers =
    23qualname = tutorial
    24
    25[handlers]
    26keys = console
    27
    28[formatters]
    29keys = generic
    30
    31[logger_root]
    32level = INFO
    33handlers = console
    34
    35[handler_console]
    36class = StreamHandler
    37args = (sys.stderr,)
    38level = NOTSET
    39formatter = generic
    40
    41[formatter_generic]
    42format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
    43
    44# End logging configuration
    
  5. 我们的启动代码 zodb/tutorial/__init__.py 获取一些引导更改:

     1from pyramid.config import Configurator
     2from pyramid_zodbconn import get_connection
     3
     4from .resources import bootstrap
     5
     6
     7def root_factory(request):
     8    conn = get_connection(request)
     9    return bootstrap(conn.root())
    10
    11def main(global_config, **settings):
    12    config = Configurator(settings=settings,
    13                          root_factory=root_factory)
    14    config.include('pyramid_jinja2')
    15    config.scan('.views')
    16    return config.make_wsgi_app()
    
  6. 我们的观点 zodb/tutorial/views.pyadd_folderadd_content for how new instances are made and put into a container:

     1from random import randint
     2
     3from pyramid.httpexceptions import HTTPFound
     4from pyramid.location import lineage
     5from pyramid.view import view_config
     6
     7from .resources import (
     8    Root,
     9    Folder,
    10    Document
    11    )
    12
    13
    14class TutorialViews(object):
    15    def __init__(self, context, request):
    16        self.context = context
    17        self.request = request
    18        self.parents = reversed(list(lineage(context)))
    19
    20    @view_config(renderer='templates/root.jinja2',
    21                 context=Root)
    22    def root(self):
    23        page_title = 'Quick Tutorial: Root'
    24        return dict(page_title=page_title)
    25
    26    @view_config(renderer='templates/folder.jinja2',
    27                 context=Folder)
    28    def folder(self):
    29        page_title = 'Quick Tutorial: Folder'
    30        return dict(page_title=page_title)
    31
    32    @view_config(name='add_folder', context=Folder)
    33    def add_folder(self):
    34        # Make a new Folder
    35        title = self.request.POST['folder_title']
    36        name = str(randint(0, 999999))
    37        new_folder = Folder(title)
    38        new_folder.__name__ = name
    39        new_folder.__parent__ = self.context
    40        self.context[name] = new_folder
    41
    42        # Redirect to the new folder
    43        url = self.request.resource_url(new_folder)
    44        return HTTPFound(location=url)
    45
    46    @view_config(name='add_document', context=Folder)
    47    def add_document(self):
    48        # Make a new Document
    49        title = self.request.POST['document_title']
    50        name = str(randint(0, 999999))
    51        new_document = Document(title)
    52        new_document.__name__ = name
    53        new_document.__parent__ = self.context
    54        self.context[name] = new_document
    55
    56        # Redirect to the new document
    57        url = self.request.resource_url(new_document)
    58        return HTTPFound(location=url)
    59
    60    @view_config(renderer='templates/document.jinja2',
    61                 context=Document)
    62    def document(self):
    63        page_title = 'Quick Tutorial: Document'
    64        return dict(page_title=page_title)
    
  7. 使我们的资源在 zodb/tutorial/resources.py

     1from persistent import Persistent
     2from persistent.mapping import PersistentMapping
     3import transaction
     4
     5
     6class Folder(PersistentMapping):
     7    def __init__(self, title):
     8        PersistentMapping.__init__(self)
     9        self.title = title
    10
    11
    12class Root(Folder):
    13    __name__ = None
    14    __parent__ = None
    15
    16
    17class Document(Persistent):
    18    def __init__(self, title):
    19        Persistent.__init__(self)
    20        self.title = title
    21
    22
    23def bootstrap(zodb_root):
    24    if not 'tutorial' in zodb_root:
    25        root = Root('My Site')
    26        zodb_root['tutorial'] = root
    27        transaction.commit()
    28    return zodb_root['tutorial']
    
  8. No changes to any templates!

  9. 运行 Pyramid 应用程序时使用:

    $ $VENV/bin/pserve development.ini --reload
    
  10. 在浏览器中打开http://localhost:6543/。

分析

我们安装 pyramid_zodbconn 处理与zodb的数据库连接。这也会提取zodb3包。

使能 pyramid_zodbconn

  • 我们激活包配置使用 pyramid.includes .

  • 我们定义了一个 zodbconn.uri 使用data.fs文件的路径进行设置。

在根工厂中,我们不再使用旧的根对象,而是获得到zodb的连接,并使用它创建对象。

我们的资源需要一些小的改变。文件夹现在从继承 persistent.PersistentMapping 和文档来源 persistent.Persistent . 注意 Folder now needs to call super()__init__ 方法,否则映射将无法正确初始化。

在引导程序上,注意 transaction.commit() to commit the change. 这是因为在第一次启动时,我们希望在继续之前有一个根资源。

ZODB有多种部署模式。例如,Zeo是跨多个进程和主机的纯Python对象存储服务。relstorage允许您使用RDBMS来存储/检索您的python pickles。

Extra Credit

  1. 创建删除文档的视图。

  2. 删除配置行,其中包括 pyramid_tm . 重新启动应用程序时会发生什么?重新启动时是否保留更改?

  3. 如果删除名为 Data.fs* 是吗?