Help us understand the problem. What is going on with this article?

急にPythonとPyramidを使って案件をこなす必要が出たので、どうやって勉強しているかのメモ

https://github.com/YukiMiyatake/YukiMiyatakeWorks/tree/prj/Python/Pyramid/main
このブランチにメモを残していく

フロントをやったことなかったけど、急にフロントとWebサーバを担当することになり
今まで使ったことのないPythonのPyramidで新規に作る必要が出たので
メモを残す(今更だけど)
だいたい私が初めてさわる言語フレームワークの時の仕事の進め方はこんなかんじ
リアルな言語習得です

まず、今回はWindowsとMacと両方のPCを行き来して仕事するので
両方のPCをセットアップしました
一応Dockerイメージも作ったけど、基本はネイティブでPythonを動かすことにします
エディタは安定のVSCode

環境構築

miniconda

https://docs.conda.io/en/latest/miniconda.html
anacondaは大きいので、minicondaをいれます
使い方は多分同じ
Python3.7をインストールします

VSCode

Pythonのプラグインを入れます
PythonerはLintに五月蝿いので、必ずLint入れましょう
もれなく PEP8とかいって怒ってくれます。PEP8ってなんなのか知りませんけど

Pythonの文法

事前知識

なんとなく、他人の話で知っている事柄

  • Lintが五月蠅い
  • オフサイドルール(カッコ使わずインデントでネスト表現)
  • セミコロン不要
  • コメントは#

これしか知らないけど、何とか雰囲気でなるだろうと楽観視

フレームワークの比較

PythonのWebフレームワーク比較を大雑把にしらべる

  • Django
    フルスタックフレームワーク
    Webで情報量が多い
    PythonのWebフレームワークではファーストチョイス

  • Bottle
    WSGI
    軽量フレームワーク

  • Flask
    WSGI
    軽量フレームワーク

  • Tornado
    ノンブロッキングI/O

  • Plone

あまりPyramidは情報がないなあ・・・

https://hotframeworks.com/languages/python
フレームワークのランキングあさる

Django、Flask、Tornadoとトップ3で
Bottle、AIOHTTP、web.py、Pyramid、web2.py がだいたい同率

Pyramidは、日本語情報が少ないので、英語情報でプログラムしなきゃダメそう・・

Githubのコードみながら推測

適当にStarの高いPythonプロジェクトを選びコードを見て
特徴をみつけ、わからないものを検索していくいつもの私のスタイル

  • 関数はdefで始まり:で終わる。Cスタイルのプロトタイプ宣言。戻り値や型指定なし
  • importでパッケージを読み込む。細かいローディングルールは必要になったら調べる
  • 複数行コメントは """""" で囲う。どうやらインデントが重要。 三連クォートはコメントではなく、文字列リテラルだそうです!
  • Pythonは全部オブジェクトでポインタで扱うっぽい。整数の変数でもポインタのようだ(間違ってたらゴメン)
  • 関数呼び出しは、キーワードで引数を指定する事も可能
  • *arg というプロトタイプはどうやら キーワードなし可変長引数のようだ
  • **kwargsは、キーワード付き可変長引数のようだ
  • class構文があり、だいたいオブジェクト指向として使えそうだ
  • __init__メソッドは、コンストラクタとして機能するようだ
  • メソッドは 第一引数に selfを入れる、thisポインタを明示的に渡す言語だ
  • classのインスタンス生成は クラス名() で行える
  • メソッドのオーバーロードは不可能なので、デフォルト引数や、Factory関数(クラスメソッド)を使う
  • 関数の前に @xxx とつけて関数のふるまいを変更するアスペクト指向ができる。デコレーターと呼ぶ
  • デコレーターの一つに @classmethodがあり、文字通り関数をクラスメソッドに指定できる
  • __init__.py 難しい。パッケージの初期化等を行う。空ファイルでも作らないと正常にロードされない。今後深堀りしよう

以上の調査で、パッケージ周りは改めて調べる必要があるが
Python言語に関しては だいたい理解できた

開発開始

プロジェクト作成

https://trypyramid.com/
https://docs.pylonsproject.org/projects/pyramid/en/latest/
公式は上記のようだ

$ pip install pyramid
$ pcreate -s alchemy testapp
$ cd testapp
$ python setup.py develop
$ pserve development.ini

一応SQL Alchemy(多分ORM)使うかもしれないのでalchemyオプションいれてtestappのテンプレート作成
development.iniの設定でサーバ立ち上げ、http://localhost:6543にアクセス
ここまでは余裕

SQLAlchemyの設定してないので、ブラウザに

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_testapp_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.

と表示される
とりあえず動かすために、SQLiteのデータベースを作る必要があるようだ

$ initialize_testapp_db development.ini

であっさり動いた

SnapCrab_NoName_2020-1-7_23-27-51_No-00.png

Pyramidっていうから黄色の砂漠をイメージしていたが、イメージカラーは赤のようだ

気になるものを調べていく

pychache が勝手に作られているので調べる

Pythonがコンパイルして作っているようだ。.gitignoreにいれてソース管理から外した

call コンストラクタとちがいインスタンスに()つけて呼べる。

C++的には operator () かな。

viewの拡張子 .jinja2を変えたい。

プロジェクトルートの initの configに追加

    config.add_jinja2_renderer(".html")

テンプレートセパレーター

jinja2では、{{ }} がデフォルトのセパレーターのようだ

block

{% extends "layout.html" %} {% block content %} {% endblock content %}
最初謎だったが、この中のHTMLを書き換えながら実行したらわかった
jinja2はテンプレートを継承できるようだ
layout.htmlがベース。それをextendsで明記している
そして子のテンプレートの blockで親のblockを書き換える
そうやってページを作成しているようだ

dynamic url

そう呼ぶのかしらないけど
{{request.static_url('testapp:static/pyramid.png')}}
この部分が気になった。おそらくパスを直接書くのではなく、フレームワークからレンダリング
これがお作法なんだと思う
パラメータの細かい事は後で調べよう。アプリ名を入れるあたりが謎い
他の命令は下記にあるので、何か使うかもしれない
https://docs.pylonsproject.org/projects/pyramid/en/latest/api/request.html

routing

routes.pyにある

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

まず includemeが気になる
https://docs.pylonsproject.org/projects/pyramid/en/latest/api/config.html
よくわからないけど、ぱっと見では includemeという名前の関数があれば、Pyramidがデフォルトで呼んでくれるようだ(関数名を省略できる)

config.add_routeの第一引数が名前、第二引数がURLになる。第一引数の名前でViewとマッチさせる

View

views/default.py

@view_config(route_name='home', renderer='../templates/mytemplate.html')
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': 'testapp'}

view_config デコレーターだ。
route_nameが、先ほどのルーティングの名前と一致させる
rendererは、レンダリングするテンプレート
他にもrequest_methodやら、match_param等があり、色々な場合分けができそう
https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/viewconfig.html

正常系は oneとprojectのはいった連想配列を返している。予想では mytemplate.htmlへ値をレンダリングしているはず。当たり。

おそらく、Dictionaryを返せばテンプレートでレンダリングするが
Response などで返せば、テンプレートを使わず独自にレンダリング

改造して覚える

Viewを追加

routing、View、Templateを追加して新しいページを作成

formからPOST

formタグを使ったらHTMLで単純に出来たのだが、formライブラリを使うべきらしく
pyramidではDeformを使うらしい
https://docs.pylonsproject.org/projects/pyramid/en/latest/quick_tutorial/forms.html

どうやらcolanderというものと併用するっぽい
colanderのオブジェクトから派生したスキーマ(class)を作り
そのclassのメンバ変数=フォームの項目になるらしい
非常に便利だ

schema
import colander
from deform.widget import TextAreaWidget

class NewPageSchema(colander.MappingSchema):
    username = colander.SchemaNode(colander.String(), title="username",
                                   validator=colander.Length(min=4, min_err='Shorter than minimum length ${min}'),
                                   default="")

viewで、このスキーマのインスタンスを生成し、スキーマからFormをクリエイトし
Viewに渡すだけでいい
せっかくなので、サンプルのみようみまねでclassを作る
(Python全くしらないけど見た感じ こんなかんじだろう)

view
class Views(object):
    def __init__(self, request):
        self.request = request

    @reify
    def form_(self):
        schema = NewPageSchema()
        btn = deform.form.Button(name="newpage", title="newpage")
        return deform.form.Form(schema, buttons=(btn,), action="/newpage")

    @view_config(route_name='home', renderer='../templates/mytemplate.html')
    def my_view(self):
        form = self.form_.render()
        return {"rendered_form": form}

    @view_config(route_name="newpage", renderer="../templates/newpage.html")
    def newpage(self):
        username = self.request.params.get("username", "")
        return {"username": username}

reify ってなんやねん・・・
https://docs.pylonsproject.org/projects/pyramid/en/latest/api/decorator.html

Pythonの propertyと似たふるまいをするが、一度呼ぶと値をDictionaryとして保存
なんだろう・・
Pythonの property って、たぶん他の言語のpropertyみたいなものだろう C#など
()を付けづに呼べる関数というか、値を返すものというか。
ただし、reifyの場合は1回目の呼び出しではCallされるが、それ以降は同じ値を返すっぽい
値を外から上書きできるので、シングルトンとも違う少し不思議なもの

上記の場合は、formを返す

postされたパラメーターは request.params.get("hoge") で取得できる

templateの方は

{{ rendered_form | safe }}

と、formの変数をおくだけでフォームが表示される。
safeが何かを調べたが、これはjinja2の機能のようだ
通常jinja2はXSS攻撃を防ぐために、レンダリング時にタグをエスケープするらしい
実際にsafeを消すと、エスケープされたHTMLが表示された。
ところが safeを加えると、エスケープせずにHTMLを書くことができる

formのバリデーションはまだかけてないが、formが非常に見通しがよくなった

Validationを行う

https://docs.pylonsproject.org/projects/deform/en/latest/validation.html
ここの通りにコードを書くも、そもそもCSRFSchemaのimportができてないのか、みつからないし
colander.Dropが無かったりするが
なんとかそれらしいコード書く

実はSchemaにvalidatorを設定しているので、あとはそれを適切に呼べばValidation出来る

validationを行うには、controlsを取得する
詳しく追ってないが、フォームの中身、値だと思う

そして、フォームオブジェクトのvalidateにcontrolsを渡すと、validatorがチェックされ
NGであれば ValidationFailure例外が発生する

今回は例外をキャッチした場合は、/ にリダイレクトしている。
もっといい方法が知りたい

        if 'newpage' in self.request.params:
            controls = self.request.POST.items()

            try:
                self.form_.validate(controls)
            except ValidationFailure as e:
                return HTTPFound(location='/')

        username = self.request.params.get("username")
        email = self.request.params.get("email")
        return {"username": username, "email": email }

今回はここまで。。。。 希望があれば続く

現状までのTag
https://github.com/YukiMiyatake/YukiMiyatakeWorks/tree/pyramid_deform_validation

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away