[備忘録] boto3.dynamodb.conditons.Keyとboto3.dynamodb.conditions.Attrについて
boto3において、DynamoDB conditionsというものがある。
Query
やUpdateItem
などで"ConditionExpression"
に渡す条件の記述に使用する。
使い方
Attr('id').eq('E1EE1703-0AA6-4EFF-A1E9-CBA15991B6B4')
上記例では、DynamoDB TableのItemのid
というAttributeにE1EE1703-0AA6-4EFF-A1E9-CBA15991B6B4
と等しい文字列が入っているという条件になる。
上記例では、Attr
を使用したがKey
でも基本的な使い方は変わらない。
また、Key
よりもAttr
の方が条件を記述するeq()
などの関数の種類が多い (詳しくはドキュメントを見てほしい)。
また、論理和(or)と論理積(and)の記述方法は次のようになっている。
# 論理和 (or) Attr('type').eq('router') | Attr('type').eq('firewall') # 論理積 (and) Attr('type').eq('router') | Attr('vendor').eq('cisco')
Key
とAttr
の違い
結論から言うと、正直わからなかった。
たぶん、PartitionKey
やSortKey
にはKey
を、それ以外にはAttr
を使う想定ではないかと予測するが、確証はない。
Pythonのコードを見る限りでは、Attr
の方がKey
よりも条件を記述するための関数が多いだけで、他の違いを見受けられない。
JSON化するにあたって
現在参画しているプロジェクトではDynamoDBへの操作失敗時に、操作に使用した関数に渡した引数をログに出力している。
また、ログ出力に関してはJSON化を行っている。
しかし、JSON化が不可能な型(class)に関しては文字列化しているだけである。
このとき、Key
やAttr
を使って記述した条件は次のような文字列になる。
'<boto3.dynamodb.conditions.Equals object at 0x10aae33c8>'
そのため、このままではどのような条件なのかがわからない。
JSON化したログに条件を残すためには、json.dumps()
のdefault
という引数に次の関数を渡すことで多少見にくいが、条件の情報を残すことができた。
def convert(obj): if isinstance(obj, ConditionBase): return obj.get_expression() if isinstance(obj, AttributeBase): return obj.name option = { 'Condition': Key('id').eq(100) } text = json.dumps(option, default=convert, indent=2) # { # "Condition": { # "format": "{0} {operator} {1}", # "operator": "=", # "values": [ # "id", # 100 # ] # } # }
以下、これを見つけるために行った調査の内容について記述する。
まず、調査にあたってはipythonを使用した。
具体的には、ipythonであたりをつけつつ、ソースコードの情報も加味して、JSON化に必要な手段を調べた。
Key
やAttr
を使って記述した条件は、原則ConditionBase
というクラスを継承したクラスのオブジェクトであった。
直接継承してるのもあれば、先祖をたどるとConditionBase
があったってのもある。
この、ConditionBase
にはget_expression()
という関数がありformat
/operator
/values
という3つのキーを持つdict
を返す。
format
: 条件式。Pythonのformat()
やf-stringsの記述方法に似ている。- operator: 条件の種類を示す、と思う。
=
とかOR
とか。 - values: 条件式で使用する値。配列で入っている。
だいたい、こんな役割があって、ipythonで見るとこんな感じだった。
In [11]: a = Key('id').eq(100) In [12]: a.get_expression() Out[12]: {'format': '{0} {operator} {1}', 'operator': '=', 'values': (<boto3.dynamodb.conditions.Key at 0x10ab318d0>, 100)}
In [14]: b = Key('timestamp').between(10, 20) In [15]: b.get_expression() Out[15]: {'format': '{0} {operator} {1} AND {2}', 'operator': 'BETWEEN', 'values': (<boto3.dynamodb.conditions.Key at 0x10ab313c8>, 10, 20)}
In [16]: c = Key('type').eq('device') & Key('name').eq('test') In [17]: c.get_expression() Out[17]: {'format': '({0} {operator} {1})', 'operator': 'AND', 'values': (<boto3.dynamodb.conditions.Equals at 0x10aaeb630>, <boto3.dynamodb.conditions.Equals at 0x10aaeba20>)}
In [18]: d = Attr('isDelete').exists() In [19]: d.get_expression() Out[19]: {'format': '{operator}({0})', 'operator': 'attribute_exists', 'values': (<boto3.dynamodb.conditions.Attr at 0x10a93fa90>,)}
これらを見る限り、values
にはKey
やAttr
, ConditionBase
を継承したクラスのオブジェクト、実際の値が入ることがわかる。
この段階で、ConditionBase
を継承したクラスのオブジェクトであればget_expression()
を実行すれば、ほとんどの情報を取ることができるとわかった。
あとは、Key
やAttr
をどうするのか。
Key
もAttr
もAttributeBase
というクラスを継承していた。
AttributeBase
にはname
というプロパティがあり、オブジェクトを作成する際に渡している引数が入っている。
(Key('name')
だとすると、'name'
が入っている)
どのAttributeか区別できれば良いと考えれば、AttributeBase
を継承したクラスのオブジェクトはname
を出力されば良い。
もし、Key
なのかAttr
なのかまで区別しようと思えば、json.dumps
のdefault
に渡す関数を次のようにすれば区別できるだろう。
def convert(obj): if isinstance(obj, ConditionBase): return obj.get_expression() if isinstance(obj, Attr): return f'Attr("{obj.name}")' if isinstance(obj, Key): return f'Key("{obj.name}")'