SpreadSheetを方眼にしてQRコードをプロットする

以前仕事で、SpreadSheetでQRコードを管理したいという要望があったのだが、SpreadSheetに画像ってどうやってアップロードするんだ？GASでやればいいのか？わからん！とかいろいろ悩んでいるうちに我を忘れて、SpreadSheetを方眼にしてそこにQRコードをプロットするという、明日から使える無駄技術を身につけてしまったのでここに書いておく。

#SpreadSheetを外部から操作するための準備
SpreadSheetをgspreadで操作するためにはGoogleCloudのコンソールからAPIを有効にして、jsonキーをダウンロードしないといけない。ここが一番めんどくさかった。
以下が参考になります。
[参考]
[【もう迷わない】Pythonでスプレッドシートに読み書きする初期設定まとめ](https://tanuhack.com/operate-spreadsheet/#Google_Drive_API)
[pythonでGoogle Spread Sheetをいじる(OAuth)](https://qiita.com/AAkira/items/22719cbbd41b26dbd0d1)

#QRのデータなんてどうせどっかに二次元配列でおいてるだろ
ってことでpythonの[qrcode](https://pypi.org/project/qrcode/)モジュールのコードサンプルを見てみた。使い方は要約すると以下のような感じ。

```python
import qrcode

img = qrcode.make('hogehoge')
img.save('qrcode.png')
```
なかった。。。
まぁそんな使い方する人はいないので、考えてみれば当たり前である。

#qrcodeモジュールの中身を見てみる
いやいや内部的にはどっかにあるでしょ！
というわけでqrcodeモジュールの中身を見てみる。

```python
import inspect
import qrcode
print(inspect.getfile(qrcode))
```
たったこれだけのコードでモジュールの`__init__.py`の場所がわかるので、そこから探すとよいです。
該当のmain.pyの中身を見てみると、どうやら`QRCode().modules`に二次元配列でそれっぽいものが入っているので、これを使うことにした。

```
>>> import qrcode
>>> qr = qrcode.QRCode()
>>> qr.add_data('hoge')
>>> qr.make()
>>> qr.modules
[[True, True, True, True, True, True, True, False, False, True, False, False, False, False, True, True, True, True, True, True, True], [True, False, False, False, False, False, True, False, False, True, False, True, False, False, True, False, False, False, False, False, True], [True, False, True, True, True, False, True, False, True, True, False, True, False, False, True, False, True, True, True, False, True], [True, False, True, True, True, False, True, False, True, True, True, True, False, False, True, False, True, True, True, False, True], [True, False, True, True, True, False, True, False, True, True, False, True, True, False, True, False, True, True, True, False, True], [True, False, False, False, False, False, True, False, True, True, False, False, True, False, True, False, False, False, False, False, True], [True, True, True, True, True, True, True, False, True, False, True, False, True, False, True, True, True, True, True, True, True], [False, False, False, False, False, False, False, False, True, False, False, True, True, False, False, False, False, False, False, False, False], [True, False, True, True, True, True, True, False, False, True, False, False, True, False, True, True, True, True, True, False, False], [True, False, False, False, True, True, False, False, False, False, True, False, True, False, False, True, False, True, False, False, True], [True, True, True, False, False, True, True, False, False, True, False, True, False, True, False, False, True, True, False, True, False], [False, False, False, True, False, False, False, True, True, True, True, False, False, False, False, True, True, True, True, False, False], [False, True, True, True, True, True, True, True, True, False, False, True, False, True, False, False, True, False, False, False, True], [False, False, False, False, False, False, False, False, True, False, True, True, True, True, True, False, False, True, True, False, True], [True, True, True, True, True, True, True, False, False, False, False, False, True, False, True, True, False, True, False, True, False], [True, False, False, False, False, False, True, False, True, True, False, True, True, True, True, False, False, True, True, False, False], [True, False, True, True, True, False, True, False, True, True, True, False, True, False, False, True, False, False, False, True, False], [True, False, True, True, True, False, True, False, True, True, False, False, True, False, False, True, False, True, True, False, False], [True, False, True, True, True, False, True, False, True, False, True, True, False, True, False, False, True, True, True, False, False], [True, False, False, False, False, False, True, False, False, False, False, False, False, False, False, True, True, False, True, False, False], [True, True, True, True, True, True, True, False, True, True, False, True, False, True, False, False, True, False, True, True, False]]
```
あった！

#ソースコード
```python
import qrcode
import gspread
from oauth2client.service_account import ServiceAccountCredentials
from gspread_formatting import *
import time

scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name('XXXXXXXXXXXXXXXXXXXXXXX.json', scope)
gc = gspread.authorize(credentials)
SPREADSHEET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
workbook = gc.open_by_key(SPREADSHEET_KEY)

text = input("Please input =>")
qr = qrcode.QRCode()
qr.add_data(text)
qr.make()

body = {
    "requests": [
        {
            "updateDimensionProperties": {
                "range": {
                    "dimension": "COLUMNS",
                    "startIndex": 1,
                    "endIndex": len(qr.modules)+1
                },
                "properties": {
                    "pixelSize": 25
                },
                "fields": "pixelSize"
            }
        }
    ]
}
res = workbook.batch_update(body)

body = {
    "requests": [
        {
            "updateDimensionProperties": {
                "range": {
                    "dimension": "ROWS",
                    "startIndex": 1,
                    "endIndex": len(qr.modules)+1
                },
                "properties": {
                    "pixelSize": 25
           

     },
                "fields": "pixelSize"
            }
        }
    ]
}
res = workbook.batch_update(body)

fmt = cellFormat(
    backgroundColor=color(255, 255, 255)
    )

for ii, l in enumerate(qr.modules):
    for jj, b in enumerate(l):
        if b:
            format_cell_range(workbook.sheet1, f'{chr(65+ii+1)}{jj+2}', fmt)
        time.sleep(0.2)


img = qr.make_image()
img.save('qr_image.png')
```

今回`gspread_formatting`というモジュールを使ったが、少し癖が強かった。
セルの指定が"A1"の形式でしかできない(?)ようなので、`chr()`で無理やり変換している。よって"AA"以降の列には対応していないので、長い文字列だとQRコードの右側が切れる。
また、もう少しまとめて一度に書きこめればいいのだが、現状１セルずつ塗っているので処理がめちゃ遅い。
sleepを0.2入れてるのは、普通に回すと以下のGoogle Sheets APIの規約に引っかかるため。

>This version of the Google Sheets API has a limit of 500 requests per 100 seconds per project, and 100 requests per 100 seconds per user. 

こちらが実行中の動画。
10倍速でもこの速度である。
Hello world!はダサいのでHello hello, how low?(かっこいい)を合成している。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">QRコードをspread sheetで管理したいという声に応えて、spread sheetにQRコードをプロットしてみました。<br><br>明日使える無駄技術をあなたに。<br><br>こちらも近々Qiitaに投稿予定。<br>ちなみに動画は10倍速笑笑<a href="https://twitter.com/hashtag/Python?src=hash&amp;ref_src=twsrc%5Etfw">#Python</a><a href="https://twitter.com/hashtag/%E9%A7%86%E3%81%91%E5%87%BA%E3%81%97%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A8%E7%B9%8B%E3%81%8C%E3%82%8A%E3%81%9F%E3%81%84?src=hash&amp;ref_src=twsrc%5Etfw">#駆け出しエンジニアと繋がりたい</a><a href="https://twitter.com/hashtag/%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A8%E7%B9%8B%E3%81%8C%E3%82%8A%E3%81%9F%E3%81%84?src=hash&amp;ref_src=twsrc%5Etfw">#エンジニアと繋がりたい</a><a href="https://twitter.com/hashtag/%E7%84%A1%E9%A7%84%E3%81%AA%E6%8A%80%E8%A1%93?src=hash&amp;ref_src=twsrc%5Etfw">#無駄な技術</a> <a href="https://t.co/wDFjIT7eWo">pic.twitter.com/wDFjIT7eWo</a></p>&mdash; たくみ@スカジャンのエンジニア (@hatt_takumi) <a href="https://twitter.com/hatt_takumi/status/1236327960623235073?ref_src=twsrc%5Etfw">March 7, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>