Kuzunoha-NEのブログ

プログラミングなどの勉強をしてます

【Python】Flaskでcreate_app関数を作って使う

こんばんは、葛の葉です。

今回はFlaskで調べものをしているとよく見かけるcreate_app関数を使ってみます。

環境

Python 3.6.6

Flask 1.0.2

ディレクトリ構成はこちら

カレントディレクトリ
├── app_factroy.py
├── app.py
└── test_app.py

create_appを作る

# app_factroy.py

from flask import Flask

def create_app(config_mode='test'):
    app = Flask(__name__)
    if config_mode == 'test':
        app.config['DEBUG'] = True
        app.config['SQL_ADDRESS'] = '127.0.0.1'
    else:
        app.config['DEBUG'] = False
        app.config['SQL_ADDRESS'] = '69.196.1.1'
    return app

create_app関数はFlask(__name__)インスタンスであるappに様々なコンフィグを与え、最終的にそのappを返却します。create_app関数の引数config_modeには開発環境か、テスト環境か、本番環境か、といった環境のステータスを渡してあげます。

create_appを呼び出す

# app.py

from app_factroy import create_app

app = create_app('dev')
# import os
# app = create_app(os.getenv('FLASK_CONFIGURE_MODE'))

# 以下何らかの処理

if __name__ == __main__:
    app.run()

create_app関数を呼び出して、引数に環境情報を与えます。返り値をapp変数に代入します。そうしてapp.run()してあげれば、Flaskは実行されます。

また、os.getenvを使って環境変数から環境情報を与えるのもありかなぁと思います。今回のコメントアウト部分がそれです。FLASK_CONFIGURE_MODEという環境変数内にtestとか入れておくと、create_appの引数にtestが入ります。いいねぇ。

下記の記事を参考にベストプラクティスなコンフを作ると良いのでは、と思います。というか、私はそうやってます。ありがとうございます。

qiita.com

create_appでtestする

# test_app.py

from app_factroy import create_app

app = create_app('test')

assert '127.0.0.1' == app.config['SQL_ADDRESS'] 

こんな感じで呼び出してテストしてあげましょう。実行したら何も起こらずにコマンドラインが返ってくると思います。(なんかあったらテスト落ちてるって意味です。)

【Python】配列コピーについて

こんばんは、葛の葉です。

さて、Pythonの配列のコピーについて、書きます。

環境

Python3.6.6

コード達

Python 3.6.6 (default, Sep  5 2018, 03:40:52)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [1,2,3,4,5]
>>> b = a
>>> a
[1, 2, 3, 4, 5]
>>> b
[1, 2, 3, 4, 5]
>>> a[0] = 6
>>> a
[6, 2, 3, 4, 5]
>>> b
[6, 2, 3, 4, 5] #←b[0]が変更されている
>>> id(a)
140405585847624
>>> id(b)
140405585847624
>>> id(a) == id(b)
True
#IDの値は全く同じである。

配列の中身をコピーしているわけではなく、配列として参照しているアドレスをコピーしているため。

配列をコピーしたい場合

import copyを用いたコピー

c配列

>>> import copy
>>> c = copy.deepcopy(a)
>>> id(c)
140405578255624
>>> id(a) == id(c)
False #<- IDが異なる
>>> c
[6, 2, 3, 4, 5]
>>> c[0] = 7
>>> c
[7, 2, 3, 4, 5]
>>> a
[6, 2, 3, 4, 5]
>>> a
[6, 2, 3, 4, 5]
>>> b
[6, 2, 3, 4, 5]
>>> c
[7, 2, 3, 4, 5]
>>> a[0] = 8
>>> a
[8, 2, 3, 4, 5]
>>> b
[8, 2, 3, 4, 5]
>>> c
[7, 2, 3, 4, 5]

スライス[:]を用いたコピー

d配列

>>> d = a[:]
>>> d
[8, 2, 3, 4, 5]
>>> d[0] = 9
>>> d
[9, 2, 3, 4, 5]
>>> a
[8, 2, 3, 4, 5]
>>> id(d)
140405578255688
>>> id(d) == id(a)
False
>>> id(d) == id(a)
False

結論

リストをコピーするときは

import copy
new_array = copy.deepcopy(array)

または

new_array = array[:]

【Discord】Botから役職へのメンション方法

こんばんは、葛の葉です。

ちょっと前に会社でDiscord使ってる旨お伝えしたかと存じますけど、そんな中でDiscord内の役割(権限)にメンションを飛ばしたくなりました。それが意外と日本語に乗っていなかったのでこちらに記載します。

役職にメンション飛ばすんだって

実は以下に記載の通り。でも、最初見たときよくわからなかったのでざっくりと説明すると役割に一回特殊なメンション飛ばすと、その飛ばしたときの書き込みがちょっと変わった文字<@&????????????>みたいなものになるので、Botを使うときはその<@&????????????>を使うようにします。

discordhelp.net

やってみた

まず役割の設定をします。サーバーを右クリックしてサーバー設定→役割をクリック。

f:id:Kuzunoha-NE:20190306184544j:plain

飛ばしたい役職を選択してこの役職に対して@メンションを許可するをオンにして保存

f:id:Kuzunoha-NE:20190306184851j:plain

どこでもいいので\@<役職名>と送信する。今回は\@ハンプティダンプティそうすると送信された文字がちょっと変わった物になっている。

f:id:Kuzunoha-NE:20190306185244j:plain

<@$????????????????>みたいなものが出ていると思うので、これをそのまま書き込めばその役職へのメンションになる。

botなんかに

botなんかで役職に送信したい場合は<@$????????????????>を文字列に付け加えてあげればOKなはず。

【GAS】Googleフォームで入力してもらった値をLogger.logに出力する

こんばんは、葛の葉です。

さて、Googleフォームという大変便利がいいWebアプリがありますね。

www.google.com

これとGASを連携し、GAS上のLogger.logに出力することが出来ます。

こんなGoogleフォームです

f:id:Kuzunoha-NE:20190214115619j:plain

問い合わせ内容の中身をGASで取得するようにします。

スクリプトエディタを開く

先ずはスクリプトエディタを開きます。開き方はGoogleフォームのエディタモードから右上のその他のボタン(黒点が縦に三つ並んでいるボタン)をクリックして、出てきたリストからスクリプト エディタをクリックしてください。

f:id:Kuzunoha-NE:20190214120204j:plain

そうしたらスクリプトエディタ画面に移行するはずです。下記のようなコードが記載されていると思います。

function myFunction() {
  
}

コードの記入

以下のように記入し、保存してください。(Ctrl + s)など。

function myFunction(event){
  var values = event.response.getItemResponses();
  var value = values[0].getResponse();
  Logger.log(value)
}

初めて保存する際はプロジェクトの名前を問われますので、お好みで。

トリガーの設定

スクリプトエディタ画面で「現在のプロジェクトのトリガー」ボタン(時計の形をしたボタン)があります。

f:id:Kuzunoha-NE:20190214122619j:plain

それをクリックしてください。そうしますと、G Suite Developper Hubというページに飛びます。右下の「トリガーを追加」ボタンをクリックします。

f:id:Kuzunoha-NE:20190214122702j:plain

実行する関数を選択myFunctionになっていることを確認した後、下部にスクロールした後イベントの種類を選択フォーム送信時に変更してください。

f:id:Kuzunoha-NE:20190214122841j:plain

保存をすればGoogleアカウントのことを聞かれますので、そこの対応をお願いします。

問題なければここで製作は完了しています。

実際にフォームに入れてみる。

f:id:Kuzunoha-NE:20190214121658j:plain

こんな内容のものを送信します。

スクリプトエディタに移動して表示-> ログとしてみてください。

f:id:Kuzunoha-NE:20190214121900j:plain

このようにログに出力されています。

Loggerlogの部分を変えてみましょう

例えば以前作成したDiscordに送信するための関数に渡してもよいでしょう。その他のチャットツールとかでも使えるんじゃないかなぁって思います。

kuzunoha-ne.hateblo.jp

kuzunoha-ne.hateblo.jp

【Python】受け取った配列に対し、Keyが1,2,3,4と連番した数字になるような辞書を返す関数 + Unittest

こんばんは、葛の葉です。

引数に配列を渡すと、連番をKeyにした辞書型を返してくれる関数を作りました。必要かどうかわかんないけど、一時、入用だったのですが、多分もういらない関数なのでここで供養させてください。

こんなんなのよ

関数numbering_dictionary()の引数に['nezumi', 'ushi', 'tora', 'usagi']のような配列を渡すと{1: 'nezumi', 2: 'ushi', 3: 'tora', 4: 'usagi'}という辞書を返します。だからなんだってレベルのものですな。

関数はこんな感じ

helper_def.pyという名前で以下のコードを保存してください。

def numbering_dictionary(arry_choices_answer, start_key=1):
    '''
    受け取った配列に対し、Keyが1,2,3,4と連番した数字になるような辞書を返します。
    ex: numbering_dictionary(['nezumi','ushi','tora'])
        -> {1: 'nemuzi', 2: 'ushi', 3: 'tora'}
    start_key(int)は連番開始の数字になります。デフォルトは1。
    '''
    numbring_dictionary = {}
    for index_number, choices_answer in enumerate(arry_choices_answer, start=start_key):
        numbring_dictionary[index_number] = choices_answer
    return numbring_dictionary

Unittestはこんな感じ

Unittest用プログラミングは以下になります。test_helper_def.pyみたいな名前でhelper_def.pyと同じところに保存してください。なお、動作させるだけならこのファイルは要らないです。

import pytest

from . import helper_def

def test_numbering_dictionary():
    array1 = ['nezumi', 'ushi', 'tora', 'usagi']
    array2 = ['serval', 'araisan', 'vulpes_zerda', 'silverfox']
    array3 = ['one', 'two', 'three', 'four']
    assert {1: 'nezumi', 2: 'ushi', 3: 'tora',
            4: 'usagi'} == helper_def.numbering_dictionary(array1)
    assert {1: 'serval', 2: 'araisan', 3: 'vulpes_zerda',
            4: 'silverfox'} == helper_def.numbering_dictionary(array2)
    assert {2: 'one', 3: 'two', 4: 'three',
            5: 'four'} == helper_def.numbering_dictionary(array3, start_key=2)

【kubernetes】SecretとRedisのパスワード設定

こんばんは、葛の葉です。

さて、今回はkubernetesでRedisをデプロイする際のパスワードの設定とSecretの設定方法を記載します。

環境

minikube ver 0.31.0

Redis(image) tag 5.0.3

簡単なRedisのパスワード設定のおさらい

Redisがインストールされている環境であれば、以下のコマンドでパスワードを設定できます。

redis-server --requirepass [password]

コマンドを打つとRedisが起動して、以下のような感じでメッセージが出力されると思います。今回のパスワードは1212です。

root@657cb0734634:/data# redis-server --requirepass 1212
15:C 20 Feb 2019 04:05:48.917 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
15:C 20 Feb 2019 04:05:48.918 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=15, just started
15:C 20 Feb 2019 04:05:48.918 # Configuration loaded
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 5.0.3 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 15
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

15:M 20 Feb 2019 04:05:48.919 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
15:M 20 Feb 2019 04:05:48.920 # Server initialized
15:M 20 Feb 2019 04:05:48.920 * DB loaded from disk: 0.000 seconds
15:M 20 Feb 2019 04:05:48.920 * Ready to accept connections

パスワードが設定されれば、そのRedisに接続したあと、操作を行うにはパスワードが必要になります。例えばredis-cliで接続した際は、auth [password]といった形で認証を得ないといけません。

root@657cb0734634:/data# redis-cli -h 192.168.99.100 -p 6379 
192.168.99.100:6379 > auth 1111
ERR invalid password
192.168.99.100:6379 > auth 1212
OK

ERR invalid passwordと出るとパスワードが間違っています。OKが出てれば接続できます。ここまでがRedisでパスワードを設定する方法とそのRedisに繋げる方法です。

redis.io

KubernetesのSecret

さて、KubernetesのSecretですが、詳細は以下のリンクを見てもらえればと思います。

kubernetes.io

KubernetesではこのSecretというオブジェクトを使ってパスワードを管理したほうが良いようです。先ほどの1212というパスワードを管理するSecretを作成します。

ファイル名test-secret.yaml

apiVersion: v1
kind: Secret
metadata:
    name: redis-password # secretの名称となり、k8s上で使用する名前
type: Opaque
data:
    RedisPassword: MTIxMg== #base64でないと受け付けません。

metadatanameを使って、このSecretをredis-password命名し、Kubernetes内ではこの名前でこのSecretを呼び出します。また、data内にあるRedisPasswordMTIxMg==はKeyとValueになっています。MTIxMg==base641212になります。Keyは複数作ることが出来ますので、一つのSecretに複数のパスワード等を含めることが出来ます。例えばusernamekeyとpasswordkeyを入れるなんてことも出来ます。

kubectl apply -f test-secret.yamlとすれば設定が反映されます。

kubectl get secretとすればSecretが表示されます。

NAME                       TYPE                                  DATA      AGE
default-token-57h4x        kubernetes.io/service-account-token   3         5d
redis-password             Opaque                                1         28m

default-token-57h4xはminikubeのものかなぁと。

Podの設定はこんな感じ

次にKubernetes上でRedisの入ったPodを作成します。今回はSecretの設定はPodで行いますが、DeployMentやStatefulSetでも設定可能です。

ファイル名test-pod.yaml

apiVersion: v1  
kind: Pod
metadata:
  name: test-redis
  labels:
    app: test-redis #ここの値を用いてServiceと連携する
spec:
  containers:
  - name: test-redis-ctr
    image: redis:5.0.3
    env:
    - name: REDIS_PASSWORD # Pod内に挿入する環境変数の名前
      valueFrom:
        secretKeyRef: 
          name: redis-password # 先ほど作成したSecret名
          key: RedisPassword #  先ほど作成したKey名。Valueが環境変数内に代入される。
    command: ["redis-server"]
    args: ["--requirepass $(REDIS_PASSWORD)"]
    ports:
    - containerPort: 6379

yaml内のspec以下にcontainers項目があって、これらが生成するコンテナの詳細に設定する部分になります。-nameはコンテナの名前です。imageはそのPodを作成するに使用するDockerimageを記します。

また、envがあって、これがPod内の環境変数を作成する項目になります。env-name環境変数名です。 valueFromはその環境変数の値を引っ張ってくる設定ファイルがどの形式であるかというもので、Secretを使用する場合はsecretKeyRefとします。configMapKeyRefだとConfigMapというオブジェクトを使用するようになります。nameはSecretの名前を、keyはSecret内dataで記入したkeyであるRedisPasswordにします。

commandはPod生成時に実行されるコマンドで、redis-serverというコマンドを、argsで引数を渡しています。--requirepassは先に説明したとおりで、$(REDIS_PASSWORD)環境変数部分になります。ブランケットの括弧${}ではなくパレンティスの括弧$()なので注意してください。

kubectl apply -f test-pod.yamlで作成しましょう。

kubectl get podsで出来たPodを見られます。

NAME                              READY     STATUS      RESTARTS   AGE
test-redis                        1/1       Running     0          34m

なお、STATUSがErrImagePullといったものであった場合は、Redis:5.0.3のイメージが存在していないので作成できていないというエラーになります。minikubeでしたらdocker pull redis:5.0.3とかやってみてください。

Serviceを使ってみる(minikube)

次にServiceを使って先のPodを確認出来るようにします。

ファイル名test-svc.yaml

apiVersion: v1 # ここも固定。kubernetes側が対応したらVの値を増やす形に
kind: Service metadata:
  name: test-service # Serviceそのものの名前。アンスコが使えない等ルールあり
spec:
  type: NodePort  
  selector:
    app: test-redis 
  ports:
  - port: 6379 
    targetPort: 6379 

Serviceの説明は省きます。kubectl apply -f test-svc.yamlで作成し、kubectl get service (又は"svc")と打って確認しましょう。

NAME                    TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
test-service            NodePort       10.111.68.208   <none>        6379:30271/TCP   38m

CLUSTER-IPPORT(S)のコロン右側の値はそれぞれ異なると思います。

IPアドレスとポート番号を確認する

GKEと違って、minikubeではNodePortタイプ(LoadBalancerタイプも同様)のServiceにEXTERNAL-IPは振られないようです。代わりに、minikubeの下記のコマンドでIPアドレスを確認する必要があります。

minikube service [Service名] --url

kubernetes.io

今回はminikube service test-service --urlです。そうしますと、http://192.168.99.100:30271といった表示が出てくると思います。IPアドレス部分、Port番号部分はそれぞれ異なると思います。

C:\>minikube service test-service --url
http://192.168.99.100:30271

これが今回生成したServiceにアクセスするためのIPアドレスとPort番号になります。

redis-cliでアクセス

下記コマンドを打ちます。

redis-cli -h [IPアドレス] -p [Port番号]

root@kuzunohasan:# redis-cli -h 192.168.99.100 -p 30271
192.168.99.100:30271> get 1
(error) NOAUTH Authentication required.
192.168.99.100:30271> auth 1212
OK
192.168.99.100:30271> get 1
(nil)

最初のget 1(error) NOAUTH Authentication required.と弾かれていますが、auth 1212とした後、get 1とした場合は(nil)とされています。無事成功しています。

綺麗にする

kubectl apply -f ***.yamlで設定したものはkubectl delete -f ***.yamlで削除可能です。

また、applyでもそうですが、ディレクトリを選択すれば、ディレクトリ内のyamlファイル全てを読みに行き、全てを削除してくれます。

C:\>kubectl delete -f test-secret.yaml
secret "redis-password" deleted

C:\>kubectl delete -f test-pod.yaml
pod "test-redis" deleted

C:\>kubectl delete -f test-svc.yaml
service "test-service" deleted

なんでRedisConfでパスワードの設定しないの?

今回のRedisのパスワードの設定はサーバー実行コマンドのオプションを使っていて、confを使った設定は行っていません。なぜならconf内で環境変数を呼び出して取得する方法がなかったからです…シェルスクリプトを使ったらよいかもしれませんね。

github.com

【Python】 値が文字列の中に含まれているか確認する。

こんばんは、葛の葉です。

さて、標題の件になりますが、ちょっと言葉だと説明しにくいかなと思います。例を挙げると

I Have a Dream.

Wikipediaより引用

ja.wikipedia.org

この中に特定の文字が入っていないかをBoolで返してもらう方法を書きます。

inを使う

例えばaという単語が入っているかを確認するには以下のようにします。

 'a' in 'I Have a Dream'

以下がコマンドラインPythonを実行したときの結果です。

>>> 'a' in 'I Have a Dream'
True
>>> 'b' in 'I Have a Dream'
False

Boolで返るのでassertも使えます。

>>> assert 'a' in 'I Have a Dream'
>>> assert 'b' in 'I Have a Dream'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

assert 'a' in 'I Have a Dream'は特に問題がないためTrueとなり、エラーなどは発生していません。一方、assert 'b' in 'I Have a Dream'はFalseとなるため、AssertionErrorとなっています。

listに入った複数の文字列が該当の文字列内に含まれているかの確認

例えば以下のように、IHaveaの文字全てが該当の文字列に含まれているかを確認したいとします。しかし、下記のようにリストをinとしてもTypeErrorが返されます。

>>> ['I', 'Have', 'a'] in 'I Have a Dream'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'in <string>' requires string as left operand, not list

リスト内包表記を使う

というわけでリスト内包表記を使いましょう。

False not in [i in 文字列 for i in 検索したい文字列の入ったリスト]

コマンドラインでは以下のようになります。

>>> False not in [i in 'I Have a Dream' for i in ['I', 'Have', 'a']]
True

分解するとFalse not in [リスト][i in 'I Have a Dream' for i in ['I', 'Have', 'a']]になると思います。

False not in [リスト]は最初にお話した'a' in 'I Have a Dream'の応用で、[list]の中にFalseが無ければTrueとなります。すなわち、リスト内が全てTrueだとTrueを返し、一つでもFalseがあればFalseを返します。ヤヤコシイネ。

>>> False not in [True,True,True,True]
True
>>> False not in [True,True,True,False]
False
>>> False not in [False,False,False,False]
False

さて、[i in 'I Have a Dream' for i in ['I', 'Have', 'a']]ですが、こちらは下部と同じ結果になります。

return_list = []
for i in ['I', 'Have', 'a']:
    result = i in 'I Have a Dream'
    return_list.append(result)

return_lisrt
[True, True, True]

結果は[True, True, True]となります。

すなわち、for文を用いて、含まれているか確認したい文字列が入ったリストの要素を一つずつとりだし、それぞれinを用いてBool値を返してもらい、それをリストの中に格納しています。

リスト内包表記での結果はこちら。

[i in 'I Have a Dream' for i in ['I', 'Have', 'a']]
[True, True, True]

含まれていない文字があればFalseがリストに返されます。

>>> [i in 'I Have a Dream' for i in ['I', 'king', 'a']]
[True, False, True] # kingの文字は存在しない

上記のFalse not inと組み合わせると

>>> False not in [i in 'I Have a Dream' for i in ['I', 'Have', 'a']]
True
>>> False not in [i in 'I Have a Dream' for i in ['I', 'king', 'a']]
False

と、このようになるわけです。

assertではこうです。

>>> assert False not in [i in 'I Have a Dream' for i in ['I', 'Have', 'a']]
>>> assert False not in [i in 'I Have a Dream' for i in ['I', 'king', 'a']]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

関数はこちら

def bool_check_words_in_word(check_words: list, the_word: str):
    # check_wordsには確認したい文字が含まれているリストを入れてください。
    # the_wordにはチェックしたい文字列を入れてください。
    return False not in [check_word in the_word for check_word in check_words]
print(
    bool_check_words_in_word(
        check_words=['I', 'Have', 'a'],
        the_word='I Have a Dream')
)

True