kivyのログを出力するTextInputをつくってみた
タイトルではわかりにくいけど、kivyのログを反映するTextInputをつくってみたが正しいのではないかと思う。
方法
今回は、kivyのLoggerにStreamHandlerを追加して反映させることにした。委譲のAdapterっぽい感じでio.TextIOBaseを継承するTextInputIOなるものをつくり、必要なものを実装していった。
テスト
import unittest from kivy.uix.textinput import TextInput from app.TextInputIO import TextInputIO class TextInputIOTestCase(unittest.TestCase): """ Test for TextInputIO class """ def setUp(self): self.text_input = TextInput() def test_as_context_manager(self): with TextInputIO(self.text_input) as f: pass def test_open_close(self): f = TextInputIO(self.text_input) f.close() def test_write(self): f = TextInputIO(self.text_input) f.write('TESTING TEXT INPUT\n') f.close() def test_non_readable(self): f = TextInputIO(self.text_input) self.assertRaises(OSError, f.read) f.close() def test_non_seekable(self): f = TextInputIO(self.text_input) self.assertRaises(OSError,f.seek) f.close() def test_isatty(self): f = TextInputIO(self.text_input) self.assertEqual(f.isatty(), False) f.close()
こんな感じで書いた。
実装
from io import TextIOBase class TextInputIO(TextIOBase): """ IO for Logger. Using TextInput as file. """ def __init__(self,text_input): super().__init__() self.text_input = text_input def isatty(self): return False def readable(self): return False def writable(self): return True def seekable(self): return False def write(self, s): self.text_input.text += s def writelines(self, lines): if not isinstance(lines,list): raise TypeError('list') for s in lines: if not isinstance(s,str): raise TypeError('str') self.write(s) def close(self): super().close() self.text_input = None
バッファとかないしどうしようかなと思ったりしてた。テストに書いたように__enter__と__exit__を継承してないけど、コンテキストマネージャとして動いた。多分、親のを呼び出してるんだと思う。
使ってみた
from kivy import Logger from kivy.app import App from kivy.uix.button import Button import logging from logging import StreamHandler from kivy.uix.gridlayout import GridLayout from kivy.uix.textinput import TextInput from app.TextInputIO import TextInputIO logger = Logger logger.setLevel(logging.DEBUG) text_input = TextInput() text_input_io = TextInputIO(text_input) sh = StreamHandler(text_input_io) fh = logging.FileHandler('app.log') logger.addHandler(fh) def pressed(view): logger.info('Application: Button was pressed!!') class MainScreen(GridLayout): def __init__(self, **kwargs): super().__init__(**kwargs) self.cols = 1 b = Button(text="Button") b.bind(on_press=pressed) self.add_widget(b) self.add_widget(text_input) logger.addHandler(sh) class TestApp(App): def build(self): return MainScreen() if __name__ == '__main__': TestApp().run()
テスト実行用にこんなの書いた。最初、別のロガーを使っていたのでグローバルなところにロガーの設定があるけど気にしないでほしい。
そこそこ動いた。多分問題点として、スレッドセーフじゃないと思う。あとTextInputを追加する前にloggerにハンドラとして追加すると強制終了する。追加は遅延させないとダメかなと。したがって、最初のGLとかのログメッセージは反映されない。あと、最初のカラムのINFOとかが出なかった。logging.BASIC_FORMATをハンドラに設定しても解析されなかった。
やってみて思ったこと
まだまだ調べることが多そうだなって思った。最近ブログを更新する気がだいぶ出てきたので、この調子でやっていきたいなぁって。