Pippi Life

主に仕事に関連するITのことや、プライベートもちょいちょい書きます。

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からのリクエストをローカルに投げるようにしてみましょう。 サーバーサイドは、前回のプログラムを利用します。

  1. 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のコンソールから、上記ドメインで設定します。 接続確認のボタンを押下すると、「成功しました」と表示されるかと思います。 image.png

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からリクエストを飛ばしてみましょう。 image.png

オッケェェェイイーーーーーー!!

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が返ってきました。

image.png

AWS上のログでも、認可コードが確認できました。

image.png

この認可コードを使えば、GoogleDriveへアップロードできるはず!! この認可コードはどこかに保持しておかないと、毎回認証しないといけなくなっちゃいますね。

保存もAWSのサービスに乗っかります。が、また次に続くことにします。

Qiita はこちら

Lineでやり取りした画像を自動でGoogleDriveにアップしたいからBotを作ることにしました。Part.1

困っていたこと

LINEを使っていて、よくあるのが、 イベントのときに、なんかのグループ作って、 情報をやり取りし、イベント後に写真をLINEに投稿し合うってやつ、ありますよね。 正直、写真の保存がマジでしんどいのです。

保存が面倒なら、自動でグーグルドライブに保存してくれるBot作ったらいいじゃない!

やりたいこと

LINEでやり取りしている写真を、グーグルドライブに放り込みたい。 自動で。

アーキテクチャ

まずはアーキテクチャをざっくり考えます。 絵で書くとこんな感じです。

image.png

実際に使うときの流れは以下の通り

  1. 写真をユーザーがトークに送信
  2. botAPIに通知
  3. botAPIがLINEから画像をDL
  4. 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がオーソライゼーションサーバー、リソースサーバーを提供しているので、一緒に サービス・プロバイダとして記載しました。

image.png

Googleでログインとか、Facebookでログインとかで使われていますね。 あれです。ちょっと怖いけど、非常に便利な仕組みですね。これからは安心して、「〇〇でログイン」できます。

oAuthの理解には こちらの説明が参考になりました。 こちらの絵も参考になりました。 ありがとうございます。

OAuthで認証してGoogle Driveを使うために

まずは、この赤枠をやるための準備をします。 クライアントになるには、クライアントID が必要になります。 image.png

Googleのアクセストークンの理解には、こちらを参考にしました。

ClientIDの作成

https://console.developers.google.com/apis/credentialsこちらから作成します。

  1. 認証情報を作成をクリックします。 image.png

  2. OAuthクライアントIDを選択します。 image.png

  3. ここで、本来はウェブアプリケーションを選択するのですが、理解のためにまずは「その他」を選択します。作成をクリック。 image.png

  4. クライアントIDができました。 image.png

  5. このままでは使わないので、一番右のDLアイコンをクリックしてCredentialファイルをDLしましょう。 image.png←これ

  6. DLすると、こんな感じのデータが入っています。アプリケーションはこの情報をもって、クライアントとなることができます。 image.png コールバックページを示すのは、redirect_urisです。今回のクレデンシャルファイルはlocalhostを指しています。

7. 同意画面の設定 image.png クライアント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()

実行すると、こんな画面が開くと思います。 さきほど同意画面で、記述したものが表示されています。 image.png

私はブラウザにすでにログインしていたので、ログインしているアカウントが表示されています。 image.png

ちなみに「Qiitaのサンプル」リンクをクリックすると、このように表示されます。 image.png

認証すると、以下のような文字が。 「The authentication flow has completed.」 Authフローが完了と出てますね。

GoogleDriveにもファイルがアップロードされています。 image.png

これで、OAuthによる認可とアップロードは完了してしまいました。 認可コードやアクセストークンが出てこなかったので、もう少し詳しく見てみることにします。

認可コードとコールバックページ

実は、先程表示された「The authentication flow has completed.」が 「認可コードを含むコールバックページ」に該当します。 image.png

「The authentication flow has completed.」が表示されるページの、 クエリストリングとして渡されるcodeが認可コードになります。

http://localhost:8080/?code=4/AAAjqBdam8TJPC33jLr0eo_PQ119ZQeoyhi8t7aPZNhQHYxdtLA7Wzp3sTV7SQNevQbd1TU0j3mF7Fe7IEteroA#

PyDriveに認可コードを渡すと、 クライアントIDと認可コードをもってアクセストークンを取得し、 APIをコールしてファイルアップロードをしてくれているのでした。

ところで今回使っているのは、 Googleで作成したのは「その他」のクライアントIDです。 コールバックページはlocalhostを指していました。

今回のサンプルコードのようにローカルで動かすときは良いのですが、 Linebotで動かしたときは、localhostではないところにリダイレクトして、 codeを取得しないといけません。

コールバックページをlocalhostではない所に設定する。

  1. 再度、https://console.developers.google.com/apis/credentialsで作成します。

  2. 今度は、「ウェブアプリケーション」を選択します。 image.png

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

こちらをクリックしてみます。 image.png

先程、設定したドメイン「sample.com」が表示されます。 認証すると、「sample.com」ドメインは存在していないため、認証してリダイレクトしてもエラーになります。

image.png

しかし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を追加する

まずはline-bot-sdkをインストールしましょう。

$ 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って本当に便利ですね。

考え方

  1. まず、原点から点(a,b,e)へのベクトルA,B,Eを作成。
  2. 点aからbへのベクトルAB、点aからeへのベクトルAEを計算。
  3. ベクトルAB、ベクトルAEの外積を計算
  4. b->c,c->d,d->aに対しても2,3を実施する。
  5. a->b, b->c, c->d, d->a のすべてで外積がマイナスであれば、 点は確変の左側にあるといえる ->四角形の中にある

image.png

実装

以下が実装。

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))
    }
}

GitHubにもアップしています。 Qiitaはこちら

APIのテスト書いたので覚書

はじめに

最近サーバーレスを良くやっているので、 APIのテストを書きたくて、いいフレームワークを探していました。 とりあえず動くところまで行けたので、覚書しておきます。

環境

やりたいこと

  • 学習コストが少なく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で書いたサーバーレスAPIPythonでut,it、NodeでAPItestとある程度テストをかけて大変うれしい次第です。 サーバーレスアーキテクチャって微妙にデバッグしにくいのは自分だけでしょうか? APIのテスト、みんなどう書いてるんでしょ?

qiitaにもあります。