ElasticsearchのDynamicTemplateのメモ
Elasticsearchを使う機会があって、 DynamicTemplateを使用したので、そのときのメモです。 メモなので雑です。
DynamicTemplateは、elasticseachにデータをアップロードする際、 elasticseachが自動でフィールドの型設定をしてくれるのですが、 特定の文字列にマッチする場合に、適用する設定のことです。
つまり 「このindexに、この名前でデータがアップロードされたら、こうしてね」 ということですね。
「path_to_apply*の中にある、「text*」に該当するものは日本語解析可能にする」 という設定のjson
{ "template": "path_to_apply*", "settings": { "index": { "analysis": { "tokenizer": { "kuromoji_user_dict": { "type": "kuromoji_tokenizer", "mode": "serach" } } } } }, "mappings": { "_default_": { "dynamic_templates": [ { "named_analyzers": { "match_mapping_type": "string", "match": "text_*", "mapping": { "fielddata": true, "analyzer": "kuromoji", "fields": { "keyword": { "type": "keyword" } } } } } ] } } }
以下のコマンドで適用します。
$ curl -X PUT 'endpoint:port/_template/path_to_apply' -d @mapping_template.json
Qiita記事はこちら
Lineでやり取りした画像を自動でGoogleDriveにアップしたいからBotを作ることにしました。Part.2
こちらの続きです。
まずはやりたいことの復習から。
困っていたこと
LINEを使っていて、よくあるのが、 イベントのときに、なんかのグループ作って、 情報をやり取りし、イベント後に写真をLINEに投稿し合うってやつ、ありますよね。 正直、写真の保存がマジでしんどいのです。
保存が面倒なら、自動でグーグルドライブに保存してくれるBot作ったらいいじゃない!
やりたいこと
LINEでやり取りしている写真を、グーグルドライブに放り込みたい。 自動で。
今回書くこと
- Local Debugの方法
- pyDriveもサーバーレスでやる
ここでDebugの方法を考えて見ましょう。
コードを追加するたびに毎回、AWS上にデプロイしてもいいのですが、
毎回、chalice deploy
は効率的ではないので、ローカルで実行することにします。
Chaliceのローカル実行
chaliceはローカル実行のコマンドがすでに用意されており、 コマンド一発でローカルで実行できます。
$ $ chalice local Serving on localhost:8000
これでPCから、ポート8000で接続できます。 ですが、Lineからのリクエストを実際に受け付けたいときもありますよね。
そこでngrokを使います。
ngrokでLINEからのメッセージをローカルで実行する
ngrokはローカルサーバーをトンネル経由で、公開してくれるサービスです。
つまり、chalice local
でローカル実行しているLINE botアプリケーションに、
ngrok経由で、LINEからのリクエストをハンドリングすることができるわけです。
ngrokの細かい仕組みは、いったんおいておいて、インストールしてみましょう。
(今度書いてみようと思います。。。思います。)
$ brew cask install ngrok
そして実行、
$ ngrok http 8000
そうすると、以下のように表示されます。
grok by @inconshreveable (Ctrl+C to quit) Session Status online Account xxxxxxxx (Plan: Free) Version 2.2.8 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://0cd043fd.ngrok.io -> localhost:8000 Forwarding https://0cd043fd.ngrok.io -> localhost:8000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00
つまり、
http://0cd043fd.ngrok.io
へのアクセスを
localhost:8000
へルーティングしてくれるよ、ということです。
ローカルでLINEからのリクエストを処理してみる
さっそく、LINEからのリクエストをローカルに投げるようにしてみましょう。 サーバーサイドは、前回のプログラムを利用します。
- ngrokを起動する。
$ ngrok http 8000 Session Status online Account xxxxxxx (Plan: Free) Version 2.2.8 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://c85d6c7e.ngrok.io -> localhost:8000 Forwarding https://c85d6c7e.ngrok.io -> localhost:8000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00
こちらがLINEからのリクエストをルーティングする先になります。
2. Chaliceをローカル起動
$ $ chalice local Serving on localhost:8000
これで、ローカルのChaliceへリクエストを飛ばす準備ができました。
3. Lineからのリクエスト先を設定する。 LINEのコンソールから、上記ドメインで設定します。 接続確認のボタンを押下すると、「成功しました」と表示されるかと思います。
ngrokにもリクエストが来ています。
Session Status online Account xxxxxxxx (Plan: Free) Version 2.2.8 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://c85d6c7e.ngrok.io -> localhost:8000 Forwarding https://c85d6c7e.ngrok.io -> localhost:8000 Connections ttl opn rt1 rt5 p50 p90 0 1 0.00 0.00 0.00 0.00 HTTP Requests ------------- POST /bot 200 OK
chaliceにも無事に届いていますね。
127.0.0.1 - - [30/May/2018 22:35:16] "POST /bot HTTP/1.1" 200 -
それでは、LINEからリクエストを飛ばしてみましょう。
オッケェェェイイーーーーーー!!
pyDriveもサーバーレスでやる
前回はオウム返しするline-botをサーバーレスで動かしました。 今度は、当初の目的に近づくために、pyDriveもサーバーレスで動かします。
pyDriveをvendorディレクトリへ
chaliceでパッケージングするために vendorディレクトリにパッケージをDLします。
$ pip install -U PyDrive==1.3.1 -t ./vendor/
client_secrets.jsonはchalicelibへ
ドキュメントの通り、設定ファイルなどは、
chalicelibディレクトリに配置します。
__init__.py
も忘れずに。
パッケージングされると、chalicelibディレクトリは、app.pyと並列に置かれます。
その結果、pyDriveで必要な client_secrets.json
が同一ディレクトリではなくなってしまうので、
このままでは、NotFoundでエラーが発生してしまいます。
なので、インスタンス化したら、LoadClientConfigFileメソッドでファイルの在処を教えてあげます。
gauth = GoogleAuth()
gauth.LoadClientConfigFile('./chalicelib/client_secrets.json')
結果、ファイルは以下のような配置になると思います。
├── app.py ├── chalicelib │ ├── __init__.py │ └── client_secrets.json └── vendor ├── PyDrive-1.3.1.dist-info : :
パッケージングされると、chalicelibディレクトリは、app.pyと並列に置かれます。
その結果、pyDriveで必要な client_secrets.json
が同一ディレクトリではなくなってしまうので、
このままでは、NotFoundでエラーが発生してしまいます。
デプロイして動かす。
今度は、前回のコードに、pyDriveを追加した、以下のコードを使用しました。 また今回、callbackのpathを追加しています。 google側でOAuthのcallback先を、デプロイしたAPIへと設定しておけば、 認可コードがここに送られてきます。 (前回のURLを設定すればいいかと思います。)
import sys import os import chalicelib from chalice import Chalice, Response from flask import Flask, request, abort, render_template from linebot import ( LineBotApi, WebhookParser ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, TextSendMessage, ImageMessage ) from linebot.models.events import ( SourceGroup, SourceRoom, SourceUser ) from pydrive.auth import GoogleAuth from pydrive.drive import GoogleDrive """ logging """ import logging from logging import getLogger logger = getLogger(__name__) logger.setLevel(logging.DEBUG) """ Line TOKEN """ CHANNEL_ACCESS_TOKEN = os.getenv('CHANNEL_ACCESS_TOKEN', None) CHANNEL_SECRET = os.getenv('CHANNEL_SECRET', None) print(CHANNEL_ACCESS_TOKEN) print(CHANNEL_SECRET) if CHANNEL_SECRET is None: print('Specify LINE_CHANNEL_SECRET as environment variable.') sys.exit(1) if CHANNEL_ACCESS_TOKEN is None: print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.') sys.exit(1) line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN) parser = WebhookParser(CHANNEL_SECRET) # app = Flask(__name__) app = Chalice(app_name='python-line-google') app.debug = True @app.route("/", methods=['GET']) def test(): return 'ok' @app.route("/callback") def callback(): print("header:", app.current_request.headers) print("code",app.current_request.query_params.get('code')) return Response(body="Setup Complete. You can close this page", status_code=200, headers={'Content-Type': 'text/plain'}) def handle_message(event: MessageEvent): message = event.message.text if message == "設定" or message == "setup": # from user message is "設定" if isinstance(event.source, SourceUser): code_key = event.source.user_id if isinstance(event.source, SourceRoom) : code_key = event.source.room_id if isinstance(event.source,SourceGroup): code_key = event.source.group_id gauth = GoogleAuth() gauth.LoadClientConfigFile('./chalicelib/client_secrets.json') gauth.GetFlow() auth_url = gauth.flow.step1_get_authorize_url(state=code_key) line_bot_api.reply_message( event.reply_token, TextSendMessage(text=auth_url) ) else: # from user message is not "設定" line_bot_api.reply_message( event.reply_token, TextSendMessage(text=event.message.text) ) @app.route("/bot", methods=['POST']) def bot(): signature = app.current_request.headers['X-Line-Signature'] body = app.current_request.raw_body.decode('utf-8') print('signature:{}'.format(signature)) print('request body:{}'.format(body)) try: events = parser.parse(body, signature) except InvalidSignatureError: abort(400) # if event is MessageEvent and message is TextMessage, then echo text for event in events: # return if callback test request if event.reply_token == "00000000000000000000000000000000": print('request is callback test') return "ok" # skip if not MessageEvent if not isinstance(event, MessageEvent): print("not Message Event") continue # skip if not TextMessage if not isinstance(event.message, TextMessage) and not isinstance(event.message, ImageMessage): print("not TextMessage and ImageMessage") continue message_type = event.message print("message_type = {}".format(message_type)) if isinstance(message_type, TextMessage): handle_message(event) if isinstance(message_type, ImageMessage): print("image message") # get message text return 'OK'
認証用のURLが返ってきました。
AWS上のログでも、認可コードが確認できました。
この認可コードを使えば、GoogleDriveへアップロードできるはず!! この認可コードはどこかに保持しておかないと、毎回認証しないといけなくなっちゃいますね。
保存もAWSのサービスに乗っかります。が、また次に続くことにします。
Qiita はこちら
Lineでやり取りした画像を自動でGoogleDriveにアップしたいからBotを作ることにしました。Part.1
困っていたこと
LINEを使っていて、よくあるのが、 イベントのときに、なんかのグループ作って、 情報をやり取りし、イベント後に写真をLINEに投稿し合うってやつ、ありますよね。 正直、写真の保存がマジでしんどいのです。
保存が面倒なら、自動でグーグルドライブに保存してくれるBot作ったらいいじゃない!
やりたいこと
LINEでやり取りしている写真を、グーグルドライブに放り込みたい。 自動で。
アーキテクチャ
まずはアーキテクチャをざっくり考えます。 絵で書くとこんな感じです。
実際に使うときの流れは以下の通り
- 写真をユーザーがトークに送信
- botAPIに通知
- botAPIがLINEから画像をDL
- Driveにアップロード
システム面の仕様はこんな感じです
botはサーバーレスでやることにします。 慣れているので、aws のサーバーレスフレームワーク、chaliceを使いましよう。 chaliceの記事もあるので、こちらも参考になれば幸いです。 なので、言語はPythonになりました。
グーグルドライブの認証はoAauthを使います。 使ったことないけど、せっかくなので勉強します。
LINEの各チャットルームと、認証したドライブは紐付いている必要があります。 それを紐づけるものは、Dynamoでやることにしましょう。
開発環境
PC:MacbookPro
OS:MacOS High Sierra
言語:Python3.6
使うフレームワーク、ツール:Chalice, LineSDK, localstack, ngrok
OAuthとはなんぞや
よく聞く認可の仕組みですが、今までよくわかってませんでした。 OAuthは、アプリケーションに許可された特定のリソース(データ)にアクセスを許可するものです。
OAuthの認可フロー
OAuthの認可フローでは、以下の4つの登場人物がいます。
- リソースオーナー
- クライアント
- オーソライゼーションサーバー
- リソースサーバー
厳密な絵ではないのですが、このような流れかと思っています。 LINEさんは透過的に存在しています。LINEの上とか下を矢印通っているのは特には理由はないです。
今回、Goolgeがオーソライゼーションサーバー、リソースサーバーを提供しているので、一緒に サービス・プロバイダとして記載しました。
Googleでログインとか、Facebookでログインとかで使われていますね。 あれです。ちょっと怖いけど、非常に便利な仕組みですね。これからは安心して、「〇〇でログイン」できます。
oAuthの理解には こちらの説明が参考になりました。 こちらの絵も参考になりました。 ありがとうございます。
OAuthで認証してGoogle Driveを使うために
まずは、この赤枠をやるための準備をします。 クライアントになるには、クライアントID が必要になります。
Googleのアクセストークンの理解には、こちらを参考にしました。
ClientIDの作成
https://console.developers.google.com/apis/credentialsこちらから作成します。
認証情報を作成をクリックします。
OAuthクライアントIDを選択します。
ここで、本来はウェブアプリケーションを選択するのですが、理解のためにまずは「その他」を選択します。作成をクリック。
クライアントIDができました。
このままでは使わないので、一番右のDLアイコンをクリックしてCredentialファイルをDLしましょう。 ←これ
DLすると、こんな感じのデータが入っています。アプリケーションはこの情報をもって、クライアントとなることができます。 コールバックページを示すのは、redirect_urisです。今回のクレデンシャルファイルはlocalhostを指しています。
7. 同意画面の設定 クライアントIDを設定すると、「同意画面」の設定をすることができます。 認可を求めるアプリケーションの提供者は誰か、を提供するものです。 あとで出てくるので、ここで設定しておきます。
これでクライアントの準備ができました。
OAuthでGoogleDriveにアクセスしてみる
ライブラリ
GoogleDriveを利用するにはAPIを使用するのですが、 GoogleDriveのAPIラッパーであるPyDriveを使います。 OAuthの認証も行ってくれる素晴らしいライブラリです。
インストールはpipでできます。
$ pip install PyDrive
サンプルコード
サンプルコードを実行してみましょう。
先程DLしたクレデンシャルファイルを、 client_secrets.json
に変更し、
サンプルプログラムと同じディレクトリに置きましょう。
sample_dir ├── pydrive_sample.py └── client_secrets.json
サンプルコードはこちらです。
#!/usr/bin/env python from pydrive.auth import GoogleAuth from pydrive.drive import GoogleDrive if __name__ == '__main__': gauth = GoogleAuth() gauth.LocalWebserverAuth() drive = GoogleDrive(gauth) file1 = drive.CreateFile({'title': 'Hello.txt'}) file1.SetContentString('Hello') file1.Upload()
実行すると、こんな画面が開くと思います。 さきほど同意画面で、記述したものが表示されています。
私はブラウザにすでにログインしていたので、ログインしているアカウントが表示されています。
ちなみに「Qiitaのサンプル」リンクをクリックすると、このように表示されます。
認証すると、以下のような文字が。 「The authentication flow has completed.」 Authフローが完了と出てますね。
GoogleDriveにもファイルがアップロードされています。
これで、OAuthによる認可とアップロードは完了してしまいました。 認可コードやアクセストークンが出てこなかったので、もう少し詳しく見てみることにします。
認可コードとコールバックページ
実は、先程表示された「The authentication flow has completed.」が 「認可コードを含むコールバックページ」に該当します。
「The authentication flow has completed.」が表示されるページの、 クエリストリングとして渡されるcodeが認可コードになります。
http://localhost:8080/?code=4/AAAjqBdam8TJPC33jLr0eo_PQ119ZQeoyhi8t7aPZNhQHYxdtLA7Wzp3sTV7SQNevQbd1TU0j3mF7Fe7IEteroA#
PyDriveに認可コードを渡すと、 クライアントIDと認可コードをもってアクセストークンを取得し、 APIをコールしてファイルアップロードをしてくれているのでした。
ところで今回使っているのは、 Googleで作成したのは「その他」のクライアントIDです。 コールバックページはlocalhostを指していました。
今回のサンプルコードのようにローカルで動かすときは良いのですが、 Linebotで動かしたときは、localhostではないところにリダイレクトして、 codeを取得しないといけません。
コールバックページをlocalhostではない所に設定する。
再度、https://console.developers.google.com/apis/credentialsで作成します。
今度は、「ウェブアプリケーション」を選択します。
3. ここで、承認済みのリダイレクトURIを設定する事ができます。 GoogleのOAuthでは設定したURL以外にはりダイレクトできません。 あとで変更できるので、localhostではないところにする、という目的と矛盾するのですが、 とりあえず「 http://qiita.sample.com/ 」にしておきましょう。
4. クレデンシャルファイルをDLして、再度実行してみましょう。 今度は以下のようなコードを実行します。
#!/usr/bin/env python from pydrive.auth import GoogleAuth from pydrive.drive import GoogleDrive if __name__ == '__main__': gauth = GoogleAuth() print(gauth.GetAuthUrl())
表示された、長いURL
https://accounts.google.com/o/oauth2/auth?client_id=koko-ha-clientid-nanode-chotto-kakushimasu.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Fqiita.sample.com%2F&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&response_type=code
こちらをクリックしてみます。
先程、設定したドメイン「sample.com」が表示されます。 認証すると、「sample.com」ドメインは存在していないため、認証してリダイレクトしてもエラーになります。
しかしURLが以下のようになっています。
http://qiita.sample.com/?code=4/AAD6PZwJESjjqqVbobH8XiLkqOIBTzf7MCix9_QYAdylEC23zORKxrAKl0z5C-t2Nm5mbYji1QNgxhDKCxZWeYo#
これで認証したら、 任意のURLにリダイレクトし、code取得できるのだということがわかります。
chaliceプロジェクトを開始する。
OAuthで認証して、GoogleDriveにアップロードすることができそうだということがわかりました。 さっそくchaliceを使って、サーバーレスアプリケーションを作って行きましょう。
こちらに入門用に書いたものがあるので、 ここでは、細かく説明しません。
$ chalice new-project python-line-gdrive $ chalice deploy reating role: python-line-gdrive-dev Creating deployment package. Creating lambda function: python-line-gdrive-dev Initiating first time deployment. Deploying to API Gateway stage: api https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/ $ curl https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/ {"hello": "world"}
APIが作成できました。
chaliceにline-botsdkを追加する
$ pip install line-bot-sdk
chalice のベースプログラムに、line-bot-sdkを追加しましょう。
これでオウム返しするbotになりました。
CHANNEL_ACCESS_TOKEN
, CHANNEL_SECRET
はデプロイ後に、Lambdaのコンソールで設定するか、
ここのNoneのところに設定しましょう。
CHANNEL_ACCESS_TOKEN = os.getenv('CHANNEL_ACCESS_TOKEN', None) CHANNEL_SECRET = os.getenv('CHANNEL_SECRET', None)
今回はこちらを実行します。
import sys import json import os from chalice import Chalice from flask import Flask, request, abort, render_template from linebot import ( LineBotApi, WebhookParser ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, TextSendMessage, ImageMessage ) from linebot.models.events import ( SourceGroup, SourceRoom, SourceUser ) app = Chalice(app_name='python-line-gdrive') """ logging """ import logging from logging import getLogger logger = getLogger(__name__) logger.setLevel(logging.DEBUG) """ Line TOKEN """ CHANNEL_ACCESS_TOKEN = os.getenv('CHANNEL_ACCESS_TOKEN', None) CHANNEL_SECRET = os.getenv('CHANNEL_SECRET', None) line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN) parser = WebhookParser(CHANNEL_SECRET) def handle_message(event: MessageEvent): line_bot_api.reply_message( event.reply_token, TextSendMessage(text=event.message.text) ) @app.route("/bot", methods=['POST']) def bot(): signature = app.current_request.headers['X-Line-Signature'] body = app.current_request.raw_body.decode('utf-8') # print('signature:{}'.format(signature)) print('request body:{}'.format(body)) try: events = parser.parse(body, signature) except InvalidSignatureError: print("InvalidSignatureError") abort(400) for event in events: # return if callback test request if event.reply_token == "00000000000000000000000000000000": print('request is callback test') return "ok" # skip if not MessageEvent if not isinstance(event, MessageEvent): print("not Message Event") continue # skip if not TextMessage if not isinstance(event.message, TextMessage) and not isinstance(event.message, ImageMessage): print("not TextMessage and ImageMessage") continue message_type = event.message print("message_type = {}".format(message_type)) if isinstance(message_type, TextMessage): handle_message(event) return 'OK'
chaliceでは、依存関係は vendorディレクトリにダウンロードしておく必要があります。
$ mkdir vendor $ pip install -U line-bot-sdk==1.5.0 -t ./vendor/ $ pip install -U Flask==0.12.2 -t ./vendor/
requirementにも追加しておきましょう。
chalice==1.0.4 Flask==0.12.2 line-bot-sdk==1.5.0
そしてデプロイ。
$ chalice deploy
ちなみにですが、pipのバージョンが10系の人は、 どこかで、こんなエラーがでるかもしれません。
AttributeError: module 'pip' has no attribute 'main'
その場合は、これで解決できるかと思います。
sudo python -m pip install --upgrade pip==9.0.3
なんか長くなりそうなので、いったんここまでにします。
Qiita はこちら
座標上の四角形の中に、点が入っているかを調べたい
はじめに
点(a,b,c,d)をもつ四角形の中に点eが入っているか、を出すためにベクトルの外積を使用します。 numpyって本当に便利ですね。
考え方
- まず、原点から点(a,b,e)へのベクトルA,B,Eを作成。
- 点aからbへのベクトルAB、点aからeへのベクトルAEを計算。
- ベクトルAB、ベクトルAEの外積を計算
- b->c,c->d,d->aに対しても2,3を実施する。
- a->b, b->c, c->d, d->a のすべてで外積がマイナスであれば、 点は確変の左側にあるといえる ->四角形の中にある
実装
以下が実装。
def in_rect(rect,target): a = (rect[0][0], rect[0][1]) b = (rect[1][0], rect[1][1]) c = (rect[2][0], rect[2][1]) d = (rect[3][0], rect[3][1]) e = (target[0], target[1]) # 原点から点へのベクトルを求める vector_a = numpy.array(a) vector_b = numpy.array(b) vector_c = numpy.array(c) vector_d = numpy.array(d) vector_e = numpy.array(e) # 点から点へのベクトルを求める vector_ab = vector_b - vector_a vector_ae = vector_e - vector_a vector_bc = vector_c - vector_b vector_be = vector_e - vector_b vector_cd = vector_d - vector_c vector_ce = vector_e - vector_c vector_da = vector_a - vector_d vector_de = vector_e - vector_d # 外積を求める vector_cross_ab_ae = numpy.cross(vector_ab, vector_ae) vector_cross_bc_be = numpy.cross(vector_bc, vector_be) vector_cross_cd_ce = numpy.cross(vector_cd, vector_ce) vector_cross_da_de = numpy.cross(vector_da, vector_de) return vector_cross_ab_ae < 0 and vector_cross_bc_be < 0 and vector_cross_cd_ce < 0 and vector_cross_da_de < 0 if __name__ == '__main__': rect = [[48, 43],[45, 42],[43, 38],[45, 39]] print(in_rect(rect, [44,45])) # -> False print(in_rect(rect, [46,41])) # -> True
参考
http://www.endo-yuta.com/2011/09/blog-post_06.html
Qiitaにも投稿しています。
古いMacでもAirDropを使えるようにする方法
私のiMacは2010年モデルなので、AirDropに対応していません。 2012年以降に発売されたものから対応しているらしい。
ですが、AirDropってふとしたときに便利ですよね 使いたかったので調べたところ、コマンドがあったので、メモしておきます。
$ defaults write com.apple.NetworkBrowser BrowseAllInterfaces -bool TRUE; killall Finder ``` もとに戻すには以下のコマンドです。
$ defaults write com.apple.NetworkBrowser BrowseAllInterfaces -bool False; killall Finder ```
参考 https://lifehacker.com/enable-airdrop-on-older-unsupported-macs-1556180698
Qiitaはこちら。
golangで配列を、分散してマージしたい
タイトルのとおり、なのですが、 コードを書いていて、以下みたいなマージをしたかったので、メモしておきます。
func main() { s1 := []int{1, 2, 3, 4, 5} s2 := []int{7, 8,9,10} s3 := MergeSlice.Mix(s1, s2) fmt.Println(s3) } // -> [1 7 2 8 3 9 4 10 5]
みたいな感じです。 なんでこれをやりたかったかというと、 2つの配列に入った処理を一定時間に按分して処理させたくて、 かつ、同じタイミングでそれぞれの処理が実行されたくなかったからです。
例えばgoroutineでそれぞれを並列実行すると 配列の数が同じだった場合は、実行タイミングが近くなりすぎてしまいました。
ライブラリがなかったので、作成しました。
func Mix(slice1 []int, slice2 []int) []int { var resp []int // ゼロチェック、どちらかが0なら、もう一つが返却される if len(slice1) == 0 { return slice2 } else if len(slice2) == 0 { return slice1 } long, short, step := checkSlice(slice1, slice2) // 返却されるスライスは2つの合計値の値を持つ goal := len(long)+len(short) for i := 1; i <= goal; i++ { if i%(step+1) == 0 { if len(short) != 0 { resp = append(resp, short[0]) short = short[1:] } else { resp = append(resp, long[0]) long = long[1:] } } else { resp = append(resp, long[0]) long = long[1:] } } return resp } func checkSlice(s1 []int, s2 []int) ([]int, []int, int) { // 大きい方の配列を判定し、大きい配列、小さい配列、小さい配列の間隔を返します。 if len(s1) > len(s2) { return s1, s2, int(len(s1) / len(s2)) } else { return s2, s1, int(len(s2) / len(s1)) } }
APIのテスト書いたので覚書
はじめに
最近サーバーレスを良くやっているので、 APIのテストを書きたくて、いいフレームワークを探していました。 とりあえず動くところまで行けたので、覚書しておきます。
環境
- PC: Macbook Pro
- OS: macOS High Sierra
- Node.js: 6.13.0
やりたいこと
- 学習コストが少なくAPIテストをテストを書きたい
- メンテナンスしやすいこと
- レスポンスの値を、後続のテストケースで使いたい。
Frisby + Jest
さがしていて出会ったのが、Frisby + Jestという構成 frisbyはAPIテストのフレームワーク、Jestが実行環境みたいです。
https://github.com/vlucas/frisby
シンプルに書ける
const frisby = require('frisby'); it('should be a teapot', function (done) { frisby.get('http://httpbin.org/status/418') .expect('status', 418) .done(done); });
ネストした処理も記述できる
const frisby = require('frisby'); const Joi = frisby.Joi; // Joiは便利らしい describe('Posts', function () { it('should return all posts and first post should have comments', function (done) { frisby.get('http://jsonplaceholder.typicode.com/posts') .expect('status', 200) .expect('jsonTypes', '*', { userId: Joi.number(), id: Joi.number(), title: Joi.string(), body: Joi.string() }) .then(function (res) { // res = FrisbyResponse object let postId = res.json[0].id; // これでやりたかったこともできる return frisby.get('http://jsonplaceholder.typicode.com/posts/' + postId + '/comments') .expect('status', 200) .expect('json', '*', { postId: postId }) .expect('jsonTypes', '*', { postId: Joi.number(), id: Joi.number(), name: Joi.string(), email: Joi.string().email(), body: Joi.string() }); }) .done(done); }); });
ビルトインのアサーションも豊富
- status
- header
- json
- jsonStrict
- jsonTypes
- jsonTypesStrict
- bodyContains
他にも拡張ハンドラが作成できる、とかJasmine Matcherを直接アサーションに記述できるとか。 使ってませんが。
環境構築
インストール
環境構築や使い方はこちらを参考にさせてもらいました。
$ npm install frisby --save-dev $ npm install jest --save-dev
package.json
npm run apitest
で実行できるように、package.jsonも修正しておきましょう。
: : }, "devDependencies": { "frisby": "^2.0.14", "jest": "^22.4.3", }, "scripts": { "apitest": "jest" } :
テスト書いてみる
Jestは、__test__
フォルダ配下か、
.spec.js
もしくは .test.js
のファイル名をフィルタして、
テスト実行してくれるみたいです。
const frisby = require('frisby'); const Joi = frisby.Joi; var target_host = 'https://hostname'; var arg1 = 'sample'; var arg2 = '1234567890'; var arg3 = ""; var arg4 = ""; it('__正常系__JSONアサーション', function (done) { frisby.post(target_host + '/api1', { "req_body1": arg1, "req_body2": arg2 } ) // ステータスコードのアサーション .expect('status', 200) // jsonのアサーションはこう書く .expect('jsonTypes', '', { 'res_body1': Joi.string().required(), 'res_body2': Joi.string().required() }) .then(function (res) { // arg3,4は後続のテストでも使用できる。 arg3 = res.json.res_body1; arg4 = res.json.res_body2; // コンソールにログを出力もできる console.log(arg3); console.log(arg4); }) .done(done); }); it('__正常系__JSONのリストアサーション', function (done) { frisby.setup({ request: { //例えばレスポンスの値を、ヘッダーに追加してリクエストしたいとき headers: { 'Authorization': arg3 } } }) .get(target_host + '/api2?req_body1=' + arg4) .expect('status', 200) // listのアサーション .expect('jsonTypes', '', { 'res_body3': Joi.array() }) .then(function (res) { // リストの中身のアサーション expect(res.json.res_body3[0].aaa).toBe("bbb"); }) .done(done); });
expectの中身はまだまだ勉強が必要です。 ネット上の情報を見る限り、決め打ちのjsonとアサーションもできる気がします。 これでPythonで書いたサーバーレスAPIが Pythonでut,it、NodeでAPItestとある程度テストをかけて大変うれしい次第です。 サーバーレスアーキテクチャって微妙にデバッグしにくいのは自分だけでしょうか? APIのテスト、みんなどう書いてるんでしょ?
qiitaにもあります。