PythonからGmail + OAuth 2でメールを送信する

PHP + Gmail + OAuth 2でメールを送信してみました。
PHPMailerからGmail + OAuth 2でメールを送信する

PythonからもGmail + OAuth 2でメール送信を試してみます。


How-to: Send HTML Mails with OAuth2 and Gmail in Python



こちらの記事が参考になりました。
How-to: Send HTML Mails with OAuth2 and Gmail in Python


まず、PHPで行った時と同様の手順でOAuth接続に必要な
・クライアント ID
・クライアント シークレット
・リフレッシュトークン
これらの値を取得します。

参考サイトのソースをリフレッシュトークンは取得済のものとして修正しました。


  1. """
  2. Adapted from:
  3. https://github.com/google/gmail-oauth2-tools/blob/master/python/oauth2.py
  4. https://developers.google.com/identity/protocols/OAuth2
  5. 1. Generate and authorize an OAuth2 (generate_oauth2_token)
  6. 2. Generate a new access tokens using a refresh token(refresh_token)
  7. 3. Generate an OAuth2 string to use for login (access_token)
  8. """
  9. import base64
  10. import imaplib
  11. import json
  12. import smtplib
  13. import urllib.parse
  14. import urllib.request
  15. from email.mime.multipart import MIMEMultipart
  16. from email.mime.text import MIMEText
  17. GOOGLE_ACCOUNTS_BASE_URL = 'https://accounts.google.com'
  18. GOOGLE_CLIENT_ID = '[クライアント ID]'
  19. GOOGLE_CLIENT_SECRET = '[クライアント シークレット]'
  20. GOOGLE_REFRESH_TOKEN = '[リフレッシュトークン]'
  21. def generate_oauth2_string(username, access_token):
  22.     auth_string = 'user=%s\1auth=Bearer %s\1\1' % (username, access_token)
  23.     auth_string = base64.b64encode(auth_string.encode('ascii')).decode('ascii')
  24.     return auth_string
  25. def refresh_authorization(google_client_id, google_client_secret, refresh_token):
  26.     params = {}
  27.     params['client_id'] = google_client_id
  28.     params['client_secret'] = google_client_secret
  29.     params['refresh_token'] = refresh_token
  30.     params['grant_type'] = 'refresh_token'
  31.     request_url = 'https://accounts.google.com/o/oauth2/token'
  32.     raw_response = urllib.request.urlopen(request_url, urllib.parse.urlencode(params).encode('UTF-8')).read().decode('UTF-8')
  33.     response = json.loads(raw_response)
  34.     return response['access_token'], response['expires_in']
  35. def send_mail(fromaddr, toaddr, subject, message):
  36.     access_token, expires_in = refresh_authorization(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN)
  37.     auth_string = generate_oauth2_string(fromaddr, access_token)
  38.     msg = MIMEText(message.encode('utf-8'), 'plain', _charset='utf-8')
  39.     msg['Subject'] = subject
  40.     msg['From'] = fromaddr
  41.     msg['To'] = toaddr
  42.     
  43.     server = smtplib.SMTP('smtp.gmail.com:587')
  44.     server.ehlo(GOOGLE_CLIENT_ID)
  45.     server.starttls()
  46.     server.docmd('AUTH', 'XOAUTH2 ' + auth_string)
  47.     server.sendmail(fromaddr, toaddr, msg.as_string())
  48.     server.quit()
  49. if __name__ == '__main__':
  50.     send_mail('sender@gmail.com', 'receive@gmail.com',
  51.              'メールタイトル', 'メール本文')




sender@gmail.comがログインユーザーも兼ねていますので、
トークンを作成したアカウントと一致させます。

これでPython + Gmail + OAuth2でメールの送信が行えました。

【参考URL】
How-to: Send HTML Mails with OAuth2 and Gmail in Python

hash_hmac PHPとPythonの対応(sha1, sha224, sha256, sha384, sha512, md5)

Web APIを称する際、sha1などでハッシュ化した署名(signature)を設定することがよくあります。
通信サンプルのドキュメントがPHPで作成されているものをPythonに読み替える時、
いつも調べている気がするのでまとめておきます。

hash_hmac



PHPのサンプルでよく使用さているのはhash_hmac関数を利用したものだと思います。
hash_hmac


  1. hash_hmac ( string $algo , string $data , string $key [, bool $raw_output = false ] ) : string




第一引数:ハッシュアルゴリズム名(string)
第二引数:ハッシュ化するデータ(string)
第三引数:共通の秘密鍵(string)



この関数で生成されたハッシュ値と同じものをPythonで生成する方法を見ていきます。



sha1



・PHP


  1. <?php
  2. $data = 'abcd123';
  3. $key = 'secret_key';
  4. $signature = hash_hmac('sha1', $data, $key);
  5. echo $signature . PHP_EOL;



・Python


  1. import hmac
  2. import hashlib
  3. data = 'abcd123'
  4. key = 'secret_key'
  5. signature = hmac.new(key.encode('utf-8'), data.encode('utf-8'), hashlib.sha1).hexdigest()
  6. print(signature)



Python3で実行する場合は、バイト配列に変換してhmac.newを呼び出します。

実行結果


$ php sample.php
467b5a252d65bc12187371f023e802bce44b891e

$ python3 sample.py
467b5a252d65bc12187371f023e802bce44b891e






sha224



・PHP


  1. <?php
  2. $data = 'abcd123';
  3. $key = 'secret_key';
  4. $signature = hash_hmac('sha224', $data, $key);
  5. echo $signature . PHP_EOL;



・Python


  1. import hmac
  2. import hashlib
  3. data = 'abcd123'
  4. key = 'secret_key'
  5. signature = hmac.new(key.encode('utf-8'), data.encode('utf-8'), hashlib.sha224).hexdigest()
  6. print(signature)



実行結果


$ php sample.php
8b51b16d0c096c610ad74c66402f06ae748a1b9c4e151b7e766bf3bb

$ python3 sample.py
8b51b16d0c096c610ad74c66402f06ae748a1b9c4e151b7e766bf3bb






sha256



・PHP


  1. <?php
  2. $data = 'abcd123';
  3. $key = 'secret_key';
  4. $signature = hash_hmac('sha256', $data, $key);
  5. echo $signature . PHP_EOL;



・Python


  1. import hmac
  2. import hashlib
  3. data = 'abcd123'
  4. key = 'secret_key'
  5. signature = hmac.new(key.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
  6. print(signature)



実行結果


$ php sample.php
bad3b6e88dd79f2da273047e063e612dcb289c6b0017a819754d303fd4c09da1

$ python3 sample.py
bad3b6e88dd79f2da273047e063e612dcb289c6b0017a819754d303fd4c09da1






sha384



・PHP


  1. <?php
  2. $data = 'abcd123';
  3. $key = 'secret_key';
  4. $signature = hash_hmac('sha384', $data, $key);
  5. echo $signature . PHP_EOL;



・Python


  1. import hmac
  2. import hashlib
  3. data = 'abcd123'
  4. key = 'secret_key'
  5. signature = hmac.new(key.encode('utf-8'), data.encode('utf-8'), hashlib.sha384).hexdigest()
  6. print(signature)



実行結果


$ php sample.php
b6f3d17ee85e0682e49a559d422cfeda30d8e43ddd40b4252e92f0352e5d80bb88355443f68352b9a85578011f88aa10

$ python3 sample.py
b6f3d17ee85e0682e49a559d422cfeda30d8e43ddd40b4252e92f0352e5d80bb88355443f68352b9a85578011f88aa10






sha512



・PHP


  1. <?php
  2. $data = 'abcd123';
  3. $key = 'secret_key';
  4. $signature = hash_hmac('sha512', $data, $key);
  5. echo $signature . PHP_EOL;



・Python


  1. import hmac
  2. import hashlib
  3. data = 'abcd123'
  4. key = 'secret_key'
  5. signature = hmac.new(key.encode('utf-8'), data.encode('utf-8'), hashlib.sha512).hexdigest()
  6. print(signature)



実行結果


$ php sample.php
5f74fe39ea8bca5df89d25e87fa2bd3d9bb78d8564af64c7657eacb80795c716aaddeab4d21a05859d90417940a8cf0adbf077ad80b0f1b11b001b1b05ded5f4

$ python3 sample.py
5f74fe39ea8bca5df89d25e87fa2bd3d9bb78d8564af64c7657eacb80795c716aaddeab4d21a05859d90417940a8cf0adbf077ad80b0f1b11b001b1b05ded5f4





md5



・PHP


  1. <?php
  2. $data = 'abcd123';
  3. $key = 'secret_key';
  4. $signature = hash_hmac('md5', $data, $key);
  5. echo $signature . PHP_EOL;



・Python


  1. import hmac
  2. import hashlib
  3. data = 'abcd123'
  4. key = 'secret_key'
  5. signature = hmac.new(key.encode('utf-8'), data.encode('utf-8'), hashlib.md5).hexdigest()
  6. print(signature)



実行結果


$ php sample.php
0fd51f5a346520191f83658868b9fc29

$ python3 sample.py
0fd51f5a346520191f83658868b9fc29





【参考URL】

hash_hmac
Pythonでhmacを計算する

Python3 http通信でGET、POST、ヘッダーの送信(urllib.request)

長らくPython2のhttplibにお世話になっていますが、
ぼちぼちPython3でhttp通信に触れていこうと思います。

urllib.request



便利なライブラリがたくさんあるのは知っているのですが、
標準機能のurllib.requestの使い方を調べてみます。
urllib.request --- URL を開くための拡張可能なライブラリ



GET



まずはget通信から。
通信先として、こんなphpスクリプトを用意しておきました。

・index.php


  1. <?php
  2. echo $_GET['name'];




サンプルはこのようになりました。


  1. import urllib.request
  2. url = 'http://192.168.1.103/index.php?name=symfoware'
  3. # getリクエストを実行
  4. response = urllib.request.urlopen(url)
  5. # read()で応答を取得
  6. # 取得できるのはbyte配列なので、decodeで文字列に変換
  7. body = response.read().decode('utf-8')
  8. print(body)




実行結果


$ python3 sample.py
symfoware



ポイントはread()で得られるのがバイト配列だということでしょうか。
decodeで文字列に変換してやります。
※python2のときは、strが戻り値だったと思います。



POST



続いてPOST送信です。
phpスクリプトを変更しておきます。

・index.php


  1. <?php
  2. echo $_POST['name'];




POST送信のサンプルです。


  1. import urllib.request
  2. import urllib.parse
  3. url = 'http://192.168.1.103/index.php'
  4. # 送信パラメーター
  5. data = {
  6.     'name':'symfoware'
  7. }
  8. # 送信パラメーターのurlエンコード実行
  9. # Python2のときはurllib.urlencode
  10. postdata = urllib.parse.urlencode(data)
  11. # 送信データはbyte配列に変換
  12. postbyte = postdata.encode('utf-8')
  13. # postリクエストを実行
  14. # Python2のときは urllib.urlopen
  15. response = urllib.request.urlopen(url, postbyte)
  16. # read()で応答を取得
  17. # 取得できるのはbyte配列なので、decodeで文字列に変換
  18. body = response.read().decode('utf-8')
  19. print(body)



実行結果


$ python3 sample.py
symfoware



urlopenの第二引数を指定すると自動的にPOSTで送信されます。
第二引数はbyte配列を渡す必要があるので、encodeで変換してやります。



JSONの送受信



リクエストボディにjsonデータを設定。
送信してやります。

・index.php


  1. <?php
  2. $json_string = file_get_contents('php://input');
  3. // 戻せるかテスト
  4. $json = json_decode($json_string, true);
  5. // そのまま返却
  6. echo json_encode($json);




Python3によるデータ送信サンプル


  1. import json
  2. import urllib.request
  3. import urllib.parse
  4. url = 'http://192.168.1.103/index.php'
  5. # 送信パラメーター
  6. data = {
  7.     'name':'symfoware'
  8. }
  9. # 送信パラメーターをjsonに変換
  10. jsondata = json.dumps(data)
  11. # byte配列に
  12. jsonbyte = jsondata.encode('utf-8')
  13. # postリクエストを実行
  14. # Python2のときは urllib.urlopen
  15. response = urllib.request.urlopen(url, jsonbyte)
  16. # read()で応答を取得
  17. # 取得できるのはbyte配列なので、decodeで文字列に変換
  18. body = response.read().decode('utf-8')
  19. result = json.loads(body)
  20. print(result['name'])




実行結果


$ python3 sample.py
symfoware






ヘッダー情報の付与



リクエストヘッダーを付与してみます。

・index.php



  1. $json_string = file_get_contents('php://input');
  2. // 戻せるかテスト
  3. $json = json_decode($json_string, true);
  4. // ヘッダーを取得して設定
  5. $key = $_SERVER['HTTP_X_API_KEY'];
  6. $json['X-API-Key'] = $key;
  7. // 返却
  8. echo json_encode($json);




リクエストヘッダーを設定するには、まずRequestオブジェクトを生成。
add_headerでヘッダー情報を設定します。


  1. import json
  2. import urllib.request
  3. import urllib.parse
  4. url = 'http://192.168.1.103/index.php'
  5. # 送信パラメーター
  6. data = {
  7.     'name':'symfoware'
  8. }
  9. # 送信パラメーターをjsonに変換
  10. jsondata = json.dumps(data)
  11. # byte配列に
  12. jsonbyte = jsondata.encode('utf-8')
  13. # リクエストオブジェクトを作成
  14. # Python2のときはurllib2.Request
  15. request = urllib.request.Request(url, jsonbyte)
  16. # ヘッダーを設定
  17. request.add_header('Content-Type', 'application/json')
  18. request.add_header('X-API-Key', 'sample_key')
  19. # リクエストを実行
  20. # Python2のときは urllib.urlopen
  21. response = urllib.request.urlopen(request)
  22. # read()で応答を取得
  23. # 取得できるのはbyte配列なので、decodeで文字列に変換
  24. body = response.read().decode('utf-8')
  25. result = json.loads(body)
  26. print(result['name'])
  27. print(result['X-API-Key'])




実行結果


$ python3 sample.py
symfoware
sample_key




これで一通りの操作は行えるようになったと思います。

Python 配列を指定個数の配列に分割する

こちらで配列を指定したサイズで分割しました。
Python 配列を指定したサイズで分割する(phpのarray_chunk)

指定数ごとの配列ではなく、指定数の配列に変換した。


  1. [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]



に対して、「2」を指定すると


  1. [1, 2]
  2. [3, 4]
  3. [5, 6]
  4. [7, 8]
  5. [9, 10]



とするのではなく、


  1. [1, 2, 3, 4, 5]
  2. [6, 7, 8, 9, 10]



となってほしい。


サンプル



こんな感じになりました。


  1. def chunks(l, n):
  2.     if len(l) < n:
  3.         return [[x] for x in l]
  4.     result = [[] for i in range(n)]
  5.     for i, item in enumerate(l):
  6.         result[i % n].append(item)
  7.     return result
  8. # test
  9. a = [1,2,3,4,5,6,7,8,9,10,11]
  10. for i in chunks(a, 5):
  11.     print(i)




狙い通りの動作です。


$ python sample.py
[1, 6, 11]
[2, 7]
[3, 8]
[4, 9]
[5, 10]



Python スレッドによる並行処理と終了の待ち合わせ、スレッド初期値の設定

Pythonで時間のかかる処理を並行して呼び出したいケースに遭遇したので、
スレッドについて調べてみます。

Python2



Python 3からはスレッドに関する便利なパッケージが追加されています。
concurrent.futures -- 並列タスク実行
asyncio --- 非同期 I/O

レンタルサーバーでPython2しか使用できないケースも(未だに)あるので、
Python2でも利用できることを前提に調べてみます。



最も簡単な例



threading.Threadのコンストラクタに関数を指定するだけでスレッド実行が行なえます。


  1. import threading
  2. import time
  3. def funA():
  4.     for i in range(5):
  5.         print('funA:%d' % i)
  6.         time.sleep(1)
  7. def funB():
  8.     for i in range(5):
  9.         print('funB:%d' % i)
  10.         time.sleep(1)
  11. if __name__ == '__main__':
  12.     threadA = threading.Thread(target=funA)
  13.     threadB = threading.Thread(target=funB)
  14.     threadA.start()
  15.     threadB.start()




実行結果


$ python sample.py
funA:0
funB:0
funA:1
funB:1
funA:2
funB:2
funA:3
funB:3
funA:4
funB:4



funA、funBが並列に実行されています。



threading.Thread



threading.Threadを継承してスレッドオブジェクトを定義することもできます。
start実行時、runメソッドが呼び出されます。


  1. import threading
  2. import time
  3. class Worker(threading.Thread):
  4.     # Thread.start時に呼び出される
  5.     def run(self):
  6.         for i in range(5):
  7.             print('Worker:%d' % i)
  8.             time.sleep(1)
  9. if __name__ == '__main__':
  10.     threadA = Worker()
  11.     threadA.start()
  12.     
  13.     threadB = Worker()
  14.     threadB.start()



実行結果


$ python sample.py
Worker:0
Worker:0
Worker:1
Worker:1
Worker:2
Worker:2
Worker:3
Worker:3
Worker:4
Worker:4





スレッドオブジェクトへの値の設定



せっかくオブジェクトにしたので、それぞれ別の個性を与えたいと思います。
オブジェクトに値を設定するにはコンストラクタを経由する方法があります。


  1. import threading
  2. import time
  3. class Worker(threading.Thread):
  4.     def __init__(self, worker_name):
  5.         threading.Thread.__init__(self)
  6.         self._worker_name = worker_name
  7.     # Thread.start時に呼び出される
  8.     def run(self):
  9.         for i in range(5):
  10.             print('Worker%s:%d' % (self._worker_name, i))
  11.             time.sleep(1)
  12. if __name__ == '__main__':
  13.     threadA = Worker('A')
  14.     threadA.start()
  15.     
  16.     threadB = Worker('B')
  17.     threadB.start()



実行結果


$ python sample.py
WorkerA:0
WorkerB:0
WorkerA:1
WorkerB:1
WorkerA:2
WorkerB:2
WorkerA:3
WorkerB:3
WorkerA:4
WorkerB:4




コンストラクタでなくても、別途値を設定するためのsetterを用意してやる手もあります。


  1. import threading
  2. import time
  3. class Worker(threading.Thread):
  4.     def set_name(self, worker_name):
  5.         self._worker_name = worker_name
  6.     # Thread.start時に呼び出される
  7.     def run(self):
  8.         for i in range(5):
  9.             print('Worker%s:%d' % (self._worker_name, i))
  10.             time.sleep(1)
  11. if __name__ == '__main__':
  12.     threadA = Worker()
  13.     threadA.set_name('A')
  14.     threadA.start()
  15.     
  16.     threadB = Worker()
  17.     threadB.set_name('B')
  18.     threadB.start()



実行結果


$ python sample.py
WorkerA:0
WorkerB:0
WorkerA:1
WorkerB:1
WorkerA:2
WorkerB:2
WorkerA:3
WorkerB:3
WorkerA:4
WorkerB:4





スレッド終了の待ち合わせ(join)



複数のスレッドで処理を行い、それぞれのスレッドで得られた結果を合算したい場合。


  1. import threading
  2. import time
  3. class Worker(threading.Thread):
  4.     def __init__(self, worker_name, max):
  5.         threading.Thread.__init__(self)
  6.         self._worker_name = worker_name
  7.         self._max = max
  8.         self._total = 0
  9.     # Thread.start時に呼び出される
  10.     def run(self):
  11.         for i in range(self._max):
  12.             print('Worker%s:%d' % (self._worker_name, i))
  13.             self._total += i
  14.             time.sleep(1)
  15.     def get_total(self):
  16.         return self._total
  17. if __name__ == '__main__':
  18.     threadA = Worker('A', 10)
  19.     threadA.start()
  20.     
  21.     threadB = Worker('B', 20)
  22.     threadB.start()
  23.     # すぐに実行されてしまう
  24.     gtotal = threadA.get_total() + threadB.get_total()
  25.     print('gtotal:%d' % gtotal)



実行結果


$ python sample.py
WorkerA:0
gtotal:0
WorkerB:0



スレッドで実行しているため、すぐに合計値の計算まで処理が進んでしまします。
runメソッドの終了を待つには、joinを呼び出します。


  1. import threading
  2. import time
  3. class Worker(threading.Thread):
  4.     def __init__(self, worker_name, max):
  5.         threading.Thread.__init__(self)
  6.         self._worker_name = worker_name
  7.         self._max = max
  8.         self._total = 0
  9.     # Thread.start時に呼び出される
  10.     def run(self):
  11.         for i in range(self._max):
  12.             print('Worker%s:%d' % (self._worker_name, i))
  13.             self._total += i
  14.             time.sleep(1)
  15.     def get_total(self):
  16.         return self._total
  17. if __name__ == '__main__':
  18.     threadA = Worker('A', 10)
  19.     threadA.start()
  20.     
  21.     threadB = Worker('B', 20)
  22.     threadB.start()
  23.     # スレッドの終了を待つ
  24.     threadA.join()
  25.     threadB.join()
  26.     # 計算結果を合計
  27.     gtotal = threadA.get_total() + threadB.get_total()
  28.     print('gtotal:%d' % gtotal)



実行結果


$ python sample.py
WorkerA:0
WorkerB:0
WorkerA:1
WorkerB:1
WorkerA:2
...
WorkerB:18
WorkerB:19
gtotal:235

プロフィール

Author:symfo
blog形式だと探しにくいので、まとめサイト作成中です。
Symfoware まとめ

PR




検索フォーム

月別アーカイブ