inquirerpy について
inquirerpy は NodeJS でのinquirer.js に触発されて開発された、会話形アプリケーションを作成するための拡張モジュールです。
類似のモジュールには python-inquirer や、PyInquirer があります。
python-inquirer も node.js の inquirer.js に触発されてほぼ同じ実装をPython で実現できるように開発されました。しかし、残念ながら Windows では一部うまく動作しないことが確認されています
これに対して、inquirerpy と PyInquirer は内部的には prompt_toolkit を使用しているため、Windows でも問題なく動作できます。
ただし、PyInquirer は prompt_toolkit の古いバージョン1.0.14に依存していて、IPython などと共存することができなくなっています。inquirerpy はこの問題を解決するとともに、いくつかのバグを修正するために再開発されています。
inquirerpy は Python 3.7 以降で動作します。
inquirerpy のインストール
inquirerpy はpipコマンドでインストールできます。
$ pip install inquirerpy
inquirerpy の互換API
inquirerpy は PyInquirer との互換性を考慮して開発されています。
まずは、PyInquirer との互換APIについて説明することにします。
prompt()
を使ってユーザから文字列を受け取る簡単な例から見てみましょう。
from InquirerPy import prompt
questions = [
{
'type': 'input',
'name': 'first_name',
'message': "What's your first name",
}
]
answers = prompt(questions)
print(answers)
print(answers['first_name'])
これを実行すると次のような質問が表示されます。
questions
は質問を記述した辞書のリストです。
prompt()
は質問の定義に従ってユーザからの入力を受け取り、辞書オブジェクトとして結果を返します。
リストにある複数の質問は続けて処理されます。
from InquirerPy import prompt
questions = [
{
'type': 'input',
'name': 'first_name',
'message': "What's your first name",
},
{
'type': 'input',
'name': 'last_name',
'message': "What's your last name",
'when': lambda answers: answers['first_name'] != '',
}
]
answers = prompt(questions)
print(answers)
$ python 002_multiple_input.py
? What's your first name Jack
? What's your last name Bauer
{'first_name': 'Jack', 'last_name': 'Bauer'}
質問の辞書に、when
で条件が与えられていると、その条件に合致するときだけ、その質問はユーザに問い合わせが行われます。
入力補完
ユーザから文字列を受け取るときに、予め登録した文字列に部分合致するか、はじめにタブキーを押下すると補完文字列をポップアップ表示します。このとき文字列入力中に
タブキーを押下すると残りの入力を補完したデータを返します。
ポップアップされた候補をカーソルキーで選ぶこともできます。
from InquirerPy import prompt
questions = [
{
"type": "input",
"message": "Which company would you like to apply:",
"completer": {
"Google": None,
"Facebook": None,
"Amazon": None,
"Netflix": None,
"Apple": None,
"Microsoft": None,
},
"multicolumn_complete": True,
},
]
answers = prompt(questions)
print(answers)
transformer, filter, validate
ユーザの入力を加工したり、結果を変換したり、データ検証を行わせることができます。
from InquirerPy import prompt
from InquirerPy.validator import NumberValidator
questions = [
{
"name": "request salary",
"type": "input",
"message": "What's your salary expectation(k):",
"transformer": lambda result: "%sk" % result,
"filter": lambda result: int(result) * 1000,
"validate": NumberValidator(),
},
]
answer = prompt(questions)
print(answer)
transformer
は入力された文字列を加工して画面に出力します。filter
は入力されたデータを加工して返します。
validate
で与えた検証が失敗すると入力は完了せずに、エラーメッセージを出力して再度入力待ちになります。
質問のタイプ
質問を定義する辞書に与えるキーtype
で、質問のタイプを指定します。
次のようなタイプがあります。
-
list
: リストした候補をカーソルキーで選択する -
rawlist
:数値付きでリストした候補を数値で選択する -
expand
:リストのコンパクト表示、キーアクションで展開表示 -
checkbox
:チョイスボックス -
confirm
:ユーザに Yes/No の確認をしブール値を返す -
input
:プロンプトを表示してユーザから文字列を受け取る -
password
:input
と似ているがユーザの入力は表示されない -
fazzy
: -
editor
:ユーザから複数行の入力を受け取る
list タイプ
選択肢を表示してユーザに選ばせる基本的なUIです。
プロパティー:type
, name
, message
, choices
, default
、
[filter]
、[transformer]
、[validate]
、[multiselect]
、[invalid_message]
filter
で与えた処理がユーザの入力に適用されて返されます。
transformer
は返り値には影響は与えませんが、表示文字列を変更します。
multiselect
がTrue
のときは複数の選択を許します。
from InquirerPy import prompt
questions = [
{
"type": "list",
"message": "Select an action:",
"choices": ["Upload", "Download",
{"name": "Exit", "value": None}],
"default": None,
# "multiselect": True,
},
]
answer = prompt(questions=questions)
print(answer)
カーソルキーで候補を選択します。
multiselect
にTrue
を与えると、複数選択することができラジオボタンのように動作します。タブキー、スペースキーで候補を選択できます。
Separator
Separator
クラスのインスタンスオブジェクトを与えると、一覧するリストに区切り文字を表示するゆになります。
from InquirerPy import prompt
from InquirerPy.separator import Separator
questions = [
{
"name": "region",
"type": "list",
"message": "Select regions:",
"choices": [
{"name": "Sydney", "value": "ap-southeast-2"},
{"name": "Singapore", "value": "ap-southeast-1"},
Separator(),
"us-east-1",
Separator(line='*' * 15),
"us-east-2",
],
"multiselect": True,
"transformer": lambda result: "%s region%s selected"
% (len(result), "s" if len(result) > 1 else ""),
},
]
result = prompt(questions=questions)
print(result)
rawlist タイプ
自動的に数字のショートカットを適用させたリストプロンプトです。
各選択肢の前には数字が付加されます。この数字をショートカットキーとして使用して選択肢にジャンプしたり、通常の上下キーバインドで移動したりすることができます。
プロパティー:type
, name
, message
, choices
, default
、
[filter]
、[transformer]
、[validate]
、[multiselect]
、[invalid_message]
from InquirerPy import prompt
questions = [
{
"type": "rawlist",
"choices": [
"Apple", "Orange", "Peach", "Cherry",
"Melon", "Strawberry", "Grapes",
],
"message": "Pick your favourites:",
"default": 3,
"multiselect": True,
"transformer": lambda result: ", ".join(result),
"validate": lambda result: len(result) > 1,
"invalid_message": "Minimum 2 selections",
},
]
fruits = prompt(questions)
print(fruits)
expand タイプ
コンパクト表示(compact)と拡大表示(expand)の2種類のUIを持つリストプロンプトです。
はじめはコンパクトな状態で表示されますが、h
キーを使って拡大することができます。拡大後は、通常のリストナビゲーションキーが有効になります。
プロパティー:type
, name
, message
, choices
, default
、
[filter]
、[transformer]
、[validate]
、[multiselect]
、[invalid_message]
、[instruction]
from InquirerPy import prompt
fruits_choice = [
{"key": "a", "name": "Apple", "value": "Apple"},
{"key": "c", "name": "Cherry", "value": "Cherry"},
{"key": "o", "name": "Orange", "value": "Orange"},
{"key": "p", "name": "Peach", "value": "Peach"},
{"key": "m", "name": "Melon", "value": "Melon"},
{"key": "s", "name": "Strawberry", "value": "Strawberry"},
{"key": "g", "name": "Grapes", "value": "Grapes"},
]
questions = [
{
"type": "expand",
"choices": fruits_choice,
"message": "Pick your favourite:",
"default": "o",
"cycle": False,
},
]
result = prompt(questions)
print(result)
checkbox タイプ
チェックボックスを表示するリストプロンプト。
マルチセレクトが可能な他のリストプロンプトでは、最小選択数は常に1です(ユーザが何も選択しない場合、現在のハイライトされた選択肢が選択されます)。チェックボックスプロンプトでは、現在のハイライトされた選択肢は自動的に選択されません。
プロパティー:type
, name
, message
, choices
, default
、
[filter]
、[transformer]
、[validate]
、[multiselect]
、[invalid_message]
、[instruction]
choices
で与える各選択肢は、文字列表現を持つ任意の値、または以下のキーで構成される辞書にすることができます。
-
name
:選択肢の表示名で、ユーザーはこの値を見ることができます。 -
value
選択肢の値。名前とは異なる場合がありますが、ユーザーはこの値を見ることはできません。 -
enabled
: 選択肢が選択された状態にあるかどうかをTrue/Falseで与える
choices
で {'enabled':True}
とされた選択肢は、デフォルトでチェックされます。
from InquirerPy import prompt
from InquirerPy.separator import Separator
region_choice = [
Separator(),
{"name": "Sydney", "value": "ap-southeast-2", "enabled": False},
{"name": "Singapore", "value": "ap-southeast-1", "enabled": True},
Separator(),
"us-east-1",
"us-west-1",
Separator(),
]
questions = [
{
"type": "checkbox",
"message": "Select regions:",
"choices": region_choice,
"transformer": lambda result: "%s region%s selected"
% (len(result), "s" if len(result) > 1 else ""),
},
]
result = prompt(questions)
print(result)
confirm タイプ
ユーザーの確認を取るためのプロンプトです。
プロパティー:type
, name
, message
, choices
,[default]
, [when]
default
はブール値である必要があります。
from InquirerPy import prompt
questions = [
{
"type": "confirm",
"message": "Proceed?",
"name": "proceed",
"default": True,
},
{
"type": "confirm",
"message": "Require 1 on 1?",
"when": lambda result: result["proceed"],
},
{
"type": "confirm",
"message": "Confirm?",
"when": lambda result: result.get(1, False),
},
]
result = prompt(questions)
print(result)
この例のように連続した質問では、when
で指定した質問の応答がTrueとなるときだけ質問するようにもできます。
input タイプ
ユーザーにいくつかのテキスト値を入力させる入力プロンプトです。Ctrl-Space
もしくは TABキーを押下すると、completter
が与えられている場合は、自動コンプリートのポップアップが強制的に表示されます。
プロパティー:type
, name
, message
,, default
、
[filter]
、[transformer]
、[validate]
、[multiline]
、[invalid_message]
、
-
message
で与えた文字列をプロンプトとして表示し、ユーザから文字列の入力を受け付けます。 -
default
にはデフォルト値を与えることができます。 -
completer
には補完文字列を登録することができます。 -
filter
で与えた処理がユーザの入力に適用されて返されます。 -
validate
で与えた関数で入力値を検証することができます。 -
multiline
がTrue
のときは複数行の入力を許します。
from prompt_toolkit.validation import Validator, ValidationError
from pprint import pprint
from InquirerPy import prompt
class AgeValidator(Validator):
def validate(self, document):
if int(document.text) < 18:
raise ValidationError(
message='Too yound.',
cursor_position=len(document.text)) # Move cursor to end
questions = [
{
'type': 'input',
'name': 'first_name',
'message': "What's your first name ?",
},
{
'type': 'input',
'name': 'last_name',
'message': "What's your last name ?",
'default': lambda ans: 'Bauer' if ans['first_name'] == 'Jack' else '',
},
{
'type': 'input',
'name': 'age',
'message': "How old are you ?",
'validate': AgeValidator()
}
]
result = prompt(questions)
pprint(result)
first name に Jack と入力されると、last name のデフォルトは Bauer になります。
age では AgeValidator()
で入力値が処理され異常値であればエラーが表示されて再度入力が求められる状態になります。
passwrd タイプ
ユーザーにパスワードなどの秘密の値を入力させるための入力プロンプトです。inputタイプと似ていますがユーザが入力した文字列は画面には表示されません。
プロパティー:type
, name
, message
, [default]
, [filter]
, [validate]
-
message
で与えた文字列をプロンプトとして表示し、ユーザから文字列の入力を受け付けます。 -
default
にはデフォルト値を与えることができます。 -
filter
で与えた処理がユーザの入力に適用されて返されます。 -
validate
で与えた関数で入力値を検証することができます。
from InquirerPy import prompt
from InquirerPy.validator import PasswordValidator
original_password = "P@ssw0rd123"
questions = [
{
"type": "password",
"message": "Old password:",
"transformer": lambda _: "[hidden]",
"validate": lambda text: text == original_password,
"invalid_message": "Wrong password",
},
{
"type": "password",
"message": "New password:",
"name": "new_password",
"validate": PasswordValidator(
length=8, cap=True, special=True, number=True
),
"transformer": lambda _: "[hidden]",
},
{"type": "confirm", "message": "Confirm?", "default": True},
]
result = prompt(questions)
print(result)
filepath タイプ
ファイルパスの補完機能があらかじめ組み込まれた入力プロンプト。
from pathlib import Path
from InquirerPy import prompt
from InquirerPy.validator import PathValidator
questions = [
{
"type": "filepath",
"message": "Enter file to upload:",
"name": "location",
"default": str(Path('/tmp')),
"validate": PathValidator(is_file=True, message="Input is not a file"),
"only_files": True,
},
{
"type": "filepath",
"message": "Enter path to download:",
"validate": PathValidator(is_dir=True, message="Input is not a directory"),
"name": "destination",
"only_directories": True,
},
]
result = prompt(questions)
print(result)
Fuzzy タイプ
入寮文字列のあいまい検索機能を持つリストプロンプトです。選択肢の数が約30万に達するまでは比較的良好な性能を発揮します。
from contextlib import ExitStack
from pathlib import Path
from InquirerPy import inquirer, prompt
_WORDFILE_ = 'sample.txt'
def get_choices(_):
p = Path.cwd().joinpath(_WORDFILE_)
choices = []
with ExitStack() as stack:
if not p.exists():
raise Exception('%s: wordfile missing!', _WORDFILE_)
else:
file = stack.enter_context(p.open("r"))
for line in file.readlines():
choices.append(line[:-1])
return choices
questions = [
{
"type": "fuzzy",
"message": "Select actions:",
"choices": ["hello", "weather", "what", "whoa", "hey", "yo"],
"default": "he",
"max_height": "70%",
},
{
"type": "fuzzy",
"message": "Select preferred words:",
"choices": get_choices,
"multiselect": True,
"validate": lambda result: len(result) > 1,
"invalid_message": "minimum 2 selection",
"max_height": "70%",
},
]
result = prompt(questions=questions)
print(result)
キーバインド
keybindings
でキーバインドを定義することができます。
デフォルトのキーバインドは次のように定義されています。
"down": [
{"key": "down"},
{"key": "c-n", "filter": ~self._is_vim_edit},
{"key": "j", "filter": self._is_vim_edit},
],
"up": [
{"key": "up"},
{"key": "c-p", "filter": ~self._is_vim_edit},
{"key": "k", "filter": self._is_vim_edit},
],
"toggle": [
{"key": "space"},
],
"toggle-down": [
{"key": Keys.Tab},
],
"toggle-up": [
{"key": Keys.BackTab},
],
"toggle-all": [
{"key": "alt-r"},
],
"toggle-all-true": [
{"key": "alt-a"},
],
"toggle-all-false": [],
}
Ctrl+n
でカーソルキーの下キー、Ctrl+p
でカーソルキーの上キーと同じ動作になります。
prompt()
の引数にvi_mode=True
を与えるとキーバインドが vi コマンドと同様に、j
でカーソルキーの下キー、k
でカーソルキーの上キーと同じ動作になります。
キーバインドを変更したい場合は、prompt()
にキーワード引数 keybinding=
に辞書を与えます。
raise_keyboard_interrupt=False
を与えると、キーボードからの Ctrl-C
割り込みを無視するようになります。
キーバインドの表記方法
キー | 表記 |
---|---|
Escape | escape |
Arrows | left, right, up, down |
Navigation | home, end, delete, pageup, pagedown, insert |
Control+lowercase | c-a, c-b ... c-y, c-z |
Control+uppercase | c-A, c-B ... c-Y, c-Z |
Control + arrow | c-left, c-right, c-up, c-down |
Other control keys | c-@, c-, c-], c-^, c-_, c-delete |
Shift + arrow | s-left, s-right, s-up, s-down |
Other shift keys | s-delete, s-tab |
Function-keys | f1, f2, .... f23, f24 |
Alt+lowercase | alt-a, alt-b ... alt-y, alt-z |
Alt+uppercase | alt-A, alt-B ... alt-Y, alt-Z |
Control+Space | c-@ |
Enter | c-m |
Tab | c-i |
スタイル
スタイルをカスタマイズする場合、辞書でスタイルを定義して、style_from_dict()
に渡したオブジェクトをprompt()
のstyle
キーワードに与えます。
from InquirerPy import prompt, style_from_dict
custom_style_1 = style_from_dict({
"separator": '#cc5454',
"questionmark": '#673ab7 bold',
"selected": '#cc5454', # default
"pointer": '#673ab7 bold',
"instruction": '', # default
"answer": '#f44336 bold',
"question": '',
})
# ... (中略)
answers = prompt(questions, style=custom_style_1)
新しいAPI
inquirerpy では、オリジナルの PyInquirer とは異なる、よりシンプルなAPIを提供しています。
これまで例示したサンプルコードのファイル名と合わせた連番に100を加えたファイル名にしています。
- 互換API:
001_singple_input.py
- 新しいAPI:
101_single_input.py
PyInquirer との互換APIでは、辞書で定義した質問をprompt()
に合わえていました。
新しいAPIでは、Inquirer()
でマップされる各UIクラスを使用します。
site-packages/InquirePy/resolver.py
からの抜粋
from InquirerPy.prompts.checkbox import CheckboxPrompt
from InquirerPy.prompts.confirm import ConfirmPrompt
from InquirerPy.prompts.expand import ExpandPrompt
from InquirerPy.prompts.filepath import FilePathPrompt
from InquirerPy.prompts.fuzzy import FuzzyPrompt
from InquirerPy.prompts.input import InputPrompt
from InquirerPy.prompts.list import ListPrompt
from InquirerPy.prompts.rawlist import RawlistPrompt
from InquirerPy.prompts.secret import SecretPrompt
uestion_mapping = {
"confirm": ConfirmPrompt,
"filepath": FilePathPrompt,
"password": SecretPrompt,
"input": InputPrompt,
"list": ListPrompt,
"checkbox": CheckboxPrompt,
"rawlist": RawlistPrompt,
"expand": ExpandPrompt,
"fuzzy": FuzzyPrompt,
}
list_prompts = {"list", "checkbox", "rawlist", "expand", "fuzzy"}
互換APIのタイプと新しいAPIのメソッドの比較
互換API | 新しいAPI | UIの説明 |
---|---|---|
input | text() | ユーザから文字列を受け取る |
list | select() | 選択肢を表示してユーザに選ばせる |
rawlist | rawlist() | 選択肢を番号付きで表示してユーザに番号を選ばせる |
expand | expand() | コンパクト表示した後、キーで展開して選ばせる |
checkbox | ncheckbox() | 選択肢をチェックボックスで表示し、ユーザの選択をリス>トで返す |
confirm | nconfirm() | ユーザにYES/NOの確認を行う |
password | nsecret() | input/text() と同じですが、ユーザの入力文字列は表示されない |
filepath | nfilepath() | ファイルパスの補完機能があらかじめ組み込まれたUI |
fuzzy | nfuzzy() | ユーザの入力を元にあいまい検索の機能を持つUI |
新しいAPIのクラスをエイリアスで定義しています。
site-packages/Inquirerpy/inquirer.py
から抜粋
from InquirerPy.prompts import CheckboxPrompt as checkbox
from InquirerPy.prompts import ConfirmPrompt as confirm
from InquirerPy.prompts import ExpandPrompt as expand
from InquirerPy.prompts import FilePathPrompt as filepath
from InquirerPy.prompts import FuzzyPrompt as fuzzy
from InquirerPy.prompts import InputPrompt as text
from InquirerPy.prompts import ListPrompt as select
from InquirerPy.prompts import RawlistPrompt as rawlist
from InquirerPy.prompts import SecretPrompt as secret
ユーザから文字列を受け取る簡単な例から見てみましょう。
同じ結果になるので実行例は省略しています。
from InquirerPy import inquirer
name = inquirer.text(message="What's your name: ").execute()
print(name)
このケースでは、互換APIでは、prompt()
に与える辞書の質問のタイプとして input
を使用しますが、新しいAPIでは、
text()
メソッドを使用します。タイプの名前とは異なるので注意してください。
連続する質問は、互換APIでは質問の辞書をリストで与えますが、新しいAPIでは質問は独立しているため、
都度Inquirer
クラスのメソッドを呼び出します。
from InquirerPy import inquirer
first_name = inquirer.text(
message="What's your first name: ").execute()
if first_name != '':
last_name = inquirer.text(
message="What's your last name: ").execute()
else:
last_name = ''
print(first_name, last_name)
入力補完
ユーザから文字列を受け取るときに、予め登録した文字列に部分合致すると補完文字列をポップアップ表示します。
タブキーを押下すると残りの入力を補完したデータを返します。
from InquirerPy import inquirer
answer = inquirer.text(
message="Which company would you like to apply:",
completer={
"Google": None,
"Facebook": None,
"Amazon": None,
"Netflix": None,
"Apple": None,
"Microsoft": None,
},
multicolumn_complete=True,
).execute()
print(answer)
transformer, filter, validate
ユーザの入力を加工したり、結果を変換したり、データ検証を行わせることができます。
from InquirerPy import inquirer
from InquirerPy.validator import NumberValidator
answer = inquirer.text(
message="What's your salary expectation(k):",
transformer=lambda result: "%sk" % result,
filter=lambda result: int(result) * 1000,
validate=NumberValidator(),
).execute()
print(answer)
ListPrompt()
選択肢を表示してユーザに選ばせる基本的なUIです。
class ListPrompt(BaseListPrompt):
def __init__(
self,
message: Union[str, Callable[[SessionResult], str]],
choices: Union[Callable[[SessionResult], List[Any]], List[Any]],
default: Any = None,
style: InquirerPyStyle = None,
vi_mode: bool = False,
qmark: str = "?",
pointer: str = INQUIRERPY_POINTER_SEQUENCE,
instruction: str = "",
transformer: Callable[[Any], Any] = None,
filter: Callable[[Any], Any] = None,
height: Union[int, str] = None,
max_height: Union[int, str] = None,
multiselect: bool = False,
marker: str = INQUIRERPY_POINTER_SEQUENCE,
validate: Union[Callable[[Any], bool], Validator] = None,
invalid_message: str = "Invalid input",
keybindings: Dict[str, List[Dict[str, Any]]] = None,
show_cursor: bool = True,
) -> None:
from InquirerPy import inquirer
from InquirerPy.separator import Separator
action = inquirer.select(
message="Select an action:",
choices=["Upload", "Download", {"name": "Exit", "value": None}],
default=None,
# multiselect=True,
).execute()
print(action)
Separator
Separator
クラスのインスタンスオブジェクトを与えると、一覧するリストに区切り文字を表示するようになります。
from InquirerPy import inquirer
from InquirerPy.separator import Separator
region = inquirer.select(
message="Select regions:",
choices=[
{"name": "Sydney", "value": "ap-southeast-2"},
{"name": "Singapore", "value": "ap-southeast-1"},
Separator(),
"us-east-1",
Separator(line='*' * 15),
"us-east-2",
],
multiselect=True,
transformer=lambda result: "%s region%s selected"
% (len(result), "s" if len(result) > 1 else ""),
).execute()
print(region)
RawlistPrompt()
自動的に数字のショートカットを適用させたリストプロンプトです。
各選択肢の前には数字が付加されます。この数字をショートカットキーとして使用して選択肢にジャンプしたり、通常の上下キーバインドで移動したりすることができます。
class RawlistPrompt(BaseListPrompt):
def __init__(
self,
message: Union[str, Callable[[SessionResult], str]],
choices: Union[Callable[[SessionResult], List[Any]], List[Any]],
default: Any = None,
separator: str = ")",
style: InquirerPyStyle = None,
vi_mode: bool = False,
qmark: str = "?",
pointer: str = " ",
instruction: str = "",
transformer: Callable[[Any], Any] = None,
filter: Callable[[Any], Any] = None,
height: Union[int, str] = None,
max_height: Union[int, str] = None,
multiselect: bool = False,
marker: str = INQUIRERPY_POINTER_SEQUENCE,
validate: Union[Callable[[Any], bool], Validator] = None,
invalid_message: str = "Invalid input",
keybindings: Dict[str, List[Dict[str, Any]]] = None,
show_cursor: bool = True,
) -> None:
from InquirerPy import inquirer
fruit = inquirer.rawlist(
message="Pick your favourites:",
choices=[
"Apple",
"Orange",
"Peach",
"Cherry",
"Melon",
"Strawberry",
"Grapes",
],
default=3,
multiselect=True,
transformer=lambda result: ", ".join(result),
validate=lambda result: len(result) > 1,
invalid_message="Minimum 2 selections",
).execute()
print(fruit)
ExpandPrompt()
コンパクト(compact)と拡張(expand)の2種類のUIを持つリストプロンプトです。
はじめはコンパクトな状態で表示されますが、h
キーを使って拡張することができます。展開後は、通常のリストナビゲーションキーが有効になります。
class ExpandPrompt(BaseListPrompt):
def __init__(
self,
message: Union[str, Callable[[SessionResult], str]],
choices: Union[Callable[[SessionResult], List[Any]], List[Any]],
default: Any = "",
style: InquirerPyStyle = None,
vi_mode: bool = False,
qmark: str = "?",
pointer: str = " ",
separator: str = ")",
help_msg: str = "Help, list all choices",
expand_pointer: str = INQUIRERPY_POINTER_SEQUENCE,
instruction: str = "",
transformer: Callable[[Any], Any] = None,
filter: Callable[[Any], Any] = None,
height: Union[int, str] = None,
max_height: Union[int, str] = None,
multiselect: bool = False,
marker: str = INQUIRERPY_POINTER_SEQUENCE,
validate: Union[Callable[[Any], bool], Validator] = None,
invalid_message: str = "Invalid input",
keybindings: Dict[str, List[Dict[str, Any]]] = None,
show_cursor: bool = True,
) -> None:
from InquirerPy import inquirer
fruits_choice = [
{"key": "a", "name": "Apple", "value": "Apple"},
{"key": "c", "name": "Cherry", "value": "Cherry"},
{"key": "o", "name": "Orange", "value": "Orange"},
{"key": "p", "name": "Peach", "value": "Peach"},
{"key": "m", "name": "Melon", "value": "Melon"},
{"key": "s", "name": "Strawberry", "value": "Strawberry"},
{"key": "g", "name": "Grapes", "value": "Grapes"},
]
fruit = inquirer.expand(
message="Pick your favourite:",
choices=fruits_choice,
default="o"
).execute()
print(fruit)
CheckboxPrompt()
チェックボックスを表示するリストプロンプト。
マルチセレクトが可能な他のリストプロンプトでは、最小選択数は常に1です(ユーザが何も選択しない場合、現在のハイライトされた選択肢が選択されます)。チェックボックスプロンプトでは、現在のハイライトされた選択肢は自動的に選択されません。
class CheckboxPrompt(BaseListPrompt):
def __init__(
self,
message: Union[str, Callable[[SessionResult], str]],
choices: Union[Callable[[SessionResult], List[Any]], List[Any]],
default: Any = None,
style: InquirerPyStyle = None,
vi_mode: bool = False,
qmark: str = "?",
pointer: str = INQUIRERPY_POINTER_SEQUENCE,
enabled_symbol: str = INQUIRERPY_FILL_HEX_SEQUENCE,
disabled_symbol: str = INQUIRERPY_EMPTY_HEX_SEQUENCE,
instruction: str = "",
transformer: Callable[[Any], Any] = None,
filter: Callable[[Any], Any] = None,
height: Union[int, str] = None,
max_height: Union[int, str] = None,
validate: Union[Callable[[Any], bool], Validator] = None,
invalid_message: str = "Invalid input",
keybindings: Dict[str, List[Dict[str, Any]]] = None,
show_cursor: bool = True,
) -> None:
from InquirerPy import inquirer
from InquirerPy.separator import Separator
region_choice = [
Separator(),
{"name": "Sydney", "value": "ap-southeast-2", "enabled": False},
{"name": "Singapore", "value": "ap-southeast-1", "enabled": True},
Separator(),
"us-east-1",
"us-west-1",
Separator(),
]
regions = inquirer.checkbox(
message="Select regions:",
choices=region_choice,
cycle=False,
transformer=lambda result: "%s region%s selected"
% (len(result), "s" if len(result) > 1 else ""),
).execute()
print(regions)
ConfirmPrompt()
ユーザーの確認を取るためのプロンプトです。
class ConfirmPrompt(BaseSimplePrompt):
def __init__(
self,
message: Union[str, Callable[[SessionResult], str]],
style: InquirerPyStyle = None,
default: Union[bool, Callable[[Dict[str, Any]], bool]] = False,
qmark: str = "?",
transformer: Callable[[bool], Any] = None,
filter: Callable[[bool], Any] = None,
**kwargs
) -> None:
from InquirerPy import inquirer
proceed, service, confirm = False, False, False
proceed = inquirer.confirm(message="Proceed?", default=True).execute()
if proceed:
service = inquirer.confirm(message="Require 1 on 1?").execute()
if service:
confirm = inquirer.confirm(message="Confirm?").execute()
print(f'proceed:{proceed}, service:{service}, confirm:{confirm}')
InputPromot()
ユーザーにいくつかのテキスト値を入力させる入力プロンプトです。Ctrl-Space
もしくは TABキーを押下すると、completter
が与えられている場合は、自動コンプリートのポップアップが強制的に表示されます。
class InputPrompt(BaseSimplePrompt):
def __init__(
self,
message: Union[str, Callable[[SessionResult], str]],
style: InquirerPyStyle = None,
vi_mode: bool = False,
default: Union[str, Callable[[SessionResult], str]] = "",
qmark: str = "?",
completer: Union[Dict[str, Optional[str]], Completer] = None,
multicolumn_complete: bool = False,
multiline: bool = False,
validate: Union[Callable[[str], bool], Validator] = None,
invalid_message: str = "Invalid input",
transformer: Callable[[str], Any] = None,
filter: Callable[[str], Any] = None,
**kwargs,
) -> None:
from prompt_toolkit.validation import Validator, ValidationError
from InquirerPy import inquirer
class AgeValidator(Validator):
def validate(self, document):
if int(document.text) < 18:
raise ValidationError(
message='Too yound.',
cursor_position=len(document.text)) # Move cursor to end
first_name = inquirer.text(message="Waht's your first name?").execute()
default_name = 'Bauer' if first_name == 'Jack' else ''
last_name = inquirer.text(message="What's your last name?",
default=default_name).execute()
age = inquirer.text(message="How old are you?",
validate=AgeValidator()).execute()
print(f'first_name: {first_name}, last_name: {last_name}, age: {age}')
SecretPrompt()
ユーザーにパスワードなどの秘密の値を入力させるための入力プロンプトです。inputタイプと似ていますがユーザが入力した文字列は画面には表示されません。
class SecretPrompt(InputPrompt):
def __init__(
self,
message: Union[str, Callable[[SessionResult], str]],
style: InquirerPyStyle = None,
default: Union[str, Callable[[SessionResult], str]] = "",
qmark: str = "?",
vi_mode: bool = False,
validate: Union[Validator, Callable[[str], bool]] = None,
invalid_message: str = "Invalid input",
transformer: Callable[[str], Any] = None,
filter: Callable[[str], Any] = None,
**kwargs
) -> None:
from InquirerPy import inquirer
from InquirerPy.validator import PasswordValidator
original_password = "P@ssw0rd123"
old_password = inquirer.secret(
message="Old password:",
transformer=lambda _: "[hidden]",
validate=lambda text: text == original_password,
invalid_message="Wrong password",
).execute()
new_password = inquirer.secret(
message="New password:",
validate=PasswordValidator(length=8, cap=True, special=True, number=True),
transformer=lambda _: "[hidden]",
).execute()
confirm = inquirer.confirm(message="Confirm?", default=True).execute()
msg = f'old_password: {old_password}, '
msg += f'new_password: {new_password}, '
msg += f'confirm: {confirm}'
print(f'{msg}')
FilePathPrompt()
class FilePathPrompt(InputPrompt)"
def __init__(
self,
message: Union[str, Callable[[SessionResult], str]],
style: InquirerPyStyle = None,
vi_mode: bool = False,
default: Union[str, Callable[[SessionResult], str]] = "",
qmark: str = "?",
amark: str = "?",
instruction: str = "",
multicolumn_complete: bool = False,
validate: Union[Callable[[str], bool], Validator] = None,
invalid_message: str = "Invalid input",
only_directories: bool = False,
only_files: bool = False,
transformer: Callable[[str], Any] = None,
filter: Callable[[str], Any] = None,
wrap_lines: bool = True,
session_result: SessionResult = None,
**kwargs,
) -> None:
from pathlib import Path
from InquirerPy import inquirer
from InquirerPy.validator import PathValidator
src_path = inquirer.filepath(
message="Enter file to upload:",
default=str(Path('/tmp')),
validate=PathValidator(is_file=True, message="Input is not a file"),
only_files=True,
).execute()
dest_path = inquirer.filepath(
message="Enter path to download:",
validate=PathValidator(is_dir=True, message="Input is not a directory"),
only_directories=True,
).execute()
print(f'src_path: {src_path}, dest_path: {dest_path}')
FuzzyPrompt()
入寮文字列のあいまい検索機能を持つリストプロンプトです。選択肢の数が約300kに達するまでは比較的良好な性能を発揮します。
class FuzzyPrompt(BaseComplexPrompt):
def __init__(
self,
message: Union[str, Callable[[SessionResult], str]],
choices: Union[Callable[[SessionResult], List[Any]], List[Any]],
default: Union[str, Callable[[SessionResult], str]] = "",
pointer: str = INQUIRERPY_POINTER_SEQUENCE,
style: InquirerPyStyle = None,
vi_mode: bool = False,
qmark: str = "?",
transformer: Callable[[Any], Any] = None,
filter: Callable[[Any], Any] = None,
instruction: str = "",
multiselect: bool = False,
prompt: str = INQUIRERPY_POINTER_SEQUENCE,
marker: str = INQUIRERPY_POINTER_SEQUENCE,
border: bool = True,
info: bool = True,
height: Union[str, int] = None,
max_height: Union[str, int] = None,
validate: Union[Callable[[Any], bool], Validator] = None,
invalid_message: str = "Invalid input",
keybindings: Dict[str, List[Dict[str, Any]]] = None,
) -> None:
from contextlib import ExitStack
from pathlib import Path
from InquirerPy import inquirer, prompt
_WORDFILE_ = 'sample.txt'
def get_choices(_):
p = Path.cwd().joinpath(_WORDFILE_)
choices = []
with ExitStack() as stack:
if not p.exists():
raise Exception('%s: wordfile missing!', _WORDFILE_)
else:
file = stack.enter_context(p.open("r"))
for line in file.readlines():
choices.append(line[:-1])
return choices
action = inquirer.fuzzy(
message="Select actions:",
choices=["hello", "weather", "what", "whoa", "hey", "yo"],
default="he",
max_height="70%",
).execute()
words = inquirer.fuzzy(
message="Select preferred words:",
choices=get_choices,
multiselect=True,
validate=lambda result: len(result) > 1,
invalid_message="minimum 2 selections",
max_height="70%",
).execute()
print(f'action: {action}, words: {words}')
PyInquirer との非互換について
InquierPy と PyInquirer とで互換性のないものについて説明します。
-
editorプロンプト
inquirerpy は今時点では、editorプロンプトをサポートしていません。 -
checkboxプロンプトのAPI
CheckBoxPrompt()のパラメタ名が異なっています。
CheckboxPrompt()パラメタの比較
PyInquirer | InquirerPy |
---|---|
pointer_sign | pointer |
selected_sign | enabled_symbol |
unselected_sign | disabled_symbol |
- Syle
PyInquirerのスタイルのキーのうち次のものは名前が変わっています。
PyInquirer | InquirerPy |
---|---|
selected | pointer |
色やスタイルなどのカスタマイズはprompt_toolkit のスタイルを利用して行います。
所感
利用頻度は少ないかもしれませんが、対話型インタフェースをコマンドに実装するときに便利に使えるはずです。
editor
など複数行入力などのインタフェースを使いたい場合は prompt_toolkit を使うことになるでしょう。
それでも、基本的な会話形インタフェースを構築するには十分な機能が提供されていて、見た目(Look&Feel) もシンプルで好感が持てます。
余談ですが、InquirerPy を使ってEmberblast
というクラシカルなRPGを作った人もいますね。