Railsでelasticsearchとsearchkick(retire)とacts-as-taggable-onでラクをしてfacetsした話
タグを付けてfacetsするところまで。コードは全く書きません。
便利でいいですね。
gem "searchkick" # full text search gem 'acts-as-taggable-on' # For tags
インストール後migration実行
$ rake acts_as_taggable_on_engine:install:migrations
$ rake db:migrate
モデル定義
class Product < ActiveRecord::Base searchkick # For full text search powerd by elasticsearch. acts_as_taggable # Alias for acts_as_taggable_on :tags # for searchkick reindex data. # def search_data { hoge: hoge, homu: homu, home: home, tags: tag_list # gen by acts_as_taggable_on. } end end
Productにデータをテキトーに入れて, elasticsearchのindexを生成し直します(elasticsearchは事前に立ち上げておいてください)
Product.reindex
facetsしてみます
4.0.2@2.1.0 (main)> Product.search('*', limit: 0, fields: [:tags], facets: {tags: {limit: 5}}).facets Search (45.5ms) {"query":{"match_all":{}},"size":0,"from":0,"facets":{"tags":{"terms":{"field":"tags","size":155}}},"fields":[]} => {"tags"=> {"_type"=>"terms", "missing"=>0, "total"=>3550, "other"=>2577, "terms"=> [{"term"=>"アメリカ", "count"=>269}, {"term"=>"イギリス", "count"=>215}, {"term"=>"日本", "count"=>182}, {"term"=>"インド", "count"=>180}, {"term"=>"ブラジル", "count"=>127}]}}
簡単すぎわらえない
試しに ansible(アンシボー) したら簡単だった
Playbookは全て自作したのですがそれでも初学から1日で終わった。なるほど覚えること少なくてハッヤイ
(同じことやるのに大体ですがChefだと3日かかった記憶があります)
これだけ作るのが早いくて、これくらいのレベルのplaybookだと汎用性考えなくても良いかもしれないと思いましたー。
良かったこと
- 一応会社でPythonistaしてますが。Ansibleにはそんなの必要じゃなかった
面倒くさいなって思ったこと
- when, failed_when: 関数とかイレサシテクレヨ-、registerするのメンドクサイヨ-
作ったもの
実行ファイルはこんな感じにしています。 (hosts, ssh_config等は設定済み)
--- - name: 何か色々インストールして設定ファイル書き換えるよー vars_files: - vars/main.yml hosts: vagrant sudo: yes roles: - common - monit - varnish - nginx - postgresql - elasticsearch - addusers - rbenv
以前作ったもの
参考
ansibleに関するikeikeikeikeのはてなブックマーク
追記
ファイルの上書きをやめて lineinfile を使うようにした
use lineinfile module · 333b288 · ikeikeikeike/ansible-playbooks · GitHub
Codeigniterで保存されているパスワードを復号化してDjangoに移行してみよう
いきなりですが
こちらのサイトによると最近流行りのPHPフレームワークTOP2は Laravel, Phalcon なんだとか
http://www.sitepoint.com/best-php-frameworks-2014/
多分昨今のPHP界隈は Codeigniter から Laravel, Phalcon あたりに旧システムを移植する話がどんどん増えてきてる頃合い(良く分かってないです)
せっかく移行するなら言語を変えたいと思ってる人もいてもいいはずですよね。
んでCodeigniterのパスワードは復号化できるのでPythonで復号化処理を書いちゃえばみなさんの重い腰もあがりやすいはず (Djangoは復号化出来ない, パスワードチェックはhash値の比較だったかな)
もくじ
いろいろ端折っているのでimportエラー他はそれなりに出ますよ
Codeigniter復号化処理
まず下記のような復号化処理を定義しておきます
# -*- coding: utf-8 -*- import base64 import logging import hashlib import itertools import mcrypt # XXX: pip install python-mcrypt logger = logging.getLogger(__name__) class DecodeError(Exception): """ CrypterDecode error """ class Codeigniter(object): def __init__(self, bit='rijndael-256', mode='cbc'): # AES:Rijndael 256bit, CBC modeを使用 self.crypter = mcrypt.MCRYPT(bit, mode) def encode(self): raise NotImplementedError("Not implemented error: Codeigniter.encode") def decode(self, string, key='my-secret-key'): """ Codeigniterで保存されているパスワードの復号化 """ data = '' hashkey = hashlib.md5(key).hexdigest() try: ## Codeigniter独自(っぽい)の復号化前処理 # data_str: 元のバイト列を返す # key_str: hash文字数が超えたらcycleして初めからhash文字を返す iters = zip(base64.b64decode(string), itertools.cycle(hashlib.sha1(hashkey).hexdigest())) for data_str, key_str in iters: temp = ord(data_str) - ord(key_str) if temp < 0: temp += 256 data += chr(temp) except TypeError as err: logger.exception(err) raise DecodeError(str(err)) ## 以下mcrypt使用時のお約束 init_vect = data[0:self.crypter.get_iv_size()] data = data[self.crypter.get_iv_size():] try: self.crypter.init(hashkey.ljust(24, '\0'), init_vect) except ValueError as err: logger.exception(err) raise DecodeError(str(err)) return self.crypter.decrypt(data)[0:32].rstrip('\0')
実行方法
>>> codeigniter = Codeigniter()
>>> codeigniter.decode(encrypted_password)
'output raw password'
Django inspectdb
Command
次に app の作成, Database から Django のモデル定義を生成するコマンドを実行
http://docs.djangoproject.jp/en/latest/ref/django-admin.html#inspectdb
$ python manage.py startapp egg $ python manage.py inspectdb > egg/models.py
生成後のディレクトリ構成は下記のようになっている
$ tree . ├── development.db ├── egg │ ├── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── ham │ ├── __init__.py │ ├── __init__.pyc │ ├── settings.py │ ├── settings.pyc │ ├── urls.py │ └── wsgi.py └── manage.py
South
パッケージ
migration ツールの South を実行して Codeigniter のDatabase定義を修正する準備
$ python manage.py schemamigration [django_app] --initial
これで、定義は終了したのでmodelsを修正すれば、south がDatabaseの改修をしてくれます。
$ python manage.py schemamigration [django_app] --auto
$ python manage.py migrate
Django Custom User
最後に先ほど定義した復号化処理を Django user model へ差し込めば移行処理が完了
# -*- coding: utf-8 -*- from django.db import models from django.utils import timezone from django.contrib.auth import hashers from django.utils.encoding import python_2_unicode_compatible from . import crypter @python_2_unicode_compatible class BaseUser(models.Model): """ 各UserModelの親クラス passwordのアルゴリズムを旧フレームワークから移植している """ encrypted_password = models.CharField(u'パスワード', max_length=128L) last_login = models.DateTimeField(u'ログイン日時', default=timezone.now) is_active = True REQUIRED_FIELDS = [] class Meta: abstract = True def get_username(self): """ AccountのID識別子を返却 """ return getattr(self, self.USERNAME_FIELD) def __str__(self): return self.get_username() def natural_key(self): return (self.get_username(), ) def is_anonymous(self): return False def is_authenticated(self): return True def set_password(self, raw_password): """ パスワードを設定する """ self.encrypted_password = hashers.make_password(raw_password) def check_password(self, raw_password): """ raw passwordのチェック 旧passwordが正しい場合は即Django暗号化方式のパスワードへ更新する """ def setter(raw_password): """ password更新 """ self.set_password(raw_password) self.save(update_fields=["encrypted_password"]) if self.has_usable_password(): # Django password return hashers.check_password(raw_password, self.encrypted_password, setter) else: # Old framework password try: result = crypter.codeigniter.decode(self.encrypted_password) == raw_password except crypter.DecodeError: return False if result: setter(raw_password) return result def set_unusable_password(self): """ 有効なhash値にならないパスワードをセット """ self.encrypted_password = hashers.make_password(None) def has_usable_password(self): """ 有効なパスワードか """ return hashers.is_password_usable(self.encrypted_password) def get_full_name(self): raise NotImplementedError() def get_short_name(self): raise NotImplementedError() def _get_encrypted_password(self): return self.encrypted_password def _set_encrypted_password(self, value): self.encrypted_password = value password = property(_get_encrypted_password, _set_encrypted_password)
実行方法
テキトーにrunserver
してログインしてみてください
$ python manage.py runserver
うん、これでDjangoに移行できるかな?
稚拙なことしか書いてないですが、是非トライしてみてください。
Pythonの例外情報(Traceback)をLogに記録するには Logger.exception が便利って話
極たまに下記のようなコードを見ます。例外情報が欲しいのは分かりますがlogging.Loggerクラスには専用のメソッドがあります。
import traceback try: dosomething() except (KeyError, ValueError): logger.error(traceback.format_exc())
Logエラーレベルが ERROR のメッセージで例外情報を残したいなら下記にすべきです。
try: dosomething() except (KeyError, ValueError) as err: logger.exception('Error dosomething: %s', err)
ドキュメント
Macの濁点問題を解決するPython unicodedataモジュール
日本語のURLを使いたいんです
最近Scrapyで採取したゴミをMongoDB※1(笑) に入れておき、その後TinkererでブログにしてS3へアップロードする一連のスクリプトを書いて、ゴミサイトを何個も作りまくってるんですけど
Tinkererで生成したtag list, tag cloudにUnicode文字も含めるカスタマイズをしたのですが、その後頻繁にS3で404が発生する現象が起きていたので調べていたのです、そしたらなにやらUTF-8関連のきな臭い問題のようでした
UTF-8-MAC
使用している環境がMacなものでUTF-8-MAC
問題が発生していました。
(この問題についてはこちらを参照)
説明は省きますが修正するにはUTF-8-MAC
からUTF-8に変換すれば良いみたいです。普通この際iconvを使うのが一般的思いますが今回は一連のゴミ採集スクリプトの言語がPythonなのでPythonでやりたいと思います
unicodedataモジュール
unicodedataモジュールに変換処理のめんどうをみてもらうことにする※2
例えばゴミって単語をUTF-8-MAC
からUTF-8へ変換する場合
import unicodedata unicodedata.normalize('NFC', u'ゴミ')
この処理を施した文字列をboto.s3に直接渡すことで問題なくS3上でゴミタグを表示できた
(∩´∀`)∩ワーイ
脚注
※1 MongoDBとScrapyは相性が悪い。 http://blog.scrapinghub.com/2013/05/13/mongo-bad-for-scraped-data/ (というか全般的なMongoDBへの評価ですね)
※2 unicodedataモジュールはUnicodeData.txtの対応表を元に変換処理をしてくれるようだ、Versionは5.2.0(UnicodeData File Format 5.2.0)。変換処理について知りたい人はこちらが参考になるかも
Southでカラムのdefault値を変更したいのけど--auto が効かない時
カラムのdefaultを変更したいのけど --auto
オプションが効かないの
解1
Djangoの場合は必要なし, 実際にDBへは反映されていない
解2
マニュアルでschemamigration実行後、編集
$ python manage.py schemamigration table1 change_default_value_to_someone --add-field Egg.someone --add-field Spam.someone
その後 db.alter_column
などで修正
Python Enum
Enumすら思い出せない忘れんぼさんなんで PythonのEnum を弄ってみました
列挙型定義
from enum import Enum class Colors(Enum): RED = '1' BLUE = 2 GREEN = 'green' print(Colors) # <enum 'Colors'>
動的に
>>> Enum('Colors', (('RED', '1'), ('BLUE', 2), ('GREEN', 'green'))) <enum 'Colors'>
Enum型の一覧
>>> Colors.__members__ mappingproxy(OrderedDict([('RED', <Colors.RED: '1'>), ('BLUE', <Colors.BLUE: 2>), ('GREEN', <Colors.GREEN: 'green'>)]))
名前、値を得る
>>> Colors.RED <Colors.RED: '1'> >>> Colors['RED'] <Colors.RED: '1'> >>> Colors.RED.name 'RED' >>> Colors.RED.value '1'
iterを当てるとgeneratorが返る
>>> iter(Colors) <generator object <genexpr> at 0x108b12120> >>> >>> (Colors.__members__[name] for name in Colors.__members__) <generator object <genexpr> at 0x108b12438> >>> >>> for color in Colors: ... print(repr(color)) ... <Colors.RED: 1> <Colors.BLUE: 2> <Colors.GREEN: 3>
定義後はClass, Attributeは削除できない
>>> del Colors.RED Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: RED >>> >>> del Colors.RED.name Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/python/lib/python3.4/enum.py", line 29, in __delete__ raise AttributeError("can't delete attribute") AttributeError: can`t delete attribute >>> >>> del Colors.RED.value Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/python/lib/python3.4/enum.py", line 29, in __delete__ raise AttributeError("can't delete attribute") AttributeError: can`t delete attribute
辞書のキーとして使う、Enumの比較
__hash__が定義されてたので辞書のキーでも使えた
>>> red1 = Colors.RED >>> dct = {red1: 10} >>> dct[red1] 10 >>> red2 = Colors.RED >>> dct[red2] 10 >>> red1 == red2 True
Pickle化
__getnewargs__ がある, pickleable みたい
>>> from pickle import dumps, loads >>> Colors.RED == loads(dumps(Colors.RED)) True
Enumの型を指定して扱う
Enumは定義時に型を指定すると列挙する値が、すべて指定した型になる
strとして列挙したい場合
from enum import Enum class Colors(str, Enum): RED = '1' BLUE = 2 GREEN = 'green' print(repr(Colors.BLUE)) # <Colors.BLUE: '2'> print(repr(Colors.BLUE.value)) # '2'
intとして列挙したい場合
IntEnumが用意されている
下記では 'green'
がエラーになる
from enum import IntEnum class Colors(IntEnum): RED = '1' BLUE = 2 GREEN = 'green' # ValueError: invalid literal for int() with base 10: 'green'
isdigitな数値ならOKのようだ、isnumericはだめ(IVとか, 四とか)
from enum import Enum class Colors(int, Enum): RED = '1' BLUE = 2 GREEN = b'3' YELLOW = '4' print(repr(Colors.GREEN.value)) # 3 print(repr(Colors.YELLOW.value)) # 4
floatとして列挙したい場合
from enum import Enum class Colors(float, Enum): RED = '1' BLUE = 2 GREEN = b'3' YELLOW = '4' print(repr(Colors.GREEN.value)) # 3.0 print(repr(Colors.YELLOW.value)) # 4.0
tupleとして列挙したい場合
もちろんtupleもある
from enum import Enum class Colors(tuple, Enum): RED = (1, 'red color') BLUE = (2, 'blue color') GREEN = (3, 'green color') print(repr(Colors.RED)) # <Colors.RED: (1, 'red color')> print(repr(Colors.RED.value)) # (1, 'red color')
NamedInt
class Colors(NamedInt, Enum): RED = ('red color', 1) BLUE = ('blue color', 2) GREEN = ('green color', 3) print(repr(Colors.RED)) # <Colors.RED: NamedInt('red color', 1)> print(repr(Colors.RED.value)) # NamedInt('red color', 1)
OrderedEnum
Eum同士の比較が可能
class Colors(OrderedEnum, Enum): RED = 1 BLUE = 2 GREEN = 3 print(Colors.RED < Colors.BLUE) # True
class Colors(Enum): RED = 1 BLUE = 2 GREEN = 3 print(Colors.RED < Colors.BLUE) # TypeError: unorderable types: Colors() < Colors()
LabelledIntEnum
class LabelledIntEnum(int, Enum): def __new__(cls, *args): value, label = args obj = int.__new__(cls, value) obj.label = label obj._value_ = value return obj class Colors(LabelledIntEnum): RED = (1, "red") BLUE = (2, "blue") GREEN = (3, "green") print(repr(list(Colors))) # [<Colors.RED: 1>, <Colors.BLUE: 2>, <Colors.GREEN: 3>] print(repr(Colors(1))) # <Colors.RED: 1> print(repr(Colors.RED)) # <Colors.RED: 1> print(repr(Colors.RED.value)) # 1
UniqueEnum
下のuniqueデコレーターと同じかな
列挙する値のユニークを保証する
uniqueデコレーターがありました。つかいませう
from enum import ( Enum, unique ) @unique class Colors(int, Enum): RED = '1' BLUE = 2 GREEN = '3' YELLOW = '3' # ValueError: duplicate values found in <enum 'Colors'>: YELLOW -> GREEN
自動採番
値がなければナンバリングされる
int
>>> Enum('Colors', ('RED', 'BLUE', 'GREEN', ), module=__name__).RED <Colors.RED: 1>
>>> Enum('Colors', 'RED BLUE GREEN').RED <Colors.RED: 1>
str
>>> StrEnum('Colors', 'RED BLUE GREEN').RED <Colors.RED: '1'>
tuple
>>> TupleEnum('Colors', 'RED BLUE GREEN').RED <Colors.RED: (1,)>
AutoNumberInAList
class AutoNumberInAList(Enum): def __new__(cls): value = [len(cls.__members__) + 1] obj = object.__new__(cls) obj._value_ = value return obj class Colors(AutoNumberInAList): RED = () BLUE = () GREEN = () print(repr(Colors.RED)) # <Colors.RED: [1]> print(repr(Colors.RED.value)) # [1]
AutoNumber
定義時に自動採番
auto_enum
定義時のmetaclassで自動採番
終わり
Enumには夢が詰まっているとのこと