sftpserverモジュールをforkしていじった件

諸事情によりテスト用のsftpサーバーが必要になったのでsftpserverなるモジュールを見つけてきた。しかしこいつシングルスレッドなので、やりたいことのためにはmultiprocessingと併用する必要があったわけなんだけど、そうするとなんかエラーが出る。

raise AssertionError("PID check failed. RNG must be re-initialized after fork().
Hint: Try Random.atfork()")

で調べてみたところそのものズバリのエラーで困ってる人がいた。

python - multiprocess module with paramiko - Stack Overflow

どうやらparamikoのバグらしい。paramikoのforkで、pure python実装なsshモジュールだとこの問題は解決済みらしい。

ちなみにpyftpdlibはFTPSには対応しているけれど、SFTPには対応してないとのことだったので諦めた。issueに「SFTPにも対応しないの?」みたいなのもあったけど、「pyftpdlibがやるようなことじゃない」って蹴られてたので多分今後もそういう方向には行かないと思う。
あと、pyFileSystemというものがSFTPに対応していたので使ってみたけど、こいつもparamiko依存なので同じエラーが出る。

で、仕方がないのでforkしてちょこっと改良した。

kk6/sftpserver at spike · GitHub

変更点

  • 内部でのparamikoの使用をやめてsshに移行した
  • optparseをやめてargparseに移行した
  • なんとなくデーモン化したかったのでpython-daemonでデーモン起動できるようにした

今にして思えばデーモン化はちょっと余計だった気がしてpull-requestは飛ばしてない

テストで使う

unittestのsetupで、こんな感じでmultiprocessingと併用して立ち上げてやる

def setUp(self):
    from sftpserver import start_server
    from multiprocessing import Process

    args = (HOST, PORT, KEYFILE, 'DEBUG')
    self._process = Process(target=start_server, args=args)
    self._process.start()


def tearDown(self):
    self._process.terminate()

Djangoの発行する生SQLが見たい

ForeignKeyとfilterのメモ - AtAsAtAmAtArA
DjangoのORマッパーでSQL文を簡単に出力するサンプル - 十番目のムーサ

というやりとりを見て。シェルで確かめるのが楽だし便利。
django.db.connection.queriesで見られます。

おもむろにdjangoのシェルを起動

$ python manage.py shell

>>> from django.db import connection
>>> connection.queries
[]  # 最初は当然からっぽ
>>> from entry.models import Entry  # 適当にモデルをインポート
>>> Entry.objects.filter(is_active=True).order_by('-modified')
[<Entry: ぴよ>, <Entry: aa>, <Entry: ふが>, <Entry: ほげ>]
>>> connection.queries[-1]['sql']
u'SELECT "entry"."id", "entry"."del_flg", "entry"."author_id", "entry"."title", "entry"."body", "entry"."slug", "entry"."created", "entry"."modified", "entry"."publish", "entry"."is_active" FROM "entry" WHERE "entry"."is_active" = True  ORDER BY "entry"."modified" DESC LIMIT 21'
>>> connection.queries[0].keys()
['time', 'sql']  # 実行時間と生SQLが入ってる

詳しくは公式ドキュメントのFAQを参照してください。

シェル操作課題をPythonでやってみた

はてブのマイホットエントリーを眺めてたらこんなものを見つけた。
シェル操作課題 (cut, sort, uniq などで集計を行う) 設問編 - Yamashiro0217の日記
awkとか全然知らないから勉強になるかなと思って覗いてみたんだけど…

いっそpythonワンライナーで実行したらカッコイイと思うw

やりましょう

Python3.2でやってみたよ

問1 このファイルを表示しろ
$ python3 -c "import functools,operator;print(functools.reduce(operator.__add__, (open('hoge.csv','r').readlines())))"

server1,1343363124,30,/video.php
server2,1343363110,20,/profile.php
server3,1343363115,7,/login.php
server1,1343363105,8,/profile.php
server2,1343363205,35,/profile.php
server2,1343363110,20,/profile.php
server3,1343363205,30,/login.php
server4,1343363225,12,/video.php
server1,1343363265,7,/video.php

問2 このファイルからサーバー名とアクセス先だけ表示しろ
$ python3 -c "import csv,operator,functools;print(functools.reduce(operator.__add__, [','.join((r[0], r[3]+'\n')) for r in csv.reader(open('hoge.csv','r'))]))"

server1,/video.php
server2,/profile.php
server3,/login.php
server1,/profile.php
server2,/profile.php
server2,/profile.php
server3,/login.php
server4,/video.php
server1,/video.php

問3 このファイルからserver4の行だけ表示しろ
$ python3 -c "print([line for line in open('hoge.csv','r').readlines() if line[6]=='4'][0])"

server4,1343363225,12,/video.php

問4 このファイルの行数を表示しろ
$ python3 -c "print(len(open('hoge.csv','r').readlines()))"

9

問5 このファイルをサーバー名、ユーザーIDの昇順で5行だけ表示しろ
$ python3 -c "import operator,csv,functools;print(functools.reduce(operator.__add__, [','.join((r[0],str(r[1]),str(r[2]),r[3])) for r in sorted([(r[0], int(r[1]), int(r[2]), r[3]+'\n') for r in csv.reader(open('hoge.csv','r'))], key=operator.itemgetter(0,2))][:5]))"

server1,1343363265,7,/video.php
server1,1343363105,8,/profile.php
server1,1343363124,30,/video.php
server2,1343363110,20,/profile.php
server2,1343363110,20,/profile.php

これが一番ひどい。数値としてソートするためにintに変換して、その後文字列連結するためにintにしたものをstrに直してる。

問6 このファイルには重複行がある。重複行はまとめて数え行数を表示しろ
$ python3 -c "print(len(set(open('hoge.csv','r'))))"

8

問7 このログのUU(ユニークユーザー)数を表示しろ
$ python3 -c "import csv;print(len(set([r[2] for r in csv.reader(open('hoge.csv','r'))])))"

6

問8 このログのアクセス先ごとにアクセス数を数え上位1つを表示しろ
$ python3 -c "import csv,collections;print(collections.Counter([r[1] for r in set([(r[2],r[3]) for r in csv.reader(open('hoge.csv','r'))])]).most_common(1))"

[('/video.php', 3)]

表示がちょっとアレだけど、知りたい情報が取り出せてるしこれでいいよねもう。

問9 このログのserverという文字列をxxxという文字列に変え、サーバー毎のアクセス数を表示しろ
$ python3 -c "import csv,collections;print(collections.Counter(map(lambda x: x.replace('server', 'xxx'), [r[0] for r in csv.reader(open('hoge.csv','r'))])))"

Counter({'xxx1': 3, 'xxx2': 3, 'xxx3': 2, 'xxx4': 1})

これまた表示が形式が違うけどこれでいいよね。ていうか出題者の想定結果が間違ってる気がするんですが。

問10 このログのユーザーIDが10以上の人のユニークなユーザーIDをユーザーIDでソートして表示しろ
$ python3 -c "import csv;print(set([r[2] for r in csv.reader(open('hoge.csv','r')) if int(r[2]) >= 10]))"

{'12', '30', '20', '35'}

これまた表示が(ry

感想

Pythonワンライナーなんてやるもんじゃない。