2021年1月7日木曜日

rbenv で Ruby3.0.0 をインストールする方法

概要

タイトルの通りです
グローバルにインストールしてもいいですがとりあえず使ってみたい場合には rbenv を使ってインストールしましょう

環境

  • macOS 11.1
  • rbenv 1.1.2

準備

  • brew update
  • brew upgrade rbenv
  • brew upgrade ruby-build

インストール

  • rbenv install -l

    => 3.0.0 があることを確認する

  • rbenv install 3.0.0

動作確認

  • rbenv local 3.0.0
  • rbenv which ruby
  • /Users/name/.rbenv/versions/3.0.0/bin/ruby -e "0 => a; puts a"

2021年1月6日水曜日

Error: homebrew-core is a shallow clone.

概要

brew update を実行しようとすると発生するエラーです

環境

  • macOS 11.1
  • Homebrew 2.6.0

解決策

  • git -C "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core" fetch --unshallow
  • git -C "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask" fetch --unshallow

cask も使っている場合はそちらも unshallow する必要があります

解説: unshallow するとどうなるのか

--depth 1 は最新のコミットのみ取得しています
fetch --unshallow するとすべてのコミットを取得するのでデータも大きくなるし時間もかかります


リポジトリが大きい場合に shallow clone はよく使うのですがどうやら Github のトラフィックが大きくなるのですべてのコミットをローカルで持ち差分だけを見るように変更したようです

エラー全文

Error: homebrew-core is a shallow clone. To `brew update` first run: git -C "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core" fetch –unshallow This restriction has been made on GitHub's request because updating shallow clones is an extremely expensive operation due to the tree layout and traffic of Homebrew/homebrew-core. We don't do this for you automatically to avoid repeatedly performing an expensive unshallow operation in CI systems (which should instead be fixed to not use shallow clones). Sorry for the inconvenience!

2021年1月5日火曜日

(Ruby) tilt 超入門

概要

tilt は複数のテンプレートファイルを同じインタフェースで扱うことができる便利ライブラリです
今回は erb を使って簡単な操作方法を紹介します

環境

  • macOS 11.1
  • Ruby 2.7.2p137
    • tilt 2.0.10

インストール

  • bundle init
  • vim Gemfile
gem "tile"
  • bundle install

Getting Started

とりあえず動かしてみます

  • vim sample.erb
<%= @message %>


  • vim test.rb
require 'erb'
require 'tilt'

@message = "hello"
sample = Tilt.new('sample.erb')
output = sample.render(self)
puts output


  • bundle exec ruby test.rb

    => hello


テンプレート側でインスタンス変数を参照する場合は上記のようになります
Ruby スクリプト側でもインスタンス変数を宣言して render する際にインスタンス変数が存在しているクラスのオブジェクトを渡す必要があります

クラス内で使用する

render する際に self や Object.new を渡すケースは少ないのでクラス内で使用する場合は以下のようにします

  • vim test.rb
require 'erb'
require 'tilt'

@message = "hello"

class MyClass
  def initialize
    @sample = Tilt.new('sample.erb')
    @message = "hello in MyClass"
  end

  def render
    output = @sample.render(self)
    puts output
  end
end

cls = MyClass.new
cls.render


  • bundle exec ruby test.rb

    => hello in MyClass

ハッシュを渡す

テンプレートにハッシュを渡すこともできます
参照する場合はハッシュのキーを使って参照します

  • vim sample.erb
<%= message %>


  • vim test.rb
require 'erb'
require 'tilt'

h = {'message': 'hello'}
sample = Tilt.new('sample.erb')
output = sample.render(self, h)
puts output


  • bundle exec ruby test.rb

    => hello

オブジェクトを渡す

テンプレート側で渡されたオブジェクトのメンバを参照してみます

  • vim sample.erb
<%= hello %>


  • vim test.rb
require 'erb'
require 'tilt'

class Message
  def initialize
    @hello = 'hello'
  end

  attr_accessor :hello
end

msg = Message.new
sample = Tilt.new('sample.erb')
output = sample.render(msg)
puts output


  • bundle exec ruby test.rb

    => hello

オブジェクトを渡しているので参照する側はメンバやメソッドを直接参照する感じになります

ブロックを渡す

ブロックを渡してテンプレート側でコールすることもできます
テンプレート側は yield でブロックを受け取りましょう

  • vim sample.erb
<%= yield %>


  • vim test.rb
require 'erb'
require 'tilt'

class Message
  def initialize
    @msg = 'hello'
  end

  def hello
    Proc.new {
      @msg + "!!"
    }
  end
end

msg = Message.new
sample = Tilt.new('sample.erb')
output = sample.render(&msg.hello)
puts output


  • bundle exec ruby test.rb

    => hello!!

参考サイト

2020年12月25日金曜日

Python のソースコードをフォーマットする yapf を使ってみた

概要

yapf は Python のコードを自動フォーマットしてれる静的解析ツールです
今回は簡単なサンプルを使っていろいろとフォーマットしてみました

環境

  • macOS 11.1
  • Python 3.8.5

インストール

  • pipenv install yapf

試すコード

  • vim test.py
test_list = ["Apple", "Orange", "Banana", "Grape", "Strawberry", "Pineapple", "Cherry", "Pear", "Watermelon"]

test_dict = {
    "name": "hawksnowlog",
    "age": 10,
    "langs": ["Ruby", "Swift", "Python", "Golang", "JavaScript", "Java"]
}

test_tuple = (
    test_list,
    test_dict,
    {
        "1": "January",
        "2": "February",
        "3": "March",
        "4": "April",
        "5": "May"
    }
)

実行

  • PYTHONPATH=./ pipenv run yapf test.py

これでフォーマット後のコードが表示されます
上書きする場合は -i オプションを使います

  • PYTHONPATH=./ pipenv run yapf -i test.py

結果

配列など 1 行が長くなる場合には自動で改行されます
dict はなぜか key/value が改行されるようです
tuple に関しては要素を 1 行ごとに書いていたのが修正されてしまいました

test_list = [
    "Apple", "Orange", "Banana", "Grape", "Strawberry", "Pineapple", "Cherry",
    "Pear", "Watermelon"
]

test_dict = {
    "name":
    "hawksnowlog",
    "age":
    10,
    "langs": [
        "Ruby", "Swift", "Python", "Golang", "JavaScript", "Java", "HTML5",
        "CSS"
    ]
}

test_tuple = (test_list, test_dict, {
    "1": "January",
    "2": "February",
    "3": "March",
    "4": "April",
    "5": "May"
})

スタイルを変更してみる

デフォルトでは pep8 が使われているみたいです
google スタイルも使えるので使ってみます
スタイルを変更するには --style=google を指定します
また style は辞書形式で指定することもでき複数のスタイルを適用する場合は辞書形式で指定しましょう

  • PYTHONPATH=./ pipenv run yapf --style=google test.py


test_list = [
    "Apple", "Orange", "Banana", "Grape", "Strawberry", "Pineapple", "Cherry",
    "Pear", "Watermelon"
]

test_dict = {
    "name":
        "hawksnowlog",
    "age":
        10,
    "langs": [
        "Ruby", "Swift", "Python", "Golang", "JavaScript", "Java", "HTML5",
        "CSS"
    ]
}

test_tuple = (test_list, test_dict, {
    "1": "January",
    "2": "February",
    "3": "March",
    "4": "April",
    "5": "May"
})

dict に関しては若干スタイルが変わっていました

yapf を適用しない

特定の行だけ yapf を適用しない方法もあります
#yapf disable というコメントを使います

行末で使った場合はその行だけを適用しない範囲にします
行頭で使った場合は複数行に対して yapf を適用しないことができます
#yapf enable が出現する行までは適用しないようにすることができます

test_list = ["Apple", "Orange", "Banana", "Grape", "Strawberry", "Pineapple", "Cherry", "Pear", "Watermelon"] # yapf: disable

test_dict = {
    "name":
        "hawksnowlog",
    "age":
        10,
    "langs": [
        "Ruby", "Swift", "Python", "Golang", "JavaScript", "Java", "HTML5",
        "CSS"
    ]
}

# yapf: disable
test_tuple = (
    test_list,
    test_dict,
    {
        "1": "January",
        "2": "February",
        "3": "March",
        "4": "April",
        "5": "May"
    }
)
# yapf: enable

1 行の最大文字数を設定する

column_limit を使います
デフォルトでは 80 行以上にならないように改行されます

  • PYTHONPATH=./ pipenv run yapf --style='{based_on_style:google,column_limit:100}' test.py
test_list = [
    "Apple", "Orange", "Banana", "Grape", "Strawberry", "Pineapple", "Cherry", "Pear", "Watermelon"
]

test_dict = {
    "name": "hawksnowlog",
    "age": 10,
    "langs": ["Ruby", "Swift", "Python", "Golang", "JavaScript", "Java", "HTML5", "CSS"]
}

test_tuple = (test_list, test_dict, {
    "1": "January",
    "2": "February",
    "3": "March",
    "4": "April",
    "5": "May"
})

設定ファイルを使う

.style.yapf というファイルを作成するとオプションの指定をファイルに記載することができます

  • vim .style.yapf
[style]
based_on_style = google
column_limit = 100


  • PYTHONPATH=./ pipenv run yapf test.py


これで先程と同じ結果になります

最後に

公式にも記載していますが手動のフォーマットのほうがきれいに見える場合があるのでその場合は disable を積極的に使っていきましょう

参考サイト

2020年12月24日木曜日

Flask-Migrate でカラム名を変更すると既存のカラムのデータがなくなってしまう

概要

flask-migrate を使ってカラム名を変更すると既存のカラムの情報が新規のカラムに引き継がれません
原因はマイグレーションスクリプト内で drop_column -> add_column しているからです
今回はカラム名を変更してもデータをロストせずにマイグレートする方法を紹介します

環境

  • macOS 11.1
  • Python 3.8.5
    • flask 1.1.2
    • flask-migrate 2.5.3

事象の確認

まずは本当にカラム名を変更したら既存のカラムのデータが引き継がれないのか確認します

Flask アプリ

  • vim my_app/__init__.py
from flask import Flask
from flask_migrate import Migrate
from my_app.database import db

app = Flask(__name__)
app.debug = True
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+mysqldb://root:@localhost/test?charset=utf8"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_ECHO'] = False

db.init_app(app)
migrate = Migrate(app, db)

database.py

  • vim my_app/database.py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))
    age = db.Column(db.Integer)

    def __repr__(self):
        return "%s,%s,%i" % (self.id, self.name, self.age)

データベース作成

  • mysql> create database test;

初期マイグレート

  • FLASK_APP=my_app pipenv run flask db init
  • FLASK_APP=my_app pipenv run flask db migrate -m "Initial migration."
  • FLASK_APP=my_app pipenv run flask db upgrade

    作成されるテーブル情報は以下の通りです
mysql> show create table user \G *************************** 1. row *************************** Table: user Create Table: CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(128) COLLATE utf8mb4_general_ci DEFAULT NULL, `age` int DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci 1 row in set (0.00 sec)

テストデータ登録

  • mysql> insert into user values (null, 'hawk', 10);
  • mysql> insert into user values (null, 'snowlog', 20);1
  • mysql> insert into user values (null, 'hawksnowlog', 30);

テーブル構成変更

name カラムを first_name というカラム名に変更してみます

  • vim my_app/database.py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(128))
    age = db.Column(db.Integer)

    def __repr__(self):
        return "%s,%s,%i" % (self.id, self.name, self.age)
  • FLASK_APP=my_app pipenv run flask db migrate -m "A name column to first_name."
  • FLASK_APP=my_app pipenv run flask db upgrade


これで user テーブルを確認すると first_name カラムのデータが NULL になってしまっているのが確認できます

mysql> select * from user; +—-+——+————+ | id | age | first_name | +—-+——+————+ | 1 | 10 | NULL | | 2 | 20 | NULL | | 3 | 30 | NULL | +—-+——+————+

対策

flask db migarte 時に自動生成されるマイグレートスクリプトを修正することで対応してみます

マイグレートスクリプトの編集

まずは migrate するところまで進めましょう
upgrade までしてしまうと適用されてしまうので upgrade はまだしません

  • FLASK_APP=my_app pipenv run flask db migrate -m "A name column to first_name."

そして生成されるマイグレートスクリプトを編集します
リビジョン番号はそのたびに違うので自信の環境に併せて書き換えてください

upgrade と downgrade 関数を以下のように書き換えます
ポイントは alter_column という関数を使ってカラムの情報を書き換えるようにします
また existing_type と type_ を指定しましょう
前者は変更前のカラムのタイプで後者は変更後のカラムのタイプになります
今回はタイプは変更しないので同じものを指定しています


* vim migrations/versions/be9a17e479a6_a_name_column_to_first_name.py

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.alter_column('user', 'name', nullable=True, new_column_name='first_name', type_=sa.String(128), existing_type=sa.String(128))
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.alter_column('user', 'first_name', nullable=True, new_column_name='name', type_=sa.String(128), existing_type=sa.String(128))
    # ### end Alembic commands ###

動作確認: upgrade してみる

書き換えたマイグレートスクリプトを使って upgrade してみます

  • FLASK_APP=my_app pipenv run flask db upgrade

これでテーブルを確認するとちゃんと前のデータが引き継がれていることが確認できると思います

mysql> select * from user; +—-+————-+——+ | id | first_name | age | +—-+————-+——+ | 1 | hawk | 10 | | 2 | snowlog | 20 | | 3 | hawksnowlog | 30 | +—-+————-+——+ 3 rows in set (0.00 sec)



また upgrade 後のテーブル情報は以下のようになっています

mysql> show create table user \G *************************** 1. row *************************** Table: user Create Table: CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT, `first_name` varchar(128) COLLATE utf8mb4_general_ci DEFAULT NULL, `age` int DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci 1 row in set (0.00 sec)

最後に

flask-migrate を愚直に使うと drop_column してデータをロストしかねないのでマイグレートするときは必ず alter_column を使うようにしましょう

参考サイト

2020年12月22日火曜日

Youtube のライブ中のコメントを curl でサクっと取得する方法

概要

Youtube Data API v3 と Youtube Live Streaming API を使います
コメントではなくチャットを取得します
コメントは動画の下に表示される非同期のメッセージでチャットはライブ中に右に表示されるリアルタイムのメッセージになります

当然ですが今回取得する方法はライブ中でなければ取得できないので注意してください

環境

  • macOS 11.1
  • Youtube Data API v3
  • Youtube Live Streaming API

事前準備: Youtube API Key の取得

こちらの方法で取得しましょう
GCP の登録が必須です

事前準備: Youtube Data API v3 の有効化

こちらの方法で有効化してください

Youtube Data API v3 を使って activeLiveChatId を取得する

curl \
  'https://youtube.googleapis.com/youtube/v3/videos?part=liveStreamingDetails&id=xxxxxxxxxxx&key=AIzaxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  --header 'Accept: application/json' \
  --compressed

id はライブ配信中の動画 ID になります
ブラウザアクセスして URL バーに表示される ID になります
レスポンス内の JSON に含まれる activeLiveChatId をメモしておきます

なおライブ配信中でないと activeLiveChatId は含まれないので activeLiveChatId がない場合はライブ配信中になっているか確認しましょう

Youtube Streaming API と activeLiveChatId を使ってチャット情報を取得する

curl \
  'https://youtube.googleapis.com/youtube/v3/liveChat/messages?liveChatId=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&part=authorDetails%2Csnippet&key=AIzaxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  --header 'Accept: application/json' \
  --compressed

part は authorDetails と snippet をカンマ区切りで指定しましょう
これで

トラブルシューティング: quotaExceeded になる場合は

なぜか quota を超えていないのに quotaExceeded になる場合があります
自分は新規でプロジェクトを作成し直してキーを作成しました

おまけ: API Explorer を活用しよう

今回紹介した curl コマンドは API Explorer を使えば簡単に作成できます
curl 以外にも Python や Java のコードも作成できるのでプログラムとして使うこともできます

Youtube Data API v3: Videos: list
https://developers.google.com/youtube/v3/docs/videos/list

Youtube Live Streaming API: LiveChatMessages: list
https://developers.google.com/youtube/v3/live/docs/liveChatMessages/list?hl=en

最後に

コードに落とす前に curl で手動で動作確認しておくとプログラミングするときに流れを理解した状態で書けるので結果的に書きやすくなると思います

SDK があれば SDK を使っても良いと思います

参考サイト

2020年12月21日月曜日

Flask でファイルを配置したらメンテナスモードにする

概要

ファイルを配置するだけでメンテナスモードにできる Flask アプリを試してみました

環境

  • macOS 11.1
  • Python 3.8.5
    • flask 1.1.2

サンプルコード

  • vim app.py
import os
from flask import Flask, abort, make_response

app = Flask(__name__)

@app.before_request
def check_under_maintenance():
    if os.path.exists("maintenance"):
        abort(503)

@app.route('/')
def index():
    return "ok"

@app.errorhandler(503)
def error_503(error):
    return make_response({"code":"server_maintenance_mode"}, 503)

動作確認

  • FLASK_APP=app.py pipenv run flask run

    で起動します

  • curl localhost:5000

    => ok


ファイルを配置するとメンテナンスモードになります
アプリの再起動は不要です

  • touch maintenance
  • curl localhost:5000

    => {"code":"server_maintenance_mode"}

最後に

アプリ層が大量にある場合は辛いので前段にリバースプロキシや LB などを配置してそこでメンテナスモードにするほうが良いと思います

参考サイト