pyenvを使って異なるバージョンのpythonを併用する #python

pythonの最新バージョンで動かないツールがあって、やむなく古いpythonを使うためにpyenvを入れてみた。

インストール

Macなのでbrewで入れる。

$ brew install pyenv

~/.bash_profile環境変数等を設定する。

#pyenv
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
if command -v pyenv 1>/dev/null 2>&1; then
  eval "$(pyenv init -)"
fi

使えるversionを確認する

すでにpythonが入っている場合、systemだけが表示されそう。

$ pyenv versions
* system

使いたいバージョンをインストールする

使いたいバージョンを探す。

3.6系とかならこんな感じ。

$ pyenv install --list | grep 3.6.*
  3.3.6
  3.6.0
  3.6-dev
  3.6.1
  3.6.2
  3.6.3
  3.6.4
  3.6.5
  3.6.6
  activepython-3.6.0

3.6.6をインストールしてみる。

$ pyenv install 3.6.6

インストールが完了すると、使えるVersionが増える。

$ pyenv versions
* system
  3.6.6

使うVersionを切り替える

system(3.7.0)から、3.6.6に切り替える。

$ python -V
Python 3.7.0
$ pyenv global 3.6.6
$ python -V
Python 3.6.6

特定ディレクトリの配下だけ使うバージョンを切り替える

特定のディレクトリ配下でだけ使用するバージョンを変更することも可能。

$ python -V
Python 3.7.0
$ mkdir -p ./hoge
$ cd hoge
$ pyenv local 3.6.6
$ python -V
Python 3.6.6
$ cd ../
$ python -V
Python 3.7.0

pyenv local を作ると .python-version というファイルが作られる。

中身は使用するバージョンが書いてあるだけ。

参考

メソッドを持ったKotlinのEnumをGroovy(Spock)から参照する #groovy #spock #kotlin

概要

  • KotlinはJava同様Enumに振る舞い(メソッド)を持たせることができる
  • Groovyから振る舞いを持ったEnum要素を直接参照するとエラーになる
  • Direct field access operator(@)経由で参照すればOK
  • EnumのValueOfでも参照できる

振る舞いを持たないEnumはうまくいく

enum class Shape {
    CIRCLE,
    TRIANGLE,
    SQUARE,
}

// Enumをフィールドに持つクラス
class Foo(val shape: Shape)
class FooTest extends Specification {
    def "KotlinのEnumを使ったテスト"() {
        given:
        def shape = Shape.CIRCLE

        when:
        def actual = new Foo(shape)

        then:
        actual.shape == Shape.CIRCLE
    }
}

振る舞いをもたせるとエラーになる

// 振る舞いを持ったEnum
enum class Shape {
    CIRCLE {
        override fun sign(): String = "○"
    },
    TRIANGLE {
        override fun sign(): String = "△"
    },
    SQUARE {
        override fun sign(): String = "□"
    };

    abstract fun sign(): String
}

class Foo(val name: String, val shape: Shape)

GroovyRuntimeExceptionが発生してしまう。

groovy.lang.GroovyRuntimeException: Could not find matching constructor for: Foo(java.lang.Class)

どうも振る舞いをもたせるとClassになるようだ。

コンパイル結果を見るとたしかにclassファイルができている。

  • Shape$CIRCLE.class
  • Shape$SQUARE.class
  • Shape$TRIANGLE.class

メソッドも呼び出せない

def "KotlinのEnumメソッドを呼び出すテスト"() {
    expect:
    Shape.CIRCLE.sign() == "○"
}

MissingMethodExceptionが発生してしまう。

Caused by: groovy.lang.MissingMethodException: No signature of method: static Shape$CIRCLE.sign() is applicable for argument types: () values: []

Enumインスタンス経由で呼び出す

Direct field access operator(@)を使って、要素のインスタンスを使うようにするとうまくいきます。

class FooTest extends Specification {
    def "KotlinのEnumを使ったテスト"() {
        given:
        def shape = Shape.@CIRCLE

        when:
        def actual = new Foo(shape)

        then:
        actual.shape == Shape.@CIRCLE
    }

    def "KotlinのEnumメソッドを呼び出すテスト"() {
        expect:
        Shape.@CIRCLE.sign() == "○"
    }
}

valueOf経由で呼び出す

valueOfを使うようにしてもOKです。

class FooTest extends Specification {
    def "KotlinのEnumを使ったテスト"() {
        given:
        def shape = Shape.valueOf('CIRCLE')

        when:
        def actual = new Foo(shape)

        then:
        actual.shape == Shape.valueOf('CIRCLE')
    }

    def "KotlinのEnumメソッドを呼び出すテスト"() {
        expect:
        Shape.valueOf('CIRCLE').sign() == "○"
    }
}

参考

MyBatisのMapKeyはValueがクラスじゃないといけないらしい #mybatis

MyBatisでリストで取得するのは簡単

@SelectProvider(type = HogeSqlBuilder::class, method = "selectList")
override fun findAll(): List<Hoge>

MapKey を使うとSelect結果をいい感じにMapにしてくれるMyBatis。

この場合、Hoge.id がKey、ValueHoge のMapにして返してくれる。

@MapKey("id")
@SelectProvider(type = HogeSqlBuilder::class, method = "selectMap")
override fun findAll(): Map<Long, Hoge>

しかしValueはクラスじゃないといけない

たとえば値は Hoge.name だけあればいいような場合に、ValueをStringにしたいのだがそれはできないようだ。

// これはできない
@MapKey("id")
@SelectProvider(type = HogeSqlBuilder::class, method = "selectNameMap")
override fun findAll(): List<id, String>

下記のような例外が発生する。 Stringに対してアクセサを呼び出そうとしてエラーになるようだ。

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'id' in 'class java.lang.String'

対応策

おとなしくクラスを用意するか、リストで受けたあとに自分で詰め替えるとかでしょうね。。

参考

stackoverflow.com qiita.com

KotlinでMyBatisのSqlProviderを使う #kotlin #mybatis

概要

MyBatisのSQLXMLに書いたりアノテーションに文字列で渡す方法があるが、SqlProviderを使う方法を試してみた。

これが一番好きかもしれない。

ちなみにSpringBoot(mybatis-spring-boot-starter)を利用している。

SqlProviderの使い方

クエリの種類に応じてProviderアノテーションが用意されている。

今回はInsertを例にする

  • @InsertProvider
  • @UpdateProvider
  • @DeleteProvider
  • @SelectProvider
@Mapper
interface FooRepository {
    // メソッドにProviderのアノテーション付与
    // 実SQLを提供するクラスとメソッド名を渡す
    @InsertProvider(type = MySqlBuilder::class, method = "insert")
    fun save(foo: Foo)

    // SQLの提供クラス
    // インナークラスにしているが、アウタークラスでもOK
    class MySqlBuilder {
        companion object {
            // MyBatis側で利用されるので、JvmStaticアノテーションを付与
            @JvmStatic
            fun insert(foo: Foo): String = foo.run {
                // SQLの生成はMyBatisのSQLビルダークラスを使っている
                // が、極端な話文字列を返せばいいので究極好き勝手にSQLを書ける
                SQL().INSERT_INTO("foo")
                        .VALUES("id", "#{id}")
                        .VALUES("name", "#{name}")
                        .VALUES("age", "#{age}")
                        .toString()
            }
        }
    }
}

動的なSQLの生成

MyBatisのSQLビルダークラスは動的にSQLの組み立ても可能である。

SqlProvider側でインターフェースと同じ引数を受け取れるので、必要に応じて処理を埋め込む。

SQLビルダーは無名クラスにして組み立てるようにする。

class MySqlBuilder {
    companion object {
        @JvmStatic
        fun insert(careType: CareType): String = careType.run {
            // 動的に組み立てる場合は無名クラスにする
            object : SQL() {
                init {
                    INSERT_INTO("foo")
                    VALUES("id", "#{id}")
                    VALUES("name", "#{name}")

                    // 処理を埋め込むことも可能
                    if (name.isEmpty()) {
                        VALUES("name", "'default name'")
                    }

                    VALUES("age", "#{age}")
                }
            }.toString()
        }
    }
}

参考

Groovy(Spock)からKotlinのCompanion Objectを呼び出す #groovy #spock #kotlin

概要

最近はJavaではなくKotlinを書いていますが、Spockが好きすぎるのでテストはGroovyで書いたりします。

KotlinのCompanion Objectをテストするときに、Groovy側から呼び出す方法です。

Companion Object

Kotlinではクラスに静的なメソッドを定義する時はCompanion Objectを使います。

Javaのstaticみたいなものと自分は理解しています。

class Sample {
    companion object {
        // 大文字にするメソッド
        fun toUpper(s: String): String = s.toUpperCase()
    }
}

こうするとKotlin側では Sample.toUpper("hoge") のように呼び出せるようになります。

Groovy側での呼び出し方

GroovyではCompanionのメソッドを呼び出すには Direct field access operator を使います。

class SampleSpec extends Specification {
    def 'hogeが大文字HOGEになる'() {
        when:
        def actual = Sample.@Companion.toUpper('hoge')

        then:
        actual == 'HOGE'
    }
}

Groovyからの呼び出しにはCompanionオブジェクトを経由する必要があり、なおかつダイレクトアクセスである必要があります。

ダイレクトアクセスする方法は、アクセスするフィールドの前に @ を付けるだけです。

Companionを経由しない Sample.toUpper('hoge') や、通常のフィールドアクセス Sample.Companion.toUpper('hoge') では MissingMethodException が発生します。

ダイレクトアクセスと通常のフィールドアクセスの違いですが、Groovyのフィールドアクセスは暗黙的にGetterが呼ばれるのに対し、ダイレクトアクセスの場合Getterは呼ばれずフィールドの直アクセスになります。

CompanionオブジェクトにはGetterがないので、直アクセスする必要があるのかなと想像しています。

JvmStatic

JvmStaticアノテーションを使えば、Groovyからでも直接メソッドを呼び出せるようになります。

@JvmStatic
fun toUpper(s: String): String = s.toUpperCase()

ただ、テストコードのためにプロダクションコードに手を入れることになるので今回のようなケースではおすすめしません。

参考

Problems about accessing Kotlin companion object in Groovy? - Stack Overflow The Apache Groovy programming language - Operators

Nuxtで画面に横断的にMiddlewareを適用する #nuxtjs #vuejs

概要

Nuxtにはミドルウェアという機構がある。

ミドルウェアを使うと、ページがレンダリングされる前に、実行されるカスタム関数を定義できる。

複数のページでミドルウェアを実行するには3つ方法がある。

  1. nuxt.config.js でrouterに読み込ませる
  2. layoutでミドルウェアを読み込む
  3. layoutを使用しているページ全てで読み込まれる
  4. 各ページでミドルウェアを読み込む

nuxt.config.js で読み込まれる方法を使うと、すべての画面遷移時にミドルウェアが実行されるようになる。
(ブラウザの更新ボタンによるリロード含む)

この方法を試してみる。

なおMiddlewareは nuxt.config.js → レイアウト → page の順で実行されるそうだ。

Middlewareの作成

単純にログだけ吐くミドルウェアmiddleware/sample.js を作成する

export default function({ route }) {
  console.log('サンプルミドルウェア', route.path)
}

routerでの読み込み

nuxt.config.js で作成したミドルウェアを読み込む。

module.exports = {
  router: {
    middleware: 'sample'
  }  
}

これで、全画面遷移時にパスをログ出力するようになる。

いろいろやってみる

せっかくなので middleware/sample.js 修正して色々試してみる。

  • パスの値を判定
  • Vuexストアの値を操作
  • ストアの値を見てリダイレクト
export default function({ route, redirect, store }) {
  // ログ出力
  console.log('サンプルミドルウェア', route.path)

  // 現在のパスを取得
  const currentPath = route.path
  if (currentPath !== '/hoge') {
    // hogeページでは何もしない
    return
  }

  // storeに数値を保持してカウントアップする
  store.dispatch('count/countUp')
  const count = store.getters['count/count']
  if (count % 2 === 0) {
    // storeの値が偶数のときだけhogeページは表示できる
    console.log('hogeページに行けるのはstoreが偶数のときだけ', count)
    // fugaページにリダイレクト
    redirect('/fuga')
  }
}

この例で使用しているVuexストアは store/count.js で下記の通り。

export const state = () => ({
  count: 0,
})

export const mutations = {
  increment(state) {
    state.count++
  },
}

export const getters = {
  count(state) {
    return state.count
  },
}

export const actions = {
  countUp(context) {
    context.commit('increment')
  },
}

参考

ja.nuxtjs.org ja.nuxtjs.org

SpringBootで独自例外を作ってControllerAdviceでハンドリングする

概要

  • 独自RuntimeExceptionを作る
  • 独自例外はControllerAdviceで横断的にハンドリングする
  • テストを書く

前提

  • Kotlin
  • RestControllerを想定
    • ゆえにRestControllerAdviceを使う
  • テストはSpock

独自例外の作成

  • RuntimeExceptionを作成
  • コンストラクタ引数はnullを許可したくなかったので、superのコンストラクタにそれぞれ渡している
class MyException : RuntimeException {
    constructor(message: String, cause: Throwable) : super(message, cause)
    constructor(message: String) : super(message)
    constructor(cause: Throwable) : super(cause)
    constructor() : super()
}

ハンドラークラスの作成

  • クラスに @RestControllerAdvice を付与
  • ハンドルするメソッドに @ExceptionHandler を付与
  • ハンドルするメソッドに @ResponseStatus を付与
    • 指定したHTTPステータスがレスポンスに設定される
    • この例だとINTERNAL_SERVER_ERROR(500)
  • ハンドルするメソッドは対象の例外を引数で受け取る
    • 返り値の型がレスポンスボディに設定される
    • デフォルトだとRestControllerならJSONになると思う
@RestControllerAdvice
class MyExceptionHandler {
    @ExceptionHandler(MyException::class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    fun handleMyException(e: MyException): Map<String, String> = mapOf("message" to "Error!!")
}

テストの作成

  • クラスに @SpringBootTest@AutoConfigureMockMvc を付与
    • MockMvcをAutowiredしてテストする
  • スタブコントローラを作成する
    • CotrollerAdviceはコントローラに横断的に適用されるので、個別のコントローラに対しては(ユニット)テストしない
    • 常にテスト対象の例外をthrowするようなダミーのエンドポイントを用意する
  • テストクラスに @ActiveProfiles を、スタブに @Profile を付与する
    • 必須ではない
    • スタブのエンドポイントが重複した場合を避けるため
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("myExceptionHandlerSpec")
class MyExceptionHandlerSpec extends Specification {
    @Autowired
    MockMvc mockMvc

    def 'MyExceptionが発生した場合はHTTP:500になる'() {
        expect:
        mockMvc.perform(MockMvcRequestBuilders.get('/mock'))
                .andExpect(MockMvcResultMatchers.status().is(HttpStatus.INTERNAL_SERVER_ERROR.value()))
                .andExpect(MockMvcResultMatchers.jsonPath("\$.message").value("Error!!"))
    }
}

// ControllerAdviceをテストするためのStub
@RestController
@RequestMapping("/mock")
@Profile("exceptionHandlerSpec")
class StubController {
    @GetMapping
    def test() {
        throw new MyException()
    }
}

参考