楽楽精算のAPI連携をPythonで行ってみた。

# 楽楽精算のAPI連携をPythonで行った経緯

業務で楽楽精算を使っていて、マスタ取込を自動化したいということで、
楽楽精算のAPI連携を利用してみました。
頂いたサンプルプログラムがJavaであり、中間サーバーにJavaがインストールできないことから、
Pythonで実装することになりました。
私がPythonでWebAPIを呼び出すのが初めてだったこともあり、トラブルの連続でしたので、
その記録を残すことにしました。

# 解答
インポート、定数宣言、ログ出力、エラーハンドリングなど割愛しています。
実際は定数などはコンフィグ読み込みにしてどのマスタ取込でも使いまわせるようにしました。

```python
def call_import_csv():
    # API呼出
    url = ('https://' +
           DOMAIN + '/' +
           ACCOUNT + '/api/' +
           VERSION + '/csvimport')
    headers = {
        'X-RS-apitoken': API_TOKEN
        # 'Content-Type': 'multipart/form-data;' # 指定してはいけない
    }

    payload = {
        'masterId': MASTER_ID,
        'duplicateKey': 0,
        'sendPasswordEmail': 0,
        'createCategory': 0
    }

    filename = os.path.basename(FILE_PATH)
    csvfile = open(FILE_PATH, 'rb')

    files = {'uploadFile': (filename, csvfile, 'text/csv')}
    data_obj = {'json': json.dumps(payload)}

    response = requests.post(url=url,
                             headers=headers,
                             files=files,
                             data=data_obj
                             )
```

# Requestsの仕様

* バウンダリの指定は不要
* headersのContent-Typeは1つしか指定できない。複数求められている場合は未指定が正解。
* dataに渡せる引数は2種類

# 楽楽APIのパラメータの仕様

* headersのContent-Typeを3つ指定する必要がある。
* dataにはjson型で渡さなければならない。

# 正解にたどり着くまでの過程
## headersの指定でハマる

ヘッダ複数ってどうなってんだ

楽楽のサポートからはバウンダリの指定がうまく行っていないとの回答が来ていたが、
Pythonではバウンダリの指定は不要とのこと。
当然、Javaサンプルを送ってきたサポートはPythonのサポートは期待できないわけで、
自力解決するしかない。
バウンダリを挟んで、Content-Typeを切り替えるようなJavaサンプルが来ているが、
ここをPythonで書き換えるのはかなり概念が違うっぽい。

そこで到達したのがこのサイト。
[データは文字列ではあってはいけません](https://stackoverflow.com/questions/27553082/valueerror-data-must-not-be-a-string)

> 私の場合、次のものを削除することで助けてくれました。
> 'Content-Type': 'application/json'
> 次に、データを辞書として渡す必要があります

これのContent-Typeを消せという回答がヒントになった。
どうやら、Content-Typeを複数必要とする場合は定義してはいけないらしい。

## ValueError: Data must not be a string

[データは文字列ではあってはいけません](https://stackoverflow.com/questions/27553082/valueerror-data-must-not-be-a-string)
ここで、解決策の1つが見つかる。
「JSON文字列をカプセル化します」
と正解と思われる回答をしている人がいたが、いまいちわからない。
それについて、サンプル見せてくれと書き込みがあるが、
それに対する追加回答なし。
どうやら、そのくらいわかっておけということなのだろうか。
説明するまでもないだろうということなのだろうか。

## dataの渡し方でハマる

調べた結果、Pythonでは、dataへ渡す方法が2種類あるらしい。
[[Python] GET・POSTリクエストによるWebデータの取得（Requestsモジュール）](https://hibiki-press.tech/learn_prog/python/requests_module/1882])

> パラメータの設定は、dataを使うか、jsonを使うか？
> 色々調べましたが、結局のところアクセスする先の「API仕様による」のではないかと思います。（当たり前かも・・・）

楽楽精算のCSVインポートはdataです。
ただし、辞書形式は受け取れません。

[Requests POSTの仕様](https://www.w3schools.com/python/ref_requests_post.asp)

> data		Optional. A dictionary, list of tuples, bytes or a file object to send to the specified url

辞書型か、タプルか、バイトか、ファイルオブジェクトって言ってるのかな。
とにかく、辞書型以外で送る方法があるよと言ってる。
JSON文字列をカプセル化というのはタプルにしろと言っているのかなと、繋がる。

## filesの渡し方でハマる

[クイックスタート](https://requests-docs-ja.readthedocs.io/en/latest/user/quickstart/)

```python
>>> import json
>>> url = 'https://api.******.com/some/endpoint'
>>> payload = {'some': 'data'}
>>> headers = {'content-type': 'application/json'}

>>> r = requests.post(url, data=json.dumps(payload), headers=headers)
```

調べたらcontent-type指定しなきゃダメそうなのに、指定するタイミングがない。

マルチパートはこうらしい

```python

>>> url = 'http://*******.org/post'
>>> files = {'file': open('report.xls', 'rb')}

>>> r = requests.post(url, files=files)
```

これでもエラーが出る。
uploadFileがないとか何とか言われて、困惑。

試行錯誤した結果、'file'を'uploadFile'に変更したら行けた。
ここも楽楽APIの仕様に合わせないとダメな文字列だったのか・・・。

そんなこんなで、なんとかできた。

# 終わりに
ちょっと試行錯誤していた時期と、記事を書いた時期が離れてしまったので、
かなり、やっつけな記事になってしまった。
楽楽精算のwebAPIをpythonで呼び出すことはできるよってことだけ言いたかった。

# 参考
データは文字列ではあってはいけません
https://stackoverflow.com/questions/27553082/valueerror-data-must-not-be-a-string

Requests POSTの仕様
https://www.w3schools.com/python/ref_requests_post.asp

Requestsの使い方
https://note.nkmk.me/python-requests-usage/

[Python] GET・POSTリクエストによるWebデータの取得（Requestsモジュール）
https://hibiki-press.tech/learn_prog/python/requests_module/1882