急に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をいれます
使い方は多分同じ
Python３．７をインストールします

##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/
公式は上記のようだ


```bash
$ 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13916/b7fb1def-6679-2988-9e44-837f176f831b.png)

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

##気になるものを調べていく
###__pychache__ が勝手に作られているので調べる
Pythonがコンパイルして作っているようだ。.gitignoreにいれてソース管理から外した

### __call__  コンストラクタとちがいインスタンスに()つけて呼べる。
C++的には operator () かな。

### viewの拡張子 .jinja2を変えたい。
プロジェクトルートの __init__の configに追加

```py
    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にある

```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

```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のメンバ変数＝フォームの項目になるらしい
非常に便利だ

```py: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全くしらないけど見た感じ こんなかんじだろう）

```py: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の方は

```jinja
{{ 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例外が発生する

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

```py
        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