Weaviateの初歩　Pythonでオブジェクトを扱ってみる

[以前の記事](https://qiita.com/yasuyuki_terashi/items/64a6f4d71c64a18e17a3)でコレクションを作ったので(スキーマが定義されたので)、実際にデータを作成、取得、削除することをやってみます。

# どのようなデータを作るか
先の記事で、以下のようなコレクションTestA,TestBを作りました（ER図で表すのがいいのかよく分かりませんが）。
```mermaid
erDiagram
    TestA ||--o{ TestB:""

    TestA{
        text title
        text summary
    }
    TestB{
        int chapter
        text text
    }
    
```
そこで、TestAに親オブジェクトを一つ、TestBに子オブジェクトを三つ作成したいと思います。また親オブジェクトと子オブジェクトとの間に相互のリファレンスを設定します。リファレンスには名前を付けることが出来るので、親オブジェクトから子オブジェクトへのリファレンスを'children'、反対方向を'parent'と名付けることにします。


# オブジェクトの作成
次のようなスクリプトを作って実行してみました。
```python:10F_insertData_Test.py
import weaviate

try: #(1)
    weaviate_client = weaviate.connect_to_custom(
        http_host="xxx.xxx.xxx.xxx",  # URL only, no http prefix
        http_port=30020,
        http_secure=False,   # Set to True if https
        grpc_host="xxx.xxx.xxx.xxx",
        grpc_port=30021,      # Default is 50051, WCD uses 443
        grpc_secure=False,   # Edit as needed
    )
    assert weaviate_client.is_live()

    TestA = weaviate_client.collections.get('TestA') #(2)
    TestB = weaviate_client.collections.get('TestB')

    # PartI
    # TestAに親オブジェクトを一つ挿入する
    parentUUID = TestA.data.insert({ #(3)
        'title':"TestDocument",
        'summary':"この文章はテストドキュメントです。"
    })
    print("parent object insert success:uuid=",parentUUID)

    chapters = [1,2,3]
    texts = ["この文章の目的....","仕様詳細について....","検収条件について...."]
    childrenUUIDs = []

    # PartII
    # TestBに子オブジェクトをバッチ挿入する
    with TestB.batch.dynamic() as batch: #(4)
        for chapter,text in zip(chapters,texts):
            uuid = batch.add_object( #(5)
                properties={
                    'chapter': chapter,
                    'text': text
                },
                references={'parent':parentUUID}
            )
            childrenUUIDs.append(uuid)
            print("children uuid=",uuid)
    print("children object insert success")

    # PartIII
    # TestAに挿入した親オブジェクトに対して、子オブジェクトへのリファレンスをバッチ挿入する
    with TestA.batch.dynamic() as batch:
        batch.add_reference( #(6)
            from_property='children',
            from_uuid=parentUUID,
            to=childrenUUIDs
        )
    print("adding reference of parent's obj success")

finally:
    weaviate_client.close()
```
(解説)
PartI
(1)全体をtry-finallyで囲っています（この形式が推奨されているようです）。これで例外が発生しても、確実にclientをcloseできます。
(2)`weaviate_client.collections.get`でcollectionを得ることが出来ます。
(3)`TestA.data.insert`でオブジェクトを一つコレクションに挿入できます。引数にはプロパティに設定するデータを辞書で設定してます。本当は一緒にリファレンスも指定できるのですが、リファレンス先の子オブジェクトがまだ作成されていないので、まだ行っていません。返り値はオブジェクトのuuidです。
PartII
(4)`TestB.batch.dynamic()`から`CollectionBatchingContextManager`というものが返ってきます。`.dynamic()`で返ってくるコンテキストマネージャーは自動的にバッチサイズの調整などを行ってくれます（だそうです）。これを使って挿入するオブジェクトを登録するわけですが、withブロックを抜けると自動的にフラッシュされます。
(5)`batch.add_object()`で挿入するオブジェクトを登録します。プロパティと共に、リファレンスを指定してます。`reference=`には{'リファレンスの名前':リファレンス先のuuid}の形で指定します。
partIII
(6)TestAに挿入済みのオブジェクトに対して、`add_reference()`でリファレンスを設定しています。なお`to=`にはリストchildrenUUIDsを渡してまとめて子オブジェクトへのリファレンスを指定しています。

これで親子オブジェクトが挿入されたので、次項でオブジェクトを取得してみます。

# オブジェクトの取得
以下のスクリプトで、オブジェクトの取得を試みます。
```python:10G_queryData_Test.py
import weaviate
from weaviate.classes.query import QueryReference

try:
    weaviate_client = weaviate.connect_to_custom(
        http_host="xxx.xxx.xxx.xxx",  # URL only, no http prefix
        http_port=30020,
        http_secure=False,   # Set to True if https
        grpc_host="xxx.xxx.xxx.xxx",
        grpc_port=30021,      # Default is 50051, WCD uses 443
        grpc_secure=False,   # Edit as needed
    )
    assert weaviate_client.is_live()

    TestA = weaviate_client.collections.get('TestA')
    TestB = weaviate_client.collections.get('TestB')

    print('parent object')
    for obj in TestA.iterator(
        return_references=QueryReference( #(1)
            link_on='children',
            return_properties=["uuid"])
    ):
        print(obj.uuid)
        print('properties',obj.properties) #(2)
        for r in obj.references['children'].objects: #(3)
            print('reference uuid:',r.uuid) #(4)
        print('--------')

    print('children objects')
    for obj in TestB.iterator(
        return_references=QueryReference(
            link_on='parent',
            return_properties=["uuid"])
    ):
        print(obj.uuid)
        print('properties',obj.properties)
        for r in obj.references['parent'].objects:
            print('reference uuid:',r.uuid)
        print('--------')

finally:
    weaviate_client.close()
```
(解説)
(1)ただの`for obj in <collection>.iterator()`だと、objにプロパティの内容は返ってきますが、リファレンスの内容がちっとも返ってきません。実はここが個人的な **大ハマりポイント** でした。
そこで、`return_references=QueryReference()`を使って取得するリファレンスについて指定します。`link_on=`にリファレンスの名前、`return_properties=`に取得するリファレンス先のプロパティを指定します。
なお、あらかじめ`from weaviate.classes.query import QueryReference`でQueryReferenceをimportしておきます。
(2)`obj.properties`をprintで出力すると、辞書形式で出てくることが分かります。
(3)`obj.references['リファレンスの名前']`でリファレンスを取得できます。さらに`.objects`を付けてリファレンス先のオブジェクトを得ることが出来ます。リファレンス先オブジェクトは複数ある可能性があるので、forループを使ってオブジェクトを取り出します。

子オブジェクトについては、親オブジェクトと同様なので解説を省略します。
実行結果は以下のようになりました。
```bash:実行例
$ python 10G_queryData_Test.py
parent object
01a887ac-8883-4b6b-add6-6a20edf94ecc
properties {'title': 'TestDocument', 'summary': 'この文章はテストドキュメントです。'}
reference uuid: 05bdb23e-5725-4068-9cd7-16b6dc3849bd
reference uuid: ce91ddae-b24b-4d35-a0bb-ee84209fe4ac
reference uuid: 3716d9c1-3c1f-4416-ade2-5bac112dc383
--------
children objects
05bdb23e-5725-4068-9cd7-16b6dc3849bd
properties {'chapter': 1, 'text': 'この文章の目的....'}
reference uuid: 01a887ac-8883-4b6b-add6-6a20edf94ecc
--------
3716d9c1-3c1f-4416-ade2-5bac112dc383
properties {'chapter': 3, 'text': '検収条件について....'}
reference uuid: 01a887ac-8883-4b6b-add6-6a20edf94ecc
--------
ce91ddae-b24b-4d35-a0bb-ee84209fe4ac
properties {'chapter': 2, 'text': '仕様詳細について....'}
reference uuid: 01a887ac-8883-4b6b-add6-6a20edf94ecc
--------
```
# オブジェクトの全削除
オブジェクトの挿入や、リファレンス設定がうまくいかない場合、TestA,TestBのコレクションから全削除したくなる時があります。以下のスクリプトで、両コレクションからのオブジェクト全削除をやってみます。
```python:10H_cleanData_Test.py
import weaviate
from weaviate.classes.query import Filter

try:
    weaviate_client = weaviate.connect_to_custom(
        http_host="xxx.xxx.xxx.xxx",  # URL only, no http prefix
        http_port=30020,
        http_secure=False,   # Set to True if https
        grpc_host="xxx.xxx.xxx.xxx",
        grpc_port=30021,      # Default is 50051, WCD uses 443
        grpc_secure=False,   # Edit as needed
    )
    assert weaviate_client.is_live()

    TestA = weaviate_client.collections.get('TestA')
    TestB = weaviate_client.collections.get('TestB')

    # 親オブジェクトをすべて削除
    responce = TestA.query.fetch_objects() #(1)
    ids = [o.uuid for o in responce.objects]
    if ids:
        TestA.data.delete_many(
            where=Filter.by_id().contains_any(ids) #(2)
        )

    # 子オブジェクトを全て削除
    responce = TestB.query.fetch_objects()
    ids = [o.uuid for o in responce.objects]
    if ids:
        TestB.data.delete_many(
            where=Filter.by_id().contains_any(ids)
        )

finally:
    weaviate_client.close()
```
(解説)
(1)引数の無い`.query.fetch_objects()`でコレクション中の全オブジェクトを取得しています。次行でオブジェクトのuuidのリスト`ids`を作成しています。
(2)リスト`ids`にあるuuidのオブジェクトを削除します。`where=`にフィルタを指定します。フィルタはクラス`Filter`で作成します。`.by_id()`と`.contains_any()`を組み合わせてます。なお、`.contains_any()`は一つ以上の内容を指定しないとエラーになるので、全体を`if ids:`ブロックの中に入れて、idsの内容がある時だけ実行するようにしています。

子オブジェクトについては、親オブジェクトと同様なので解説を省略します。
`python 10H_cleanData_Test.py`を実行した後、`python 10G_queryData_Test.py`を走らせると結果は以下のようになります。
```bash
$ python 10G_queryData_test.py
parent object
children objects
```
親オブジェクト、子オブジェクトとも全削除されていることが分かります。