Kuzunoha-NEのブログ

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

【TypeScript】interfaceを作る

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

今回はTypeScriptでinterfaceオブジェクトを作ってみます。

生物に対し足の数と全長メンバー+成長というメソッド

犬というクラスとカエルというクラスを作る。

それぞれ、footというメンバーとlengthというメンバー,そしてgrowthというインスタンスメソッドを持つものとします。

AnimalInterface

// animalinterface
export interface AnimalInterface {
    foot: number;
    length: number;
    growth(): void;
};

ここでは宣言だけして、内容物は何も書きません。インターフェイスとはそのようなものです。

tsc animalinterface.ts

コンパイルをします。

インターフェイスを使ってみる。

// objects.ts
import { AnimalInterface } from "./animalinterface.js"

class Dog implements AnimalInterface {
    name:string;
};

implementsインターフェイスを使う宣言になります。今回はインターフェイスを使用していますが、その機能を無視した形を取っていますので、コンパイル時にエラーになります。

[kuzunoha@:00:31:30:~]$ tsc objects.ts
objects.ts:4:7 - error TS2420: Class 'Dog' incorrectly implements interface 'AnimalInterface'.
  Type 'Dog' is missing the following properties from type 'AnimalInterface': foot, growth

4 class Dog implements AnimalInterface {
        ~~~


Found 1 error.

interfaceを使用している場合、その使用したinterfaceオブジェクトで宣言されているメンバーやメソッドを必ず宣言しなきゃいけないものになります。

改めてインターフェイスを使って動物のクラスを作る。

object.tsという名前で以下のコードを作成する。

// objects.ts
import { AnimalInterface } from "./animalinterface";

// 犬クラス
class Dog implements AnimalInterface {
    foot: number = 4;
    length: number = 30;
    tail: boolean = true;
    growth(): void {
        this.length = 90;
    };
};

// カエルクラス
class Frog implements AnimalInterface {
    foot: number = 0;
    length: number = 3;
    tail: boolean = true;
    growth(): void {
        this.length = 5;
        this.foot = 4;
        this.tail = false;
    };
};

let inu = new Dog();
let kaeru = new Frog();
console.log(`子犬の全長:${inu.length}cm,オタマジャクシの全長:${kaeru.length}cm`)
console.log(`子犬の足の数:${inu.foot}本,オタマジャクシの足の数:${kaeru.foot}本`)
console.log("---そして月日は流れ、二匹は成長した---")
inu.growth();
kaeru.growth();
console.log(`犬の全長:${inu.length}cm,カエルの全長:${kaeru.length}cm`)
console.log(`犬の足の数:${inu.foot}本,カエルの足の数:${kaeru.foot}本`)

コンパイルして実行する。

[kuzunoha@:01:26:25:~]$ tsc objects.ts
[kuzunoha@:01:28:10:~]$ node objects.js 
子犬の全長:30cm,オタマジャクシの全長:3cm
子犬の足の数:4本,オタマジャクシの足の数:0本
---そして月日は流れ、二匹は成長した---
犬の全長:90cm,カエルの全長:5cm
犬の足の数:4本,カエルの足の数:4本

このように犬クラスとカエルクラスは共通のメンバーとメソッドを持っているけど、中身がそれぞれで異なるといった場合はインターフェイスを使用する…であってるかな?

【Linux】Bashのバックグラウンドで実行する

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

さて、今回はLinuxでバックグラウンドモードで起動する方法をお伝えします。

flaskアプリを作る

以下のコードをapp.pyとして作成する。

from flask import Flask
app = Flask(__name__)


@app.route("/")
def index():
    return "Hello World!"

&を使う

例えばflaskアプリがあるとして、flask runで通常通り実行すると以下のようになります。

[kuzunoha@:13:44:38:~]$ flask run
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

こうなるとキーを受け付けません。

flask run &として実行をすればバックグラウンドで起動します。

[kuzunoha@:13:40:16:~]$ flask run &
[1] 11422
[kuzunoha@:13:41:06:~]$ * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
■ <- EnterKeyを押す
[kuzunoha@:13:42:21:~]$

コマンドが返ってきているようですがよくわからないのでエンターキーを押してみましょう。PSが表示されどういうことかがわかりやすくなります。

バックグラウンドで動いているのでCurlで確認できます。

[kuzunoha@:13:45:45:~]$ curl http://127.0.0.1:5000/
127.0.0.1 - - [19/Jul/2019 13:47:28] "GET / HTTP/1.1" 200 -
Hello World![kuzunoha@:13:47:28:~]$

バックグラウンドで動いていたコマンドを元に戻す

fgと打てばコマンドが戻ってきます。

[kuzunoha@:13:47:28:~]$fg
flask run

flask runのコマンドに戻ってきたのでCtrl + cで停止します。

^C[kuzunoha@:13:49:29:~]$

起動中のバックグラウンドコマンドを見るには

jobsコマンドを打ちます。

[kuzunoha@:13:49:29:~]$flask run &
[1] 12423
[kuzunoha@:13:50:16:~]$jobs
[1]+  実行中               flask run &

また、バックグラウンドで起動しているコマンドを削除をするにはkillを使います。kill %nとしてjobsで表示された[n]を指定します(nはjob番号といいます)。

[kuzunoha@:13:50:18:~]$kill %1
[1]+  Terminated              flask run
[kuzunoha@:13:53:30:~]$jobs
[kuzunoha@:13:53:39:~]$ <- jobに表示できるものがないため何も返されない

【TypeScript】mochaとchaiでユニットテストする

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

前回からTypeScriptを使っています。

kuzunoha-ne.hateblo.jp

今回はTypeScriptでUnittestを行いたいと思います。

フォルダ構成

├── src
│   └── fizzbuzz.ts
└── test
    └── fizzbuzz.test.ts

FizzBuzzをつくる

srcディレクトリ内でfizzbuzz.tsという名前のプログラムを作成します。

export class Numbers {
    fizz: number = 3;
    buzz: number = 5;
    fizzbuzz: number = this.fizz * this.buzz;
}

export class FizzBuzz {
    public function: string; check(params: number) {
        if (params % 15 == 0) {
            return "FizzBuzz!";
        } else if (params % 3 == 0) {
            return "Fizz!";
        } else if (params % 5 == 0) {
            return "Buzz!";
        } else {
            return params.toString()
        };
    };
}

export class とはモジュールとして他のJSから取得出来るようにするための記述?のようですね。まだこの辺りも含めて、JSとTSがよくわかっていないというのが現状です。モウシワケナイ。

tsc fizzbuzz.tsとして、コンパイルしましょう。

fizzbuzz.jsが出来上がります。

"use strict";
exports.__esModule = true;
var Numbers = /** @class */ (function () {
    function Numbers() {
        this.fizz = 3;
        this.buzz = 5;
        this.fizzbuzz = this.fizz * this.buzz;
    }
    return Numbers;
}());
exports.Numbers = Numbers;
var FizzBuzz = /** @class */ (function () {
    function FizzBuzz() {
    }
    FizzBuzz.prototype.check = function (params) {
        if (params % 15 == 0) {
            return "FizzBuzz!";
        }
        else if (params % 3 == 0) {
            return "Fizz!";
        }
        else if (params % 5 == 0) {
            return "Buzz!";
        }
        else {
            return params.toString();
        }
        ;
    };
    ;
    return FizzBuzz;
}());
exports.FizzBuzz = FizzBuzz;

テストプログラムをつくる

npmでmochaというライブラリとchaiというライブラリを入手します。この辺りもちょっとよくわからないので、間違ったインストール方法だったら申し訳ないです。

npm install mocha chai --save-dev

npm install @types/{mocha,chai} --save-dev

testディレクトリ内でfizzbuzz.test.tsという名前のプログラムを作ります。

import * as chai from "chai"

import { FizzBuzz } from "../src/fizzbuzz"
import { Numbers } from "../src/fizzbuzz"

describe("FizzBuzzのテスト", () => {
    it("check関数のテスト", () => {
        let fizzbuzz = new FizzBuzz();
        let nums = new Numbers();
        chai.assert.equal(fizzbuzz.check(nums.fizz), "Fizz!");
        chai.assert.equal(fizzbuzz.check(nums.buzz), "Buzz!");
        chai.assert.equal(fizzbuzz.check(nums.fizzbuzz), "FizzBuzz!");
        chai.assert.equal(fizzbuzz.check(4), "4");
    })
})

describeitとはmochaというテストフレームワークの記述になります。describeがテスト内容の大項目で、itが小項目に当たるといった感じでしょうか。

mochajs.org

chaiは多種のアサーション機能を備えたライブラリのようです。

www.chaijs.com

tsc fizzbuzz.test.tsとしてコンパイルします。

fizzbuzz.test.jsが出来上がります。

"use strict";
exports.__esModule = true;
var chai = require("chai");
var fizzbuzz_1 = require("../src/fizzbuzz");
var fizzbuzz_2 = require("../src/fizzbuzz");
describe("FizzBuzzのテスト", function () {
    it("check関数のテスト", function () {
        var fizzbuzz = new fizzbuzz_1.FizzBuzz();
        var nums = new fizzbuzz_2.Numbers();
        chai.assert.equal(fizzbuzz.check(nums.fizz), "Fizz!");
        chai.assert.equal(fizzbuzz.check(nums.buzz), "Buzz!");
        chai.assert.equal(fizzbuzz.check(nums.fizzbuzz), "FizzBuzz!");
        chai.assert.equal(fizzbuzz.check(4), "4");
    });
});

ユニットテストを実施する

testディレクトリがカレントディレクトリなら以下のコマンドを打ちます。

mocha fizzbuzz.test.js

以下のようにログが出力されます。

[kuzunoha@:23:44:18:~]$mocha fizzbuzz.test.js 


  FizzBuzzのテスト
    ✓ check関数のテスト


  1 passing (7ms)

【TypeScript】列挙型をTypeScriptで使う

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

現在、TypeScriptを勉強しています。

Enumesをちょっとだけ勉強しました。

ディレクトリ構成

├── app
│   └── enum.ts
└── enum.html

enum.html

/*./app/enum.tsじゃないよ*/
<script src="./app/enum.js"></script>

TypeScriptを書く

app/enum.tsをいじる。

enum Hoge {
    foo,
    bar,
    hoge,
    piyo,
    fuga
}

let tmp = Hoge.foo

console.log(tmp)

tsc app/enum.tsとしてjsにビルドします。そうするとapp/enum.jsが出来上がります。

app/enum.js

var Hoge;
(function (Hoge) {
    Hoge[Hoge["foo"] = 0] = "foo";
    Hoge[Hoge["bar"] = 1] = "bar";
    Hoge[Hoge["hoge"] = 2] = "hoge";
    Hoge[Hoge["piyo"] = 3] = "piyo";
    Hoge[Hoge["fuga"] = 4] = "fuga";
})(Hoge || (Hoge = {}));
var tmp = Hoge.foo;
console.log(tmp);

Chromeで表示してみる。

console.logの出力結果を見たいのでenum.htmlChromeで開き、F12キーを押します。

Consoleタブを開くと画像のような感じに出力されていると思います。

f:id:Kuzunoha-NE:20190704153856j:plain
コンソールログで0と表示されている。

let tmp = Hoge.foofooとしたところが、数字0に相当するからです。

let tmp = Hoge.barにすると1と表示されます。

enum.tsをもう少しいじる。

TypeScriptといえば型宣言なのかなぁと思います。

tmpという変数に代入できる値を数字だけにしてみましょう。

enum Hoge {
    foo,
    bar,
    hoge,
    piyo,
    fuga
}

/* :numberは数値のみ代入を受け入れるということ */
let tmp: number = Hoge.foo

console.log(tmp)

これでもtsc enum.tsによってbuildは通るのです。

enumってただの数字ですから。

また、TypeScriptにはオブジェクト型宣言というのが使えますのでこちらを使ってみます。

enum Hoge {
    foo,
    bar,
    hoge,
    piyo,
    fuga
}

/* :HogeはHogeというオブジェクトのみ受け付けるということ */
let tmp: Hoge = Hoge.foo

console.log(tmp)

これでも通ります。

オブジェクト型宣言の方が用途がわかりやすい気がしますので、私はこっちを選びたいなぁと思いました。

ちなみに

let tmp: Hoge = Hoge.foo
tmp = 5

としても

let tmp: number = Hoge.foo
tmp = 5

としても

いずれもビルドは通りました。

ちょっとこんがらがってきたので、勉強を続けたいと思います。

【Flask】Configについて

とても参考になったサイト

https://qiita.com/nanakenashi/items/e272ff1aafb3889230bc

https://www.subarunari.com/entry/2018/03/17/いまさらながらFlaskについてまとめる_〜Configuration〜

http://flask.pocoo.org/docs/1.0/config/

https://damyanon.net/post/flask-series-configuration/

Configを使おうというお話。

あなたは何らかのデータベースサーバーやあるいはGoogleSpreadSheetsと連携してプログラムを書いていることでしょう。

書いている内容としてはこんな感じでしょうか。

gss_url = "http://google.****.com"

さて、本番と開発をわけた開発をするとした場合、このgss_urlの値をツドツド変更しているのかもしれません。あるいは、コメントアウトして使っているのかもしれません。

# 本番
# gss_url = 'http://google.****.com'
# 開発
gss_url = 'http://test.google.****.com'

ただ、どちらかといえば本番時はhttp://google.****.com、そうでないときはhttp://test.google.****.comと条件分岐で指定してあげたいところです。

if HONBAN == True:
    gss_url = 'http://google.****.com'
else:
    gss_url = 'http://test.google.****.com'

また、Flaskなら開発環境でDEBUGモードで開発していると思います。本番でDEBUGモードが起きるのはセキュリティ的によくないでしょう。ただ、その複数の切り替えを手動で行っていると絶対忘れが発生して事故るでしょう。

app.run(debug=True)
# app.run()

開発環境、本番環境によって変えたい項目は沢山あるはずです。もう一つテスト環境というものがあると思います。

テスト環境はテスト用のデータが挿入された環境であることが望ましいかも知れませんし、あるいはデータそのものが全くない環境が望ましいものかもしれません。

テスト環境1
テスト環境2
開発環境
本番環境

それぞれの状況でアクセスするDBのアドレスやGSSのアドレスが切り替わるような仕組みがあったら便利ですね。今回は環境変数を使って切り替わりを実施できるような仕組みを考えます。

Flaskのデバッグモードとconfigについて

Flaskクラスのインスタンスにはconfigというものがあります。configはdictionaryとなっています。

app.pyとして以下を作成します。

from flask import Flask
app = Flask(__name__)
print(app.config)

python app.pyとして起動すると以下のようになります。

[kuzunoha@:11:09:05:~/sand_box/flaskconfig]$python app.py 
<Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093}>

また、app.pyの3行目を更新してprint(app.config['DEBUG'])として実行します。

app.py

from flask import Flask
app = Flask(__name__)
print(app.config['DEBUG'])

実行結果

[kuzunoha@:11:09:10:~/sand_box/flaskconfig]$python app.py 
False

次にapp.debug = Trueを追記してprintしてみます。

from flask import Flask
app = Flask(__name__)
app.debug = True
print(app.config['DEBUG'])

これを実行すると

[kuzunoha@:11:14:58:~/sand_box/flaskconfig]$python app.py 
True

それではapp.config['Debug'] = Falseと追記してからprintしてみます。

from flask import Flask
app = Flask(__name__)
app.debug = True
app.config['DEBUG'] = False
print(app.config['DEBUG'])

これを実行すると

[kuzunoha@:11:17:35:~/sand_box/flaskconfig]$python app.py 
False

すなわち以下の2つは同義であるということです。

app.debug = Boolean
app.config['DEBUG'] = Boolean

また、app.run(debug=True)で設定するDebugの真偽も同様です。

その他にも環境変数を使用するなどflaskをデバッグモードで実施する方法はあるようです。

参考:http://flask.pocoo.org/docs/1.0/quickstart/#debug-mode

このようにconfig[PARAMETAR]を変更することで、Flaskアプリケーションのコンフィグを変更できます。

その他のデフォルトのパラメータは公式に載っています。

参考:http://flask.pocoo.org/docs/0.12/config/#builtin-configuration-values

オリジナルのパラメータを作る

flaskのコンフィグパラメータは好きに作ることが出来ます。app.pyを以下のように記載します。

from flask import Flask
app = Flask(__name__)
app.config['hogehoge'] = 'piyopiyo'
print(app.config['hogehoge'])

そして実行すると

[kuzunoha@:11:19:08:~/sand_box/flaskconfig]$python app.py 
piyopiyo

となります。そして、これはすなわち以下のようにも使えます。

from flask import Flask
app = Flask(__name__)
app.config['MYSQL_ADDRESS'] = '127.0.0.1'
print(app.config['MYSQL_ADDRESS'])

実行すると

[kuzunoha@:11:34:35:~/sand_box/flaskconfig]$python app.py 
127.0.0.1

となります。ただ、以下のようにインスタンスとして持ち合わせることは出来ません。

from flask import Flask
app = Flask(__name__)
app.config['MYSQL_ADDRESS'] = '127.0.0.1'
print(app.config.mysql_address)

Traceback (most recent call last):
  File "app.py", line 4, in <module>
    print(app.config.mysql_address)
AttributeError: 'Config' object has no attribute 'mysql_address'

さて、それではその他にもパラメータを追加してMySQLに繋げてみましょう。以下が一例になります。(ほんとにただの一例ですのでこれで実行しても何も起きないです。)

from flask import Flask
app = Flask(__name__)
app.config['MYSQL_ADDRESS'] = '127.0.0.1'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = 'password'
app.config['MYSQL_PORT'] = 3306
app.config['MYSQL_DATABASE'] = 'hogepiyodb'

import MySQLdb
connection = MySQLdb.connect(
    host=app.config['MYSQL_ADDRESS'],
    user=app.config['MYSQL_USER'],
    passwd=app.config['MYSQL_PASSWORD'],
    port=app.config['MYSQL_PORT'],
    db=app.config['MYSQL_DATABASE'])
connection.commit()
connection.close()

MySQLdbというモジュールについては以下の参考サイトを見てください。

参考:https://uxmilk.jp/23509

app.config['MYSQL_ADDRESS'] = '127.0.0.1'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = 'password'
app.config['MYSQL_PORT'] = 3306
app.config['MYSQL_DATABASE'] = 'hogepiyodb'

と、このようにパラメータに値を代入していくことが出来るのです。

パラメータの代入方法の選択

Configのパラメータには直接値を代入する方法以外にも方法があります。

参考:https://www.subarunari.com/entry/2018/03/17/いまさらながらFlaskについてまとめる_〜Configuration〜

以下がそれぞれ同じ設定方法になります。ここに記載してる方法以外のものもあります。

ハードコーディング(参考)

上記に書いたものと同じですね。

app.py

from flask import Flask
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['MYSQL_ADDRESS'] = '127.0.0.1'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = 'password'
app.config['MYSQL_PORT'] = 3306
app.config['MYSQL_DATABASE'] = 'hogepiyodb'
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

結果は以下の感じ

[kuzunoha@:12:14:56:~/sand_box/flaskconfig]$python app.py 
True
127.0.0.1
root
password
3306
hogepiyodb

from_pyfile

cfgファイルを読み込むタイプです。app.pyと同じディレクトリにdevelopment.cfgというcfgファイルを作って、それを読み込むようにします。

development.cfg

DEBUG=True
MYSQL_ADDRESS='127.0.0.1'
MYSQL_USER='root'
MYSQL_PASSWORD='password'
MYSQL_PORT=3306
MYSQL_DATABASE='hogepiyodb'

app.py

from flask import Flask
app = Flask(__name__)
app.config.from_pyfile('development.cfg')
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

app.pyの3行目app.config.from_pyfile('development.cfg')に注目です。

app.config.from_pyfile(CFGファイル名)となっております。

実行結果は以下の通り

[kuzunoha@:12:29:10:~/sand_box/flaskconfig]$python app.py
True
127.0.0.1
root
password
3306
hogepiyodb

from_object

設定のクラスが書かれたPythonファイルを読み込むようにします。

setting.py

class Development(object):
    DEBUG=True
    MYSQL_ADDRESS='127.0.0.1'
    MYSQL_USER='root'
    MYSQL_PASSWORD='password'
    MYSQL_PORT=3306
    MYSQL_DATABASE='hogepiyodb'

app.py

from flask import Flask
app = Flask(__name__)
app.config.from_object('setting.Development')
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

実行結果は以下の通り

[kuzunoha@:12:37:18:~/sand_box/flaskconfig]$python app.py 
True
127.0.0.1
root
password
3306
hogepiyodb

app.pyの3行目app.config.from_object('setting.Development')に注目です。

app.config.from_object('Pythonファイル名.オブジェクト名')となっていることがわかると思います。

オブジェクトを取得するようになっているので、setting.pyに新しいクラスを作って読み込ませることも出来ます。クラスなので継承を使って新しいクラスを作っても良いです。

setting.py

class Development(object):
    DEBUG=True
    MYSQL_ADDRESS='127.0.0.1'
    MYSQL_USER='root'
    MYSQL_PASSWORD='password'
    MYSQL_PORT=3306
    MYSQL_DATABASE='hogepiyodb'

class Production(Development):
    DEBUG=False
    MYSQL_ADDRESS='63.31.125.212'

app.py

from flask import Flask
app = Flask(__name__)
app.config.from_object('setting.Production')
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

実行結果はこうなりました

[kuzunoha@:12:54:12:~/sand_box/flaskconfig]$python app.py 
False
63.31.125.212
root
password
3306
hogepiyodb

DEBUGMYSQL_ADDRESSの値が変更されていることと、それ以外は継承元のそれと同じであるというところが注目点です。

from_pyfile + from_object

ではこの2つを合体して使ってみるとどうでしょうか

password.cfg

MYSQL_PASSWORD='heavensdoor'

setting.py

class Development(object):
    DEBUG=True
    MYSQL_ADDRESS='127.0.0.1'
    MYSQL_USER='root'
    MYSQL_PASSWORD='password'
    MYSQL_PORT=3306
    MYSQL_DATABASE='hogepiyodb'

class Production(Development):
    DEBUG=False
    MYSQL_ADDRESS='63.31.125.212'

app.py

from flask import Flask
app = Flask(__name__)
app.config.from_object('setting.Production')
app.config.from_pyfile('password.cfg')
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

実行結果は以下の通りです。

[kuzunoha@:13:10:48:~/sand_box/flaskconfig]$python app.py 
False
63.31.125.212
root
heavensdoor
3306
hogepiyodb

最終的なMYSQL_PASSWORDheavensdoorになりました。app.pyの3-4行目を見てみてください。

app.config.from_object('setting.Production')
app.config.from_pyfile('password.cfg')

変数のそれと同じく、最後に宣言された値に上書きされていくからです。

環境変数を使ってそれぞれの環境にConfを変える

from_objectで読み込むクラスを変更すれば各環境ごとに使うconfigを変更できます。

# 開発なら
app.config.from_object('setting.Development')
# 本番なら
app.config.from_object('setting.Production')

辞書型を使うともう少しスマートになります。

dict_confmode = {
    'dev' : 'setting.Development',
    'pro' : 'setting.Production'
}

# 開発なら
confmode = dict_confmode['dev']
# 本番なら
confmode = dict_confmode['pro']

app.config.from_object(confmode)

さらに環境変数を使います。例えばFLASK_CONFIGURATIONという環境変数名に開発ならdev、本番ならproと入れておきます。

---開発環境なら---

環境変数

FLASK_CONFIGURATION=dev

app.pyの一部

import os

dict_confmode = {
    'dev' : 'setting.Development',
    'pro' : 'setting.Production'
}

env_confmode = os.getenv('FLASK_CONFIGURATION')

confmode = dict_confmode[env_confmode]

app.config.from_object(confmode)

---本番環境なら---

環境変数

FLASK_CONFIGURATION=pro

app.pyの一部

import os

dict_confmode = {
    'dev' : 'setting.Development',
    'pro' : 'setting.Production'
}

env_confmode = os.getenv('FLASK_CONFIGURATION')

confmode = dict_confmode[env_confmode]

app.config.from_object(confmode)

それぞれのapp.pyには全く違いはありません。この通り、PCに宣言した環境変数だけで環境を変更できるようになりました。

参考:https://wa3.i-3-i.info/word11027.html

共有することがあまりよろしくない値はfrom_pyfileへ

DBのpasswordや暗号方法、暗号キーやアクセストークンなどはcfgファイルへ記載するようにします。

そうしてinstanceディレクトリを作ってそのcfgファイルを置いておくようにします。

アプリケーションを初期化する際にinstance_relative_config=Trueという引数を当ててあげます。そうすることでinstanceというディレクトリからcfgファイルを読みに行くようになります。

app = Flask(__name__, instance_relative_config=True)

そうして、.gitignoreinstanceを入れてあげます。

最終的な構成はこのようになりました。

app.py
setting.py
instance/
 ┗ password.cfg

password.cfg

MYSQL_PASSWORD='heavensdoor'

setting.py

class Development(object):
    DEBUG=True 
    MYSQL_ADDRESS='127.0.0.1'
    MYSQL_USER='root'
    MYSQL_PASSWORD='password'
    MYSQL_PORT=3306
    MYSQL_DATABASE='hogepiyodb'

class Production(Development):
    DEBUG = False
    MYSQL_ADDRESS='63.31.125.212'

app.py

import os

from flask import Flask

app = Flask(__name__, instance_relative_config=True)
dict_confmode = {
    'dev' : 'setting.Development',
    'pro' : 'setting.Production'
}
env_confmode = os.getenv('FLASK_CONFIGURATION')
confmode = dict_confmode[env_confmode]
app.config.from_object(confmode)
app.config.from_pyfile('password.cfg')
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

実行結果

[kuzunoha@:14:38:21:~/sand_box/flaskconfig]$export FLASK_CONFIGURATION=dev
[kuzunoha@:14:38:30:~/sand_box/flaskconfig]$python app.py 
True
127.0.0.1
root
heavensdoor
3306
hogepiyodb
[kuzunoha@:14:38:32:~/sand_box/flaskconfig]$export FLASK_CONFIGURATION=pro
[kuzunoha@:14:38:43:~/sand_box/flaskconfig]$python app.py 
False
63.31.125.212
root
heavensdoor
3306
hogepiyodb

参考 Windows環境変数設定:https://www.k-cube.co.jp/wakaba/server/environ.html

参考 Unix,Linux,Mac環境変数設定:https://qiita.com/iam1at/items/91cb8478160c9fbee134

残りはベストプラクティスを見てみてください。

細かいところは違いますが、以下のURLの内容がよくわかるようになったと思います。後はここを読んでください。

https://qiita.com/nanakenashi/items/e272ff1aafb3889230bc

【Docker】コマンド以降をEchoするDockerfileの作成 - ENTRYPOINT

こんにちは、葛の葉です。

Dockerに出てくるENTRYPOINTについて調べていました。

今回はdocker run [IMAGE_ID] ******をエコーするDockerImageを作成したいと思います。

大変参考になったサイト

qiita.com

Dockerfile

FROM alpine:3.9.4
ENTRYPOINT ["echo"]
CMD ["Hello World!"]

今回はechoという名前でDockerImageにbuildしておきます。

実行してみる。

docker run --rm -it echo

とすると

Hello World!

と返ってきます。

[kuzunoha@:18:17:53:~]$docker run -it --rm echo
Hello World!

docker run --rm -it echo FOX

とすると

FOX

と返ってきます。

[kuzunoha@:18:18:39:~]$docker run -it --rm echo FOX
FOX

ENTRYPOINT(echo)がコマンドでCMD(Hello World)がデフォルト引数といったような感じになりました。

シェルを起動したいなぁ

しかしこのままでは以下のようなコマンドではシェルを起動できません。docker run -it --rm echo /bin/shでは/bin/shという値が返ってきます。

[kuzunoha@:18:22:53:~]$docker run -it --rm echo /bin/sh
/bin/sh

そのため、entrypointを上書きしてあげる必要があります。

docker run -it --rm --entrypoint=/bin/sh echoとすればシェルが起動します。

[kuzunoha@:18:27:45:~]$docker run -it --rm --entrypoint=/bin/sh echo 
/ # 

【DockerCompose】DockerfileのFROMに使える変数の設定

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

DockerfileでFROMに対する変数を入れることで、色々使いまわせます。

docs.docker.com

DockerComposeのargsを使えばこんなこともできます。

ファイル構成

.
├── Dockerfile
└── docker-compose.yml

Dockerfile

ARG Ver
FROM python:$Ver

docker-compose.yml

version: '3'
services:
  test_app:
    build: 
      context: ./
      args:
        Ver: "3.7.3"
    command: python --version

upしてみる。

Creating network "teste_default" with the default driver
Creating teste_test_app_1 ... done
Attaching to teste_test_app_1
test_app_1  | Python 3.7.3
teste_test_app_1 exited with code 0

もう一つServiceを追加して実行する。

docker-compose.yml

version: '3'
services:
  test_app:
    build: 
      context: ./
      args:
        Ver: "3.7.3"
    command: python --version

  test_app2:
    build: 
      context: ./
      args:
        Ver: "3.6.5"
    command: python --version

docker-compose upとしてみる。

Starting teste_test_app_1  ... done
Creating teste_test_app2_1 ... done
Attaching to teste_test_app_1, teste_test_app2_1
test_app_1   | Python 3.7.3
test_app2_1  | Python 3.6.5
teste_test_app_1 exited with code 0
teste_test_app2_1 exited with code 0

結論

ひとつのDockerfileを使いまわして複数のServiceを起動できる。