基本レイアウト(Basic Layout)

「alchemy」cookiecutterで生成されたスターターファイルは非常に基本的ですがほとんどの URL dispatch ベースの:app:Pyramid プロジェクトに共通する高水準のパターンに適しています。

「__init __.py」によるアプリケーション構成( Application configuration with __init__.py

ディスク上のディレクトリは「__init__.py」ファイルを含むことで、Pythonの package に変換できます。たとえ空であってもディレクトリをPythonパッケージとしてマークします。「__init__.py」はパッケージのディレクトリを示すマーカーとして、そしてアプリケーションの設定コードを格納するためのマーカーとして使われます。

「tutorial/__init__.py」を開きます。それはすでに以下を含んでいるはずです:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from pyramid.config import Configurator


def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    config.include('pyramid_jinja2')
    config.include('.models')
    config.include('.routes')
    config.scan()
    return config.make_wsgi_app()

この記事を見て行きましょう。まず、後のコードをサポートするためにいくつかのインポートが必要です。

1
2
3
from pyramid.config import Configurator


「__init__.py」は 「main」という名前の関数を定義します。ここでは「__init__.py」で定義した 「main」関数の全体を表示します:

 4
 5
 6
 7
 8
 9
10
11
12
def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    config.include('pyramid_jinja2')
    config.include('.models')
    config.include('.routes')
    config.scan()
    return config.make_wsgi_app()

「pserve development.ini」コマンドを実行すると、上記の 「main」関数が実行されます。これはいくつかの設定を受け入れて WSGI アプリケーションを返却します。 ( 「pserve」の詳細については (機械翻訳) 起動 を参照してください。)

次に「main」の中に Configurator オブジェクトを作成します:

7
    config = Configurator(settings=settings)

「settings」は 「Configurator」にキーワード引数として渡され、辞書の値は 「** settings」引数として渡されます。これは 「.ini」ファイルで設定した辞書型の値で、 「pyramid.reload_templates」、 「sqlalchemy.url」などのデプロイメント関連の値を含んでいます。

次に、プロジェクト内で「.jinja2」拡張子を持つレンダラーを使用できるように、Jinja2 テンプレートバインディングをインクルードします。

8
    config.include('pyramid_jinja2')

次にドットのPythonパスを使って 「models」パッケージをインクルードします。モデルの正確な設定については後で説明します。

9
    config.include('.models')

次にドットのPythonパスを使って「routes」モジュールをインクルードします。このモジュールについては、次のセクションで説明します。

10
    config.include('.routes')

注釈

Pyramidの pyramid.config.Configurator.include() メソッドは、コンフィグレータを拡張して、コードを機能に焦点を当てたモジュールに分割するための主なメカニズムです。

「main」はコンフィギュレータ( pyramid.config.Configurator.scan() )の「scan」メソッドを呼び出します。このメソッドは「tutorial」パッケージを再帰的にスキャンして、「@view_config」と他の特別なデコレータを探します。 「@ view_config」デコレータが見つかると、ビューの設定が登録され、アプリケーションのURLの1つがいくつかのコードにマッピングされます。

11
    config.scan()

最後に「main」が物事を設定し終わったので、 pyramid.config.Configurator.make_wsgi_app() メソッドを使って WSGI アプリケーションを返します:

12
    return config.make_wsgi_app()

ルート宣言(Route declarations)

「tutorial/routes.py」ファイルを開いてみてると、以下がすでに記載されているはずです:

1
2
3
def includeme(config):
    config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_route('home', '/')

2行目では、 「static」(名前)、 「static」(パス)、 「cache_max_age」(a)の3つの引数を持つ pyramid.config.Configurator.add_static_view() を呼び出します。キーワード引数)。

これは静的リソースビューを登録します。これは「/static」いう接頭辞で始まるURLにマッチします( 「add_static_view」の最初の引数によって)。これは「tutorial」パッケージの「static」ディレクトリから静的リソースを提供します。この場合、 「 http://localhost:6543/static/ 」以下で「add_static_view」の2番目の引数)。この宣言では、 「/static」で始まるURLはすべて静的ビューに移動する必要があります。 CSSファイルのような静的なファイルリソースへのパスを構成するために、パスの残りの部分( 「/foo」、 「/static/foo」など)が使われます。

3行目では、route configuration メソッドを使って pyramid.config.Configurator.add_route() を登録します。このメソッドは、URLが 「/」のときに使用されます。このルートは 「/」と同じ「パターン」を持っているので、URL「/」が訪れたときにマッチするルートです。例えば 「 http://localhost:6543/ 」。

「views」パッケージを使って宣言を表示する( View declarations via the views package )

Webフレームワークの主な機能は、リクエストされたURLが対応する route と一致するときに実行されるコード( view callable )への各URLパターンのマッピングです。アプリケーションは pyramid.view.view_config() デコレータを使ってこのマッピングを行います。

「views」パッケージの「tutorial/views/default.py」を開きます。それはすでに以下が含まれています:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from pyramid.response import Response
from pyramid.view import view_config

from sqlalchemy.exc import DBAPIError

from ..models import MyModel


@view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
def my_view(request):
    try:
        query = request.dbsession.query(MyModel)
        one = query.filter(MyModel.name == 'one').first()
    except DBAPIError:
        return Response(db_err_msg, content_type='text/plain', status=500)
    return {'one': one, 'project': 'myproj'}


db_err_msg = """\
Pyramid is having a problem using your SQL database.  The problem
might be caused by one of the following things:

1.  You may need to run the "initialize_tutorial_db" script
    to initialize your database tables.  Check your virtual
    environment's "bin" directory for this script and try to run it.

2.  Your database server may not be running.  Check that the
    database server referred to by the "sqlalchemy.url" setting in
    your "development.ini" file is running.

After you fix the problem, please restart the Pyramid application to
try it again.
"""

ここで重要な部分は、 「@view_config」デコレータが、装飾する関数(「my_view」)を view configuration と関連付けることです。

  • route_name (home)
  • 「renderer」はパッケージの 「templates」サブディレクトリからのテンプレートです。

「home」ビューに関連付けられたパターンがリクエスト中にマッチすると、 「my_view()」が実行されます。 「my_view()」は辞書を返却します。レンダラは 「templates/mytemplate.jinja2」テンプレートを使用して辞書の値に基づいてレスポンスを作成します。

「my_view()」は 「request」とい引数のみを受け付けます。これは、Pyramid view callable の標準コールシグネチャです。

pyramid.config.Configurator.scan() メソッドの「config.scan()」を実行したときの私たちの「__init __.py」を覚えていますか? scanメソッドを呼び出す目的は、 「@view_config」デコレータを見つけて処理して、アプリケーション内でビュー設定を作成することでした。 「scan」で処理されないとデコレータは事実上何もしません。 「@view_config」は scan で検出されることなく不活性です。

cookiecutterが作成したサンプルの「my_view()」は 「try:」と 「except:」節で、プロジェクトのデータベースへのアクセスに問題があるかどうかを検出して、エラーレスポンスを提供します。レスポンスには、ファイルの最後に表示されるテキストが含まれます。このテキストはブラウザに表示され,問題を解決するために取るべき可能なアクションについてユーザーに通知します。

「models」パッケージによるコンテンツモデル(Content models with the models package)

SQLAlchemyベースのアプリケーションでは、 model オブジェクトは、SQLデータベースを照会して作成されたオブジェクトです。 「model」パッケージは、 「alchemy] cookiecutterがモデルを実装するクラスを置く場所です。

最初に「tutorial/models/meta.py」を開きます。これには以下がすでに含まれています:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import MetaData

# Recommended naming convention used by Alembic, as various different database
# providers will autogenerate vastly different names making migrations more
# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
NAMING_CONVENTION = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s"
}

metadata = MetaData(naming_convention=NAMING_CONVENTION)
Base = declarative_base(metadata=metadata)

「meta.py」には、モデルを定義するためのインポートとサポートコードが含まれています。インデックスや制約のようなサポートオブジェクトの一貫した命名のために、ディクト型の「NAMING_CONVENTION」を作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import MetaData

# Recommended naming convention used by Alembic, as various different database
# providers will autogenerate vastly different names making migrations more
# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
NAMING_CONVENTION = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s"
}

次に、sqlalchemy.schema.MetaData クラスの引数「naming_convention」の値を「NAMING_CONVENTION」として「metadata」オブジェクトを作成します。

「MetaData」オブジェクトは、単一のデータベースのためのテーブルと他のスキーマ定義を表します。また、モデルの基底クラスとして使用する宣言型の 「Base」オブジェクトを作成する必要があります。モデルは、この「Base」から継承します。これは作成した「metadata」にテーブルを添付して、アプリケーションのデータベーススキーマを定義します。

15
16
metadata = MetaData(naming_convention=NAMING_CONVENTION)
Base = declarative_base(metadata=metadata)

次に「tutorial/models/mymodel.py」を開きます。これにはすでに以下が含まれています:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from sqlalchemy import (
    Column,
    Index,
    Integer,
    Text,
)

from .meta import Base


class MyModel(Base):
    __tablename__ = 'models'
    id = Column(Integer, primary_key=True)
    name = Column(Text)
    value = Column(Integer)


Index('my_index', MyModel.name, unique=True, mysql_length=255)

別々のモジュールでモデルを定義するのが簡単であるように、 「models」をパッケージとして定義しました。モデルクラスの簡単な例のために、 「mymodel.py」に 「MyModel」クラスを定義しました:

11
12
13
14
15
class MyModel(Base):
    __tablename__ = 'models'
    id = Column(Integer, primary_key=True)
    name = Column(Text)
    value = Column(Integer)

サンプルモデルは「__init__」メソッドを必要としません。理由はSQLAlchemyはデフォルトのコンストラクタが存在しない場合、それがマッピングされた属性と同じ名前のキーワード引数を受け入れます。

注釈

MyModelの使用例 (Example usage of MyModel):

johnny = MyModel(name="John Doe", value=10)

「MyModel」クラスは 「__tablename__」アトリビュートを持っています。これはSQLAlchemyに、このクラスのインスタンスを表すデータの格納に使用するテーブルを通知します。

最後に、「tutorial/models/__init__.py」を開きます。これにはすでに以下が含まれています:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
from sqlalchemy import engine_from_config
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import configure_mappers
import zope.sqlalchemy

# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines
from .mymodel import MyModel  # flake8: noqa

# run configure_mappers after defining all of the models to ensure
# all relationships can be setup
configure_mappers()


def get_engine(settings, prefix='sqlalchemy.'):
    return engine_from_config(settings, prefix)


def get_session_factory(engine):
    factory = sessionmaker()
    factory.configure(bind=engine)
    return factory


def get_tm_session(session_factory, transaction_manager):
    """
    Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.

    This function will hook the session to the transaction manager which
    will take care of committing any changes.

    - When using pyramid_tm it will automatically be committed or aborted
      depending on whether an exception is raised.

    - When using scripts you should wrap the session in a manager yourself.
      For example::

          import transaction

          engine = get_engine(settings)
          session_factory = get_session_factory(engine)
          with transaction.manager:
              dbsession = get_tm_session(session_factory, transaction.manager)

    """
    dbsession = session_factory()
    zope.sqlalchemy.register(
        dbsession, transaction_manager=transaction_manager)
    return dbsession


def includeme(config):
    """
    Initialize the model for a Pyramid app.

    Activate this setup using ``config.include('tutorial.models')``.

    """
    settings = config.get_settings()
    settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'

    # use pyramid_tm to hook the transaction lifecycle to the request
    config.include('pyramid_tm')

    # use pyramid_retry to retry a request when transient exceptions occur
    config.include('pyramid_retry')

    session_factory = get_session_factory(get_engine(settings))
    config.registry['dbsession_factory'] = session_factory

    # make request.dbsession available for use in Pyramid
    config.add_request_method(
        # r.tm is the transaction manager used by pyramid_tm
        lambda r: get_tm_session(session_factory, r.tm),
        'dbsession',
        reify=True
    )

「models/__init__.py」モジュールは、アプリケーション内のデータベース接続の設定に使うプライマリAPIを定義しています。また以下で説明するいくつかの機能が含まれています。

前述のように、 「models.meta.metadata」オブジェクトの目的は、データベースのスキーマを記述することです。これは「metadata」オブジェクトに付随する「Base」オブジェクトから継承するモデルを定義することによって行われます。 Pythonではコードがインポートされた場合のみコードが実行され、 `` 「mymodel.py」で定義された「models」テーブルを「metadata」に添付するには、インポートする必要があります。このステップをスキップすると、後で sqlalchemy.schema.MetaData.create_all() を実行すると、「metadata」オブジェクトは「models」テーブルについて知りませんのでテーブルは作成されません!

すべてのモデルをインポートするもう1つの重要な理由はモデル間の関係を定義するときに、SQLAlchemyがそれらの内部マッピングを見つけて構築するためには存在する必要があるためです。すべてのモデルをインポートした後にすべてのモデルが定義されていることを確認して接続を作成する前に、関数 sqlalchemy.orm.configure_mappers() を明示的に実行するのはこのためです。

次にデータベースに接続するための関数をいくつか定義します。最初のレベルと最低レベルは 「get_engine」関数です。””これは「development.ini」ファイルの [app:main] セクションのの 「sqlalchemy」接頭辞の設定から sqlalchemy.engine_from_config() を使用して、 SQLAlchemy データベースエンジンを生成します。この設定はURIです( 「sqlite://」と似ています)。

15
16
def get_engine(settings, prefix='sqlalchemy.'):
    return engine_from_config(settings, prefix)

「get_session_factory」関数は SQLAlchemy データベースエンジンを受け取り、 SQLAlchemy の class sqlalchemy.orm.session.sessionmaker クラスから「session_factory」を生成します。この「session_factory」は、データベースエンジンにバインドされたセッションの作成に使用されます。

19
20
21
22
def get_session_factory(engine):
    factory = sessionmaker()
    factory.configure(bind=engine)
    return factory

「get_tm_session」関数はデータベースセッションをトランザクションマネージャに登録して、「dbsession」 オブジェクトを返します。トランザクションマネージャを使用すると、例外が発生しない限りはアプリケーションはすべてのリクエストの後に自動的にトランザクションコミットを発行します。例外が発生した場合はトランザクションは中止されます。

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def get_tm_session(session_factory, transaction_manager):
    """
    Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.

    This function will hook the session to the transaction manager which
    will take care of committing any changes.

    - When using pyramid_tm it will automatically be committed or aborted
      depending on whether an exception is raised.

    - When using scripts you should wrap the session in a manager yourself.
      For example::

          import transaction

          engine = get_engine(settings)
          session_factory = get_session_factory(engine)
          with transaction.manager:
              dbsession = get_tm_session(session_factory, transaction.manager)

    """
    dbsession = session_factory()
    zope.sqlalchemy.register(
        dbsession, transaction_manager=transaction_manager)
    return dbsession

最後に、「includeme」関数を定義します。これは pyramid.config.Configurator.include() でPyramidアプリケーションアドオンでコードをアクティブにするためのフックです。これは、アプリケーションの「main」関数で 「config.include(.models)」を実行したときに実行されるコードです。この関数はアプリケーションからの設定を受け取り、エンジンを作成してアプリケーションへのリクエスト応答処理に代わって作業を行うために使用できる「request.dbsession」プロパティを定義します。

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def includeme(config):
    """
    Initialize the model for a Pyramid app.

    Activate this setup using ``config.include('tutorial.models')``.

    """
    settings = config.get_settings()
    settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'

    # use pyramid_tm to hook the transaction lifecycle to the request
    config.include('pyramid_tm')

    # use pyramid_retry to retry a request when transient exceptions occur
    config.include('pyramid_retry')

    session_factory = get_session_factory(get_engine(settings))
    config.registry['dbsession_factory'] = session_factory

    # make request.dbsession available for use in Pyramid
    config.add_request_method(
        # r.tm is the transaction manager used by pyramid_tm
        lambda r: get_tm_session(session_factory, r.tm),
        'dbsession',
        reify=True
    )

これは、ストックアプリケーションのモデル、ビュー、および初期化コードに関して、ここにあるすべてです。

このチュートリアルでは、 「Index」インポートと「Index」オブジェクトの作成は「mymodel.py」では必須ではなく、次のステップで削除されます。