pythonでkintoneのアプリレコードを更新する方法（レコード1件）

# pythonでkintoneのアプリレコードを更新する方法（レコード1件）

## 最初に

　kintoneのアプリのデータを自動で一括取得したり、登録したりしたい状況があるかと思います。kintoneではREST APIが用意されており、いろいろなプログラム言語等からデータを簡単に取得したり登録したりすることができます。
　そこで、本記事ではpythonを使ってkintoneのアプリにレコードを1件更新する方法をご紹介します。  
  
【コンセプト】

* 本記事だけで完結
* 標準モジュールだけで記述
（exe化を見越して軽量に）

kintone公式のkintone APIの使い方の説明に従います。
'https://cybozu.dev/ja/kintone/docs/rest-api/records/update-record/'

pythonを使ったkintoneのREST APIの操作解説一覧  
'https://qiita.com/guitar_ell_r/items/e76a2134b9d59b19ae61'

【解説動画】  
<iframe width="560" height="315" src="https://www.youtube.com/embed/lZ5l0xDAH3U" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

---

## 環境

* windows11
* python 3.11.1

---

## 準備するもの

* kintoneアプリ
* 上記のレコード追加、編集の権限があるログイン名とパスワード

---
今回は下記フィールドが配置されたアプリを用意します。

|フィールド名|フィールドコード|
|---|---|
|文字列（1行）|文字列__1行_|
|複数選択|複数選択|

今回はアプリの更新なので、最初に何かしらのレコードが存在する必要があります。  
下記画像のように文字列1行に「更新前」、複数選択に「sample2, sample4」が選択されているようなレコードを用意します。

![003_アプリイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118247/9b33601e-786e-dcf2-8789-4abff1337d7c.png)

---

## プログラム

```python 003_record_put_one.py
import base64
import urllib.request
import json
```

必要なモジュールをインポートします。
すべてpythonの標準モジュールです。

```python 003_record_put_one.py
DOMAIN = "domain"## ドメイン　自環境のものを入れる
LOGIN = "login"## ログイン名　自環境のものを入れる
PASS = "pass"## パス　自環境のものを入れる
appno = 25## 取得したいアプリのアプリNo
record_no = 1## 更新したいアプリのアプリNo
```

ログイン名とパスワードはそれぞれ、ご自分の環境のものを入力してください。  
アプリ番号は私の環境では25となります。上記とともに適宜書き換えてください。  
上記画像の例ですと、アドレスが「devhswehn.cybozu.com/k/25/show#record=1」となっているので、ドメインは「devhswehn」、アプリ番号appnoは25、レコード番号record_noは1、となります。

次に、kintoneのREST APIを使うためのuriを設定します。  
kintoneの公式にレコード更新（1件）の場合は下記をuriとしてください、と書いてあります。  

'https://(サブドメイン名).cybozu.com/k/v1/record.json'  
このサブドメイン名の部分がDOMAINに相当するので、uriを下記のように作成します。

```python 003_record_put_one.py
uri = "https://" + DOMAIN + ".cybozu.com/k/v1/record.json"
```

kintoneのREST APIのルールで、ログイン名とパスワードを使った文字列をbase64でエンコードしたものをヘッダに記載して送信する、というものがあります。

'https://developer.cybozu.io/hc/ja/articles/201941754-kintone-REST-API%E3%81%AE%E5%85%B1%E9%80%9A%E4%BB%95%E6%A7%98'

　（パスワード認証の部分）

正確には　（ログイン名）:（パスワード）の文字列をbase64でエンコードしたもの、になります。  間にコロンが入っていることに注意してください。  
これに従ってAUTHという変数を作成します。

```python 003_record_put_one.py
AUTH = base64.b64encode((LOGIN + ":" + PASS).encode())
```

base64モジュールを使ってエンコードします。

次にリクエストのヘッダを作成します。  
下記にkintone公式の説明を引用します。  

>(2) パラメータを JSON形式で送信する場合（HTTP リクエストのリクエストボディに JSON データをセットする場合）  
>リクエストのヘッダとボディの例  
>リクエストヘッダ  
>GET /k/v1/record.json HTTP/1.1  
>Host: example.cybozu.com:443  
>X-Cybozu-Authorization: QWRtaW5pc3RyYXRvcjpjeWJvenU=  
>Authorization: Basic QWRtaW5pc3RyYXRvcjpjeWJvenU=  
>Content-Type: application/json  
>Content-Type に application/json を指定して下さい。  
 指定しない場合は JSON が解釈できないため、実行時エラーとなります。  

これをpythonでコーディングすると下記ようになります。  

```python 003_record_put_one.py
headers = {
    "Host":DOMAIN + ".cybozu.com:443",
    "X-Cybozu-Authorization":AUTH,
    "Content-Type": "application/json",
}
```

次にリクエストのボディを作成します。  
こちらもkintone公式を引用します。

>ボディ
{  
  "app": 1,  
  "id":1001,  
  　　"record": {  
    　　"文字列1行": {  
      　　"value": "  

(一部抜粋) 'https://cybozu.dev/ja/kintone/docs/rest-api/records/update-record/'

ボディは汎用的になるように内部から作っていきます。  
これから作成するプログラムのように作ることでkintoneのフィールドが増えても簡単に対応できます。  
  
最初にフィールドの部分のbodyを作成するために空のリストを作成します。  
このリストにフィールドの情報を追加していき、最後に辞書型にして結合するようにします。

```python 003_record_put_one.py
## フィールド部分のbodyのリスト
body_fields = []
```

最初に文字列１行のフィールドについて登録したい文字列をルールに従って作成します。  
作成した辞書をbody_fieldsに追加していきます。

```python 003_record_put_one.py
## 文字列１行
temp_body = {
    "文字列__1行_":{"value":"更新「後」"}
}
body_fields.append(temp_body)
```

複数選択です。

```python 003_record_put_one.py
## 複数選択
temp_body = {
    "複数選択":{"value":[]}## 何も選択しない場合は空のリスト
}
body_fields.append(temp_body)
```

複数選択は更新時に何も選択しない、ように更新するようにしてみます。  
何も選択しない場合は空のリストを入れることで対応します。

このように`temp_body`に追加したいフィールドについて記述し、`body_fields`に追加していくことで、一見複雑なJSON形式の記述を少し簡単にすることができます。  

ここまででbodyのフィールドの個々の部分の記述は終了です。最後にこれらを辞書型として結合しておきます。`body_field`という辞書型の変数に、これまで登録した`body_fields`を結合します。  
具体的には空の辞書`body_field = {}`を作成し、これに`body_fields`を1個ずつ辞書型として結合していきます。

```python 003_record_put_one.py
## 全てのフィールドを辞書型として結合する
body_field = {}
for item_dict in body_fields:
     body_field = {**body_field, **item_dict}
```

次に`"record:"の部分を作ります。kintone公式のボディの作り方を再掲載します。

>ボディ
{  
　"app": 1,  
  "id":1001,  
  　"record": {  
    　　"文字列1行": {  
      　　"value": "  

(一部抜粋) 'https://cybozu.dev/ja/kintone/docs/rest-api/records/update-record/'

ここまでで`"record":{}"`の中かっこの中身は`body_field`で作成済ですので、下記のように`record`部分を作成します。

```python 003_record_put_one.py
## record部分のbody　フィールドの内容を辞書型で結合
body_record = {"record":body_field}
```

次に`{"app":1, "id":1001}`の部分を作成します。

```python 003_record_put_one.py
## アプリ番号、レコード番号部分のbody
body_app_id = {
    "app":appno,
    "id":record_no
}
```

ここまでで、`body`のパーツを全て作成しました。最後に全てを結合します。

```python 003_record_put_one.py
## body全体を辞書型として作成する
body = {**body_app_id, **body_record}
```

これで、kintoneに送信するデータを全て作成しました。これらの情報からkintoneに送信するリクエストを作成します。

```python 003_record_put_one.py
## リクエスト作成
req = urllib.request.Request(
            url=uri, ## url
            data=json.dumps(body).encode(), ## body 
            headers=headers, ## header
            method="PUT", ## PUT
            )
```

pythonの標準モジュールurllibを使ってリクエストを作成します。  
こちらの記事を参考に作成しました。

'https://qiita.com/hoto17296/items/8fcf55cc6cd823a18217'  

引数の注意点として、dataはjson形式にすること、headersは辞書型にしておくことに留意してください。  
特にdataはjsonにして、それをバイト型にエンコードします。  
メソッドは公式説明に「HTTPメソッド」はPUTと書いてあるのでその通りにします。

次にkintoneにこのリクエストを送信します。

```python 003_record_put_one.py
try:
    response = urllib.request.urlopen(req)
except urllib.error.URLError as e:
        if hasattr(e, "reason"):
            res_error = (
                "We failed to reach a server." + "\n" +
                "Reason: " + e.reason + "\n"
            )
            print(res_error)
        elif hasattr(e, 'code'):
            res_error = (
                'The server couldn\'t fulfill the request.' + "\n" +
                'Error code: ', e.code + "\n"
            )
            print(res_error)
else:
    res_dict = json.load(response)
    print(res_dict)
```

tryで囲みましたが、リクエスト送信部分は下記です。

```python 003_record_put_one.py
response = urllib.request.urlopen(req)
```

エラーの補足については下記のサイトをほぼ同じように使わせてもらっています。  
'https://docs.python.org/ja/3/howto/urllib2.html#wrapping-it-up'  
（エラーをラップする　の「その2」）

送信に成功すると結果がres_dictに入ってきます。  
jsonで返ってくるので、使いやすいように辞書型に変換して格納します。

下記に全てのコードを掲載します。  
git hubにも掲載しています。  
'https://github.com/guitar-ell-r/kintoneAPI_pythonTIPS/blob/master/003_record_put_one/003_record_put_one.py'

```python python 003_record_put_one.py
## kintoneのアプリのレコードを1件更新する
## https://cybozu.dev/ja/kintone/docs/rest-api/records/update-record/
import base64
import urllib.request
import json

## 自分の環境のものを入力すること
DOMAIN = "domain"## kintoneのドメイン
LOGIN = "login"## ログイン名
PASS = "pass"## パスワード

appno = 25## 更新したいアプリのアプリNo
record_no = 1## 更新したいアプリのアプリNo

uri = "https://" + DOMAIN + ".cybozu.com/k/v1/record.json"

## https://developer.cybozu.io/hc/ja/articles/201941754-kintone-REST-API%E3%81%AE%E5%85%B1%E9%80%9A%E4%BB%95%E6%A7%98
## パスワード認証　の部分
## LOGIN と PASSを「：」でつないでbase64でエンコード
AUTH = base64.b64encode((LOGIN + ":" + PASS).encode())

## ヘッダ作成
headers = {
    "Host":DOMAIN + ".cybozu.com:443",
    "X-Cybozu-Authorization":AUTH,
    "Content-Type": "application/json",
}
## body作成

## フィールド部分のbodyのリスト
body_fields = []

## 文字列１行
temp_body = {
    "文字列__1行_":{"value":"更新「後」"}
}
body_fields.append(temp_body)

## 複数選択
temp_body = {
    "複数選択":{"value":[]}## 何も選択しない場合は空のリスト
}
body_fields.append(temp_body)

## 全てのフィールドを辞書型として結合する
body_field = {}
for item_dict in body_fields:
     body_field = {**body_field, **item_dict}

## record部分のbody　フィールドの内容を辞書型で結合
body_record = {"record":body_field}

## アプリ番号、レコード番号部分のbody
body_app_id = {
    "app":appno,
    "id":record_no
}

## body全体を辞書型として作成する
body = {**body_app_id, **body_record}
print(body)

## リクエスト作成
req = urllib.request.Request(
            url=uri, ## url
            data=json.dumps(body).encode(), ## body 
            headers=headers, ## header
            method="PUT", ## PUT
            )
## リクエスト送信　結果受け取り
try:
    response = urllib.request.urlopen(req)
except urllib.error.URLError as e:## エラーが生じた場合は補足する
    # https://docs.python.org/ja/3/howto/urllib2.html tryの参考
        if hasattr(e, "reason"):
            res_error = (
                "We failed to reach a server." + "\n" +
                "Reason: " + e.reason + "\n"
            )
            print(res_error)
        elif hasattr(e, 'code'):
            res_error = (
                'The server couldn\'t fulfill the request.' + "\n" +
                'Error code: ', e.code + "\n"
            )
            print(res_error)
else:
    res_dict = json.load(response)
    print(res_dict)
```