DynaModelを継承したクラス経由で生成したレコードのnullの取扱いで困ったときのメモ


環境

Python 3.6.1

事象

  • boto3ライブラリのDynaModelクラスのputメソッドを使って
    DynamoDBにレコードを登録した際、nullableなカラムに明示的にnullを入れても
    生成されたレコードにカラムが出来上がらない。

    →ライブラリ側の仕様でputメソッド内でレコード生成時にnull入りカラムは明示的に除去している様子。

上記の除去されたカラムを含むレコードを取得してきてSchemaクラスで定義しているフィールド(カラム)に
普通にアクセスしようとするとAttributeErrorで叩き落ちる。

対応

その1

nullの可能性があるカラムのみ、ビルドイン関数のhasattrを使用して判定してからアクセス。

hasattr.py

attribute = model.attribute if hasattr(model, "attribute") else None

その2

追加開発するタイミングとかで必須カラムを追加することになったりして、
すべての必須カラムがnullになりうるとする。
「都度都度hasattr噛ませたり噛ませるための関数を呼ぶのうざくない?」と思い至り
DynaModelクラスを拡張して、ほぼ恒久的にこの件を気にしなくて済む方法を考えてみた。
こっちがメモを残しておきたい本題。

dynamodel.py
from dynamorm import DynaModel as BaseDynaModel

class DynaModel(BaseDynaModel):
    def __getattr__(self, item):
        field_names = self.Schema.dynamorm_fields().keys()
        if item in field_names:
            return None
        else:
            raise AttributeError(
                ": '{}' object has no attribute '{}'".format(
                    self.__class__.__name__, item)
            )

__getattr__がPythonのクラスに標準搭載されている隠しメソッドで、
存在しないカラム(フィールド)へアクセスしたタイミングでのみ呼ばれる。
このメソッドが呼ばれるタイミングでNone返せば叩き落ちずに済む。

ただし、Schemaクラスで定義済みのもののみNoneを返したくて、
他のパターンはエラー返さないとバグ検知できなくなっちゃうのは避けたかったので、
Schemaクラスへ定義済みのフィールドを全部返してくれる
dynamorm_fields()ってライブラリ内で用意されてる関数を使って定義有無確認の処理を挟んだ。

参考

Swiftでいう変数に直接設定できるゲッターセッター的なものがPythonにはないのだろうかと
あれこれググって、以下の記事で__getattr__について見つけてとても助かりましたm(_ _)m

Pythonを書き始める前に見るべきTips