【LINE Blockchain API】『NameError: name ‘get_signature’ is not defined』と出た時の対処法

【LINE Blockchain API】『NameError: name ‘get_signature’ is not defined』と出た時の対処法

今行っているブロックチェーン業務では、LINE Blockchain APIを使用することになり、まずはAPIを勉強しようと思いPythonを用いてAPI操作をしようとしていました。

しかし、こちらのようにドキュメントに乗っているサンプルコードをそのまま試そうとすると、「NameError: name ‘get_signature’ is not defined.」というエラーが発生します。


署名関連だと当たりをつけ、こちらを参考にして色々と試行錯誤したのですが、コピペしても動かず、解消するのにかなりハマってしまいました。

今回は、NameError: name ‘get_signature’ is not defined.が発生した時の対処法(get_signatureメソッドの作成方法)についてお教えします。

ファイル構成


ファイル構成はこのようにします。

.
├── line_blockchain_api.py  # サンプルコード
├── signature_generator.py  # get_signatureメソッドがあるファイル
└── request_flattener.py    # 署名作成で必要な文字列を作成するクラスファイル


line_blockchain_api.pyには、API通信を行うコードを記載します。今回はこちらのサンプルコードを使用します。
自身が作成した、Non-fungibleアイテムトークン(NFT)の一覧を取得してみます。

signature_generator.pyには、get_signatureメソッドを実装します。
get_signatureメソッドでは、HTTPヘッダーに載せるために必要な署名を作成します。
この書名の生成をする際に、HTTPリクエストのボディに含まれているkeyとvalueを、クエリ文字列のようにつなげる必要があります。


request_flattener.pyには、その必要なクエリ文字列を作成するクラスファイルです。こちらにその文字列を作成するクラスを作成します。

作成するファイルについて
signature_generator.pyや、request_flattener.pyは こちらのLINE Blockchain Docsの認証ページにて、署名作成するためのサンプルコードが記載されています。

そちらを今回若干修正して紹介しています。

元々のコードを知りたい!という方や、詳細を知りたい方はドキュメントをご覧ください。
https://docs-blockchain.line.biz/ja/api-guide/Authentication



それでは、それぞれのコードの中身を紹介します。

【signature_generator.py】get_signatureメソッドを記載したファイルを作成する


まずはget_signatureメソッドを実装したsignature_generator.pyファイルを作成します。
ファイルを新規作成し、下のコードをそのままコピぺしてください

import hmac
import hashlib
import base64

from request_flattener import RequestBodyFlattener

def createSignTarget(method, path, timestamp, nonce, parameters: dict = {}):
    signTarget = f'{nonce}{str(timestamp)}{method}{path}'
    if(len(parameters) > 0):
        signTarget = signTarget + "?"
    return signTarget


def get_signature(method: str, path: str, nonce: str, timestamp: int, secret: str, query_params: dict = {}, body: dict = {}):

    body_flattener = RequestBodyFlattener()
    all_parameters = {}
    all_parameters.update(query_params)
    all_parameters.update(body)

    signTarget = createSignTarget(method.upper(), path, timestamp, nonce, all_parameters)

    if (len(query_params) > 0):
        signTarget += '&'.join('%s=%s' % (key, value) for (key, value) in query_params.items())

    if (len(body) > 0):
        if (len(query_params) > 0):
            signTarget += "&" + body_flattener.flatten(body)
        else:
            signTarget += body_flattener.flatten(body)

    raw_hmac = hmac.new(bytes(secret, 'utf-8'), bytes(signTarget, 'utf-8'), hashlib.sha512)
    result = base64.b64encode(raw_hmac.digest()).decode('utf-8')

    return result


こちらは、署名を作成するsignature_generator.pyのサンプルコードを、クラスではなく関数に修正したものです。
関数に修正することで、API通信を行うサンプルコード自体にあまり変更を加えないようにしています。

それでは次にrequest_flattener.pyを作成します。

【request_flattener.py】署名生成に必要な文字列を生成するファイルを作成する

次に作成するrequest_flattener.pyは、下記のコードはURLにあるrequest_flattener.pyと同じコードになっています。
LINE Blockchain API Docs 認証 – Pythonのサンプル

このファイルでは署名を作成するのに必要なクエリ文字列を生成します。
こちらも同様に、ファイルを新規作成し、下のコードをそのままコピペしてください

class RequestBodyFlattener:

    def __flatten_key_value(self, key, value):
        if (isinstance(value, str)):
            return f"{key}={value}"

        if (isinstance(value, list)):
            l_key_value = {}
            for index, ele in enumerate(value):
                for lkey in list(ele.keys() | l_key_value.keys()):
                    if lkey in ele.keys():
                        lvalue = ele[lkey]
                    else:
                        lvalue = ""

                    if (lkey in l_key_value.keys()):
                        l_key_value[lkey] = f"{l_key_value[lkey]},{lvalue}"
                    else:
                        l_key_value[lkey] = f"{',' * index}{lvalue}"
            return "&".join("%s=%s" % (f"{key}.{lkey}", lvalue) for (lkey, lvalue) in sorted(l_key_value.items()))

    def flatten(self, body: dict = {}):
        sorted_body = sorted(body.items())
        return "&".join(self.__flatten_key_value(key, value) for (key, value) in sorted_body)

こちらで準備完了です。

最後に、API通信するサンプルコードを作ります。

【line_blockchain_api.py】サンプルコード + get_signatureをimportする


line_blockchain_api.pyには、以下の手順でコードを記載していきます。

  1. こちらのサンプルコードをコピペ(自身のAPI通信したいサンプルコードを使用)
  2. import部分に、「from signature_generator import get_signature」という行を追加
  3. コントラクトIDが必要なので、自身のものに適宜変更する
  4. 出力確認のため、下部にprint文を追加

全体のコードはこのようになります。

import os

import requests
import random
import string
import time

############ 追加する ############
from signature_generator import get_signature

def GET_v1_item_tokens_contractId_non_fungibles():
    server_url = os.environ['SERVER_URL']
    service_api_key = os.environ['SERVICE_API_KEY']
    service_api_secret = os.environ['SERVICE_API_SECRET']

    nonce = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(8))
    timestamp = int(round(time.time() * 1000))

    # ここの部分を自身のコントラクトIDに変更する
    path = '/v1/item-tokens/{:contract_id:}/non-fungibles'

    query_params = {
        'limit': 10,
        'orderBy': 'desc',
        'page': 1
    }

    headers = {
        'service-api-key': service_api_key,
        'nonce': nonce,
        'timestamp': str(timestamp)
    }


    signature = get_signature('GET', path, nonce, timestamp, service_api_secret, query_params)
    headers['signature'] = signature

    res = requests.get(server_url + path, params=query_params, headers=headers)
    return res.json()

############ 追加する ############
print(GET_v1_item_tokens_contractId_non_fungibles())

実際に動かしてみると、このようにレスポンスが返ってきます。

{'responseData': 
    [
        {
            'createdAt': 1649918615569,
            'meta': '{"meta":"meta情報など"}',
            'name': 'TokenName2',
            'tokenType': '10000002',
            'totalBurn': '0',
            'totalMint': '17',
            'totalSupply': '17'
        },
        {
            'createdAt': 1649918444116,
            'meta': '{"meta":"meta情報など"}',
            'name': 'TokenName',
            'tokenType': '10000001',
            'totalBurn': '0',
            'totalMint': '86',
            'totalSupply': '86'
        }
    ]
}

きちんと取得することができました!

KeyError: ‘SERVER_URL’というエラーが出た場合


上記のようなエラーが出た場合は、line_blockchain_api.pyの関数内にある下記のコードが原因です。

server_url = os.environ['SERVER_URL']
service_api_key = os.environ['SERVICE_API_KEY']
service_api_secret = os.environ['SERVICE_API_SECRET']


この場合には、下記のように直接値を記載するか、こちらをご参考にしてos.environで環境変数を追加してください。

server_url = "https://test-api.blockchain.line.me" # テストのURL
service_api_key = "e1df****-****-****-****-******87c708"
service_api_secret = "384e****-****-****-****-******cf3763"

まとめ

いかがだったでしょうか。

LINE Blockchain APIにあるコードをコピペで実行できたらよかったのですが、変なところで詰まってしまいました。

上記の方法を使えば、ファイルを2つ追加してサンプルコードに1行追加することでサンプルコードそのまま使用することができます。


APIに不慣れだったこともあり、署名の意味がよくわからずかなり詰まってしまったのでこの記事で誰かの役に立てれば幸いです。


参考資料:
・LINE Blockchain API ガイド https://docs-blockchain.line.biz/ja/api-guide/
・LINE Blockchain API 認証 https://docs-blockchain.line.biz/ja/api-guide/Authentication
・LINE Blockchain API Non-fungibleアイテムトークンの一覧を取得する https://docs-blockchain.line.biz/ja/api-guide/category-item-tokens/retrieve#v1-item-tokens-contractId-non-fungibles-get

Blockchainカテゴリの最新記事