Pipenv から Poetry への乗り換え

乗り換えるリポジトリ

GitHub - kk6/aeroplast: Transparent PNG conversion (Mainly for Twitter)

twitterにアップロードしたPNGがJPGに強制変換されて見栄えが悪いという問題がある。それを回避するために画像の四隅どこかに1pxの透過ドットを打ち込むという方法がある。いちいちペイントソフトでそれを毎回やるのが面倒なのでコマンドラインで画像のPath渡したら変換してくれるコマンドラインツールを以前作った。こいつをPipenvで管理しているので、Poetryへの乗り換えを試してみる。

Poetry については数日中にもうちょっと突っ込んだ記事書きます。

pyproject.toml の生成

init コマンドで対話形式で情報を入力していくだけ。

$ poetry init

This command will guide you through creating your pyproject.toml config.

Package name [aeroplast]:
Version [0.1.0]:
Description []: Transparent PNG conversion.
Author [kk6 <hiro.ashiya@gmail.com>, n to skip]:
License []: MIT
Compatible Python versions [^3.7]:

Would you like to define your dependencies (require) interactively? (yes/no) [yes] no



Would you like to define your dev dependencies (require-dev) interactively (yes/no) [yes] no

Generated file

[tool.poetry]
name = "aeroplast"
version = "0.1.0"
description = "Transparent PNG conversion."
authors = ["kk6 <hiro.ashiya@gmail.com>"]
license = "MIT"

[tool.poetry.dependencies]
python = "^3.7"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"


Do you confirm generation? (yes/no) [yes] yes

これで pyproject.toml が生成される。

依存パッケージのインストール

続いて Pipfile を見ながらパッケージを poetry add で追加する。

(追記) だるいのでツール作った

kk6.hateblo.jp

(追記おわり)

$ poetry add pillow fire logzero
Using version ^5.3 for pillow
Using version ^0.1.3 for fire
Using version ^1.5 for logzero

Updating dependencies
Resolving dependencies... (3.2s)


Package operations: 0 installs, 1 update, 0 removals

Writing lock file

  - Updating six (1.11.0 -> 1.12.0)

インストールしたパッケージは tool.poetry.dependencies セクションに追記されている。

[tool.poetry]
name = "aeroplast"
version = "0.1.0"
description = "Transparent PNG conversion."
authors = ["kk6 <hiro.ashiya@gmail.com>"]
license = "MIT"

[tool.poetry.dependencies]
python = "^3.7"
pillow = "^5.3"
fire = "^0.1.3"
logzero = "^1.5"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

と同時に poetry.lock ファイルが生成されている。依存関係はこちらに記述されている。

pytestのインストールを忘れていた。

$ poetry add -D pytest
Using version ^4.0 for pytest

Updating dependencies
Resolving dependencies... (0.5s)


Package operations: 0 installs, 1 update, 0 removals

Writing lock file

  - Updating pytest (4.0.1 -> 4.0.2)

今回は -D オプションを付けてインストールしたので、pyproject.toml の tool.poetry.dev-dependencies セクションの方に追記されている。

[tool.poetry.dependencies]
python = "^3.7"
pillow = "^5.3"
fire = "^0.1.3"
logzero = "^1.5"

[tool.poetry.dev-dependencies]
pytest = "^4.0"

コマンドラインツールとして実行できるようにする

PipenvのときはPipfileに以下のように記述して使っていた。

[scripts]
cli = "pipenv run python -m aeroplast"

こんな感じで使っていた

$ pipenv run cli convert ...

Potryの場合は pyproject.tomltool.poetry.scripts セクションに記述する。

[tool.poetry.scripts]
aeroplast = "aeroplast.cli:main"

Poetryの便利なところは、パッケージをビルドした際にこの設定がそのまま setup.pyconsole_scripts として記述される。ビルドする時にコマンドとして実行できるようにあらためてsetup.pyを書く必要がない。

...
entry_points = \
{'console_scripts': ['aeroplast = aeroplast.cli:main']}
...

とまあそれはさておき poetry run aeroplast で実行できるようになった。

$ poetry run aeroplast
Type:        CLI
String form: <aeroplast.cli.CLI object at 0x100f6e978>
Docstring:   Transparent PNG conversion

Usage:       aeroplast
             aeroplast convert
             aeroplast resize

また、poetry developpip install -e . するのと同様に開発途中のパッケージをvenvにインストールできる。インストールした上で poetry shell でvenvをactivateすると aeroplast コマンドのみで実行可能になる。

$ poetry shell
Spawning shell within /Users/kk6/PycharmProjects/aeroplast/.venv
Welcome to fish, the friendly interactive shell
$ poetry develop
develop is deprecated use install instead.

Installing dependencies from lock file

Nothing to install or update

  - Installing aeroplast (0.1.0)
$ aeroplast
Type:        CLI
String form: <aeroplast.cli.CLI object at 0x107dfa908>
Docstring:   Transparent PNG conversion

Usage:       aeroplast
             aeroplast convert
             aeroplast resize
<aeroplast.cli.CLI object at 0x107dfa908>

以上で移行完了。わりと簡単。しかも今すぐにでも以下の2行を実行するだけでPyPIに公開可能な状態となっている。

$ poetry build
$ poetry publish

Poetry便利。

またPyenv環境に戻してみた

動機

ライブラリ開発とかしてるとどうしてもhomebrewだけではつらくなる(python3.6が入ってる状態でbrew upgradeしてpython3.7に上がると、brew cleanup叩いた時点でpython3.6が消される)

公式からバージョンごとにインストーラー落としてきて入れるというのも考えたが、pipenvにはpyenvと連携するオプションが用意されてるし、CircleCIも内部でpyenv使ってるしpoetryの作者もとあるissueに対する返信で「pyenvを使うことを強く推奨する」なんて言ってたのでもう海外でもpyenvが市民権得たのかなーとか思った次第。じゃあもうpyenvでいいやーと。

事前確認

brewで入れているPython

  • 2.7.10
  • 3.7.0

pyenvのインストール

~ $ brew install pyenv
==> Installing dependencies for pyenv: autoconf
==> Installing pyenv dependency: autoconf
==> Downloading https://homebrew.bintray.com/bottles/autoconf-2.69.mojave.bottle.4.tar.gz
######################################################################## 100.0%
==> Pouring autoconf-2.69.mojave.bottle.4.tar.gz
==> Caveats
Emacs Lisp files have been installed to:
  /usr/local/share/emacs/site-lisp/autoconf
==> Summary
🍺  /usr/local/Cellar/autoconf/2.69: 71 files, 3.0MB
==> Installing pyenv
==> Downloading https://homebrew.bintray.com/bottles/pyenv-1.2.8.mojave.bottle.tar.gz
######################################################################## 100.0%
==> Pouring pyenv-1.2.8.mojave.bottle.tar.gz
🍺  /usr/local/Cellar/pyenv/1.2.8: 612 files, 2.4MB
==> Caveats
==> autoconf
Emacs Lisp files have been installed to:
  /usr/local/share/emacs/site-lisp/autoconf

pyenvを使えるようにするために、bash等の場合は.bash_profile等にパスの追記等が必要だが、自分はfish shell & fisherman を使っているので、このへんはプラグインを入れるだけでOK.

~ $ fisher pyenv
Installing 1 plugin/s
OK Fetch pyenv github.com/fisherman/pyenv
Done in 2s 26ms

python3.6をインストールしてみるも失敗する

~ $ pyenv install 3.6.7
python-build: use openssl from homebrew
python-build: use readline from homebrew
Downloading Python-3.6.7.tar.xz...
-> https://www.python.org/ftp/python/3.6.7/Python-3.6.7.tar.xz
Installing Python-3.6.7...
python-build: use readline from homebrew

BUILD FAILED (OS X 10.14 using python-build 20180424)

Inspect or clean up the working tree at /var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189
Results logged to /var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189.log

Last 10 log lines:
  File "/private/var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189/Python-3.6.7/Lib/ensurepip/__main__.py", line 5, in <module>
    sys.exit(ensurepip._main())
  File "/private/var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189/Python-3.6.7/Lib/ensurepip/__init__.py", line 204, in _main
    default_pip=args.default_pip,
  File "/private/var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189/Python-3.6.7/Lib/ensurepip/__init__.py", line 117, in _bootstrap
    return _run_pip(args + [p[0] for p in _PROJECTS], additional_paths)
  File "/private/var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189/Python-3.6.7/Lib/ensurepip/__init__.py", line 27, in _run_pip
    import pip._internal
zipimport.ZipImportError: can't decompress data; zlib not available
make: *** [install] Error 1

最近Mojaveにアップグレードしたのが原因だった

mac os Mojave にアップグレードしたら bundle install が失敗する事象を解決した - Qiita

~ $ open /Library/Developer/CommandLineTools/Packages/
The file /Library/Developer/CommandLineTools/Packages does not exist.

上記リンクのQiita記事を参考にopenしてみるも、無いって言われる。記事の一番下の参照元リンク先みるとxcode-selectが必要っぽい。

~ $ xcode-select --install
xcode-select: note: install requested for command line developer tools

インストール完了後、再びopenコマンド実行すると今度はちゃんとフォルダが開くので、macOS_SDK_headers_for_macOS_10.14.pkgをダブルクリックでインストールする。

再びpyenvでpython3.6をインストールすると、今度は無事成功。

それぞれのPythonを呼び出せるようにする

simlink作成(非推奨)

tox等で使えるよう、python3.6コマンドで呼び出せるようにしたい。何も考えずに最初はsimlinkで対応した。

$ cd /usr/local/bin
$ ln -s ~/.pyenv/versions/3.6.7/bin/python3.6 .

でも後で気づいたが公式のやりかたがあった

pyenv global コマンド

~ $ pyenv versions
* system (set by /Users/kk6/.pyenv/version)
  2.7.15
  3.4.9
  3.5.6
  3.6.7

上記のような状態で、とりあえず3系全部動くようにしたいとする。pyenv globalに使いたいバージョンを列挙すればいいだけ。

~ $ pyenv global 3.4.9 3.5.6 3.6.7
~ $ pyenv versions
  system
  2.7.15
* 3.4.9 (set by /Users/kk6/.pyenv/version)
* 3.5.6 (set by /Users/kk6/.pyenv/version)
* 3.6.7 (set by /Users/kk6/.pyenv/version)
~ $ python3.5
Python 3.5.6 (default, Nov  7 2018, 06:07:53)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

これだけ。いちいちsimlink張るより楽。ただ、simlinkのほうが起動は速い。

【追記】

pyenv global するとき、バージョンが新しいものから列挙してやらないとpython3コマンドが3.4を見に行ったりするので以下のようにする。

$ pyenv global 3.7.1 3.6.7 3.5.6 3.4.9 2.7.15

brewで入れたpython3.7をどうするか問題

brewpythonとpyenvのpython混在とか地獄を見そうなのでbrewpythonを消そうと思ったが…

~ $ brew uninstall python3
Error: Refusing to uninstall /usr/local/Cellar/python/3.7.0
because it is required by thefuck and vim, which are currently installed.
You can override this and force removal with:
  brew uninstall --ignore-dependencies python3

python3に依存してるやつらがいるから消せないとのこと。

thefuckに関してはわりとどうでもいい。vimもいい機会だしneovimにするのもありかもしれない。pyenvのpython3.7インストールしたら問題無さそうな気もするし、問題があればpythonインストール後にこいつらをbrewでインストールし直すのでもいけそうではある。

brewpython消してpyenvで入れ直した

やっぱり3.7だけbrewで他のバージョンがpyenvなの気持ち悪すぎるのでpyenvに統一する。

$ brew uninstall thefuck vim python3したあと$ pyenv install 3.7.1した。

で、既存のvenvが壊れるのは想定内で、pipenvが壊れるのも想定内(brewのpython3.7で入れていた)。ただ、pipも動かないとは思ってなかった。

$ pipenv
Failed to execute process '/Users/kk6/.local/bin/pipenv'. Reason:
The file '/Users/kk6/.local/bin/pipenv' specified the interpreter '/usr/local/opt/python/bin/python3.7', which is not an executable command.
$ pip
Failed to execute process '/usr/local/bin/pip'. Reason:
The file '/usr/local/bin/pip' specified the interpreter '/usr/local/opt/python/bin/python3.7', which is not an executable command.
$ which pip
/usr/local/bin/pip

エラーメッセージ見れば分かる通り、/usr/local/bin/にゴミが残っている。ついでにpip3も消しておく。

$ which pip
/Users/kk6/.pyenv/shims/pip
$ pip

Usage:
  pip <command> [options]

Commands:
  install                     Install packages.
  download                    Download packages.
  ...

これで動くようになった。

あとはpipenvを入れ直す。

$ pip install --user pipenv

以上でひとまずpyenvへの移行完了です。

RSCSS という CSS 設計について

(追記)

実践例あった

http://ricostacruz.com/rscss/index.html

追記終わり。


https://github.com/rstacruz/rscss

しばらく CSS とか追ってなかったので、触るにあたって「むやみにCSS書いてたら後で確実に死ぬし、そういえばなんかOOCSSとかあったな」と思っていろいろ調べてたら OOCSS の他にも SMACSS とか BEM とか SuitCSS とか FLOCSS とかなんかいろいろ出てきて大変でした。たしか SMACSS くらいまでは記憶があるんだけど…。

で、どうもどれもしっくり来ないのでさらに調べてみると RSCSS というものを発見。「フレームワークじゃなくてあくまでもアイデア集だよ」ってことらしく、ルールガチガチでもないしなんだかよさ気。

で、RSCSS の README を読みながらメモしたんでせっかくだし公開します。ほぼただ訳しただけなんですが…。英語あまり得意ではないので間違いなど指摘いただけると助かります。

Components

検索フォームのようなひとかたまりをひとつのコンポーネントとして考える

Components の命名規則

Components は ダッシュで区切られた 少なくとも2つの単語 からなる。

  • Like ボタン ( .like-button )
  • 検索フォーム ( .search-form )
  • ニュースカード ( .article-card )

Elements

Elements とは Component を構成する内部要素。

Elements の命名規則

それぞれの Component は通常、複数の Element を持つ。 Element の名前は 1単語 にする。

  .search-form {
    > .field { /* ... */ }
    > .action { /* ... */ }
  }

Element のセレクタ

可能な限り子セレクタ > を使う。Componentのネスト防止になるし、子孫セレクタよりパフォーマンスがいい。

  .article-card {
    .title     { /* これでもいいけど */ }
    > .author  { /* ✓ こっちのほうがいい */ }
  }

複数の単語が使いたい

2つ以上の単語からなる名前をつけたい場合は、ダッシュやアンダースコアを使わないで単純に連結する。

  .profile-box {
    > .firstname { /* ... */ }
    > .lastname { /* ... */ }
    > .avatar { /* ... */ }
  }

タグセレクタを避ける

可能な限りクラス名を使用する。タグセレクタはよいものだが、若干のパフォーマンス上のペナルティがあり、また説明的ではないかもしれない。

  .article-card {
    > h3    { /* ✗ これは避ける */ }
    > .name { /* ✓ こうするほうがいい */ }
  }

Variant

Component も Element もそれぞれ variant を持つことができる。

Variant の命名

variant のためのクラス名にはダッシュ ( - ) を prefix としてつける

  .like-button {
    &.-wide { /* ... */ }
    &.-short { /* ... */ }
    &.-disabled { /* ... */ }
  }

Element の variant

Element もまた variant をもつことができる

  .shopping-card {
    > .title { /* ... */ }
    > .title.-small { /* ... */ }
  }

ダッシュプレフィックス

ダッシュは variant のプレフィックス(接頭辞)として使用できる

  • Element との曖昧さ防止
  • CSSのクラス名はアルファベットそして _ または - で始めることができる
  • ダッシュはアンダースコアよりタイプしやすい
  • it kind of resembles switches in UNIX commands ( gcc -O2 -Wall -emit-last)

ダッシュやアンダースコアではじまるクラス名ってOKなの知らなかった)

In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9] and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_); they cannot start with a digit, two hyphens, or a hyphen followed by a digit. Identifiers can also contain escaped characters and any ISO 10646 character as a numeric code (see next item). For instance, the identifier "B&W?" may be written as "B&W\?" or "B\26 W\3F".

http://www.w3.org/TR/CSS21/syndata.html#characters

ネストした Component

<div class='article-link'>
  <div class='vote-box'>
    ...
  </div>
  <h3 class='title'>...</h3>
  <p class='meta'>...</p>
</div>

時には Component をネストする必要がでてくる

ネストした Component の Variant

Component は何らかの方法で他のComponentにネストする必要が生じるかもしれない。ネストしたコンポーネントを、それを内包するコンポーネントに突っ込むような変更は避ける。

.article-header {
  > .vote-box > .up { /* ✗ これは避ける */ }
}

かわりに、ネストした Component に Variant を追加し、それを含む component からそれを適用するのが好ましい。

<div class='article-header'>
  <div class='vote-box -highlight'>
    ...
  </div>
  ...
</div>
.vote-box {
  &.-highlight > .up { /* ... */ }
}

ネストした Component の単純化

しばしば、ネストしたコンポーネントマークアップが汚くなる

<div class='search-form'>
  <input class='input' type='text'>
  <button class='search-button -red -large'></button>
</div>

CSSプリプロセッサーの @extend によって単純化することができる

<div class='search-form'>
  <input class='input' type='text'>
  <button class='submit'></button>
</div>
.search-form {
  > .submit {
    @extend .search-button;
    @extend .search-button.-red;
    @extend .search-button.-large;
  }
}

Layout

ポジションに関するプロパティを避ける

Component は異なるコンテキストで再利用できるべきなので、以下のようなプロパティを書くのを避ける

  • Positioning (position, top, left, right, bottom)
  • Floats (float, clear)
  • Margins (margin)
  • Dimensions (width, height) *

固定寸法

アバターやロゴのような width/height が固定される要素については例外とする

親要素でポジションを定義する

これらを定義したいなら、それらが所属するコンテキストに定義する。下記の例で言うと、 list コンポーネントに適用するのであって、 .article-card 自身に定義するのではない。

  .article-list {
    & {
      @include clearfix;
    }

    > .article-card {
      width: 33.3%;
      float: left;
    }
  }

  .article-card {
    & { /* ... */ }
    > .image { /* ... */ }
    > .title { /* ... */ }
    > .category { /* ... */ }
  }

Helpers

._unmargin { margin: 0 !important; }
._center { text-align: center !important; }
._pull-left { float: left !important; }
._pull-right { float: right !important; }

汎用クラスはアンダースコアで始まる名前で別ファイルに置かれ、値を上書きする。通常、 !important でタグ付けされる。慎重に使うこと。

Helper の命名

アンダースコアをクラス名のプレフィックスとして付ける。これは component に定義された modifier と容易に見分けることができる。アンダースコアはちょっと不格好で内部的な副作用がある。Helperの使い過ぎには嫌気が差すだろう。

  <div class='order-graphs -slim _unmargin'>
  </div>

Helper の整理

helpers というひとつのファイルに収める。複数ファイルに分割することもできるが、たくさんの helper を最小限に維持するのが好ましい。

CSS structure

ファイル単位でひとつの Component

それぞれの Component はそれぞれひとつのファイルに収める

  /* css/components/search-form.scss */
  .search-form {
    > .button { /* ... */ }
    > .field { /* ... */ }
    > .label { /* ... */ }

    // variants
    &.-small { /* ... */ }
    &.-wide { /* ... */ }
  }

Use glob matching

 @import 'components/*';

過剰なネストを避ける

できるだけ1階層のネストにとどめる。ネストし過ぎは簡単に迷子になる。

  /* ✗ Avoid: 3 levels of nesting */
  .image-frame {
    > .description {
      /* ... */

      > .icon {
        /* ... */
      }
    }
  }

  /* ✓ Better: 2 levels */
  .image-frame {
    > .description { /* ... */ }
    > .description > .icon { /* ... */ }
  }

落とし穴

ネストした Component で死亡

ネストした Component が似たような名前の Element を持つ場合に気をつける。

<article class='article-link'>
 <div class='vote-box'>
    <button class='up'></button>
    <button class='down'></button>
    <span class='count'>4</span>
  </div>

  <h3 class='title'>Article title</h3>
  <p class='count'>3 votes</p>
</article>
.article-link {
  > .title { /* ... */ }
  > .count { /* ... (!!!) */ }
}

.vote-box {
  > .up { /* ... */ }
  > .down { /* ... */ }
  > .count { /* ... */ }
}

この場合、 .article-link > .count> セレクタを持っておらず、おそらく .vote-box .count に適用される。これは子セレクタが好ましい理由のひとつ。

懸念事項

  • ダッシュとか最悪: このルールを無視して普通の単語をつかってもいい。ただ、 Component - Element - Variant という考えは忘れないで欲しい

  • 2単語なんて思いつかない: alert のような、いくつかの Component は表現上の理由から1単語であることを要する。これらはサフィックス(接尾辞)を使用することでブロック要素だということをより明確にすることを検討したほうがよい。

  • .alert-box

  • .alert-card
  • .alert-block

Or for inlines:

  • .link-button
  • .link-span

その他のリソース

その他のソリューション

BEM

BEM は素晴らしいけどシンタックスがちょっとキモいという人もいると思う。RSCSSはBEMとはシンタックスが違うだけで、慣習にはほぼ従っている。

<!-- BEM -->
<form class='site-search site-search--full'>
  <input  class='site-search__field' type='text'>
  <button class='site-search__button'></button>
</form>
<!-- rscss -->
<form class='site-search -full'>
  <input  class='field' type='text'>
  <button class='button'></button>
</form>

用語

同様のコンセプトはその他のCSS設計にもみられる

RSCSS BEM SMACSS
Component Block Module
Element Element Sub-Component
Layout ? Layout
Variant Modifier Sub-Module & State

まとめ