في نظام Python البيئي ، هناك العديد من الحزم لتطبيقات CLI ، كتلك الشائعة ، مثل Click ، وليس الكثير. أكثر
المقالات شيوعًا تم بحثها في
مقال سابق ، ولكن سيتم عرض هنا غير معروف ، ولكن ليس أقل إثارة للاهتمام.

كما في الجزء الأول ، سيتم كتابة نص وحدة التحكم لمكتبة todolib لكل مكتبة في Python 3.7. بالإضافة إلى ذلك ، سيتم كتابة اختبار تافه مع هذه التركيبات لكل تطبيق:
@pytest.fixture(autouse=True) def db(monkeypatch): """ monkeypatch , """ value = {"tasks": []} monkeypatch.setattr(todolib.TodoApp, "save", lambda _: ...) monkeypatch.setattr(todolib.TodoApp, "get_db", lambda _: value) return value @pytest.yield_fixture(autouse=True) def check(db): """ """ yield assert db["tasks"] and db["tasks"][0]["title"] == "test" # , EXPECTED = "Task 'test' created with number 1.\n"
كل شفرة المصدر متاحة في
هذا المستودع .
جرف
جيثبالوثائقسمع الكثيرون عن OpenStack ، منصة مفتوحة المصدر لـ IaaS. تتم كتابة معظمها في Python ، بما في ذلك أدوات مساعدة وحدة التحكم التي كانت تكرر وظيفة CLI لفترة طويلة. استمر هذا الأمر حتى ظهر جرف ، أو إطار صياغة سطر الأوامر ، كإطار عمل مشترك. مع ذلك ، قام مطورو Openstack بدمج حزم مثل python-novaclient و python-swiftclient و python-keystoneclient في برنامج
openstack واحد.
الأوامريشبه أسلوب التصريح عن الأوامر الاسمنت و cleo: argparse كمحلل للمعلمات ، ويتم إنشاء الأوامر نفسها من خلال وراثة فئة الأوامر. في الوقت نفسه ، هناك امتدادات صغيرة لفئة الأوامر ، مثل Lister ، والتي تقوم بتنسيق البيانات بشكل مستقل.
شفرة المصدر from cliff import command from cliff.lister import Lister class Command(command.Command): """Command with a parser shortcut.""" def get_parser(self, prog_name): parser = super().get_parser(prog_name) self.extend_parser(parser) return parser def extend_parser(self, parser): ... class Add(Command): """Add new task.""" def extend_parser(self, parser): parser.add_argument("title", help="Task title") def take_action(self, parsed_args): task = self.app.todoapp.add_task(parsed_args.title) print(task, "created with number", task.number, end=".\n") class Show(Lister, Command): """Show current tasks.""" def extend_parser(self, parser): parser.add_argument( "--show-done", action="store_true", help="Include done tasks" ) def take_action(self, parsed_args): tasks = self.app.todoapp.list_tasks(show_done=parsed_args.show_done)
التطبيق والرئيسيفئة التطبيق لديها
طرق تهيئة و
تنظيف ، في حالتنا ، يقومون بتهيئة التطبيق وحفظ البيانات.
شفرة المصدر from cliff import app from cliff.commandmanager import CommandManager from todolib import TodoApp, __version__ class App(app.App): def __init__(self):
أمثلة العمل igor$ ./todo_cliff.py add "sell the old laptop" Using database file /home/igor/.local/share/todoapp/db.json Task 'sell the old laptop' created with number 0. Saving database to a file /home/igor/.local/share/todoapp/db.json
تسجيل الخروج من المربع! وإذا نظرت إلى
أسفل الغطاء ، يمكنك أن ترى أنه تم بطريقة ذكية: المعلومات في stdout ، والتحذير / الخطأ في stderr ، وإذا لزم الأمر ، يتم تعطيل بواسطة العلم
الهادئ .
igor$ ./todo_cliff.py -q show +--------+----------------------+--------+ | Number | Title | Status | +--------+----------------------+--------+ | 1 | sell the old laptop | ✘ | +--------+----------------------+--------+
كما ذكرنا سابقًا ، تقوم Lister بتنسيق البيانات ، لكن الجدول لا يقتصر على:
igor$ ./todo_cliff.py -q show -f json --noindent [{"Number": 0, "Title": "sell old laptop", "Status": "\u2718"}]
بالإضافة إلى json and table ، تتوفر yaml و csv.
يوجد أيضًا تتبع مخفي افتراضي:
igor$ ./todo_cliff.py -q remove 3 No such task.
بحث REPL وبحث غامض آخر يُعرف أيضًا باسم
بحث غامض :
igor$ ./todo_cliff.py -q (todo_cliff) help Shell commands (type help %topic%): =================================== alias exit history py quit shell unalias edit help load pyscript set shortcuts Application commands (type help %topic%): ========================================= add complete done help remove show (todo_cliff) whow todo_cliff: 'whow' is not a todo_cliff command. See 'todo_cliff --help'. Did you mean one of these? show
تجريبالأمر بسيط: يتم إنشاء كائن تطبيق ويُطلق عليه أيضًا () يُرجع رمز الخروج.
def test_cliff(capsys): app = todo_cliff.App() code = app.run(["add", "test"]) assert code == 0 out, _ = capsys.readouterr() assert out == EXPECTED
إيجابيات وسلبياتالايجابيات:
- وسائل الراحة المختلفة من خارج منطقة الجزاء.
- التي وضعتها OpenStack.
- الوضع التفاعلي
- القابلية للتوسعة من خلال نقطة إدخال setuptools و CommandHook ؛
- Sphinx plugin للتوثيق التلقائي لـ CLI ؛
- إكمال القيادة (باش فقط) ؛
سلبيات:
- وثائق صغيرة ، والتي تتكون أساسا من مثال مفصل ولكن واحد ؛
تم ملاحظة خطأ آخر: في حالة حدوث خطأ عند إخفاء تتبع المكدس ، يكون رمز الخروج دائمًا صفريًا.
PLAC
جيثبالوثائقللوهلة الأولى ، يبدو أن Plac يشبه Fire ، ولكنه
في الواقع مثل Fire ، الذي يخفي نفس الحجة وأكثر من ذلك بكثير تحت الغطاء.
يتبع Plac ، مستشهداً بالوثائق ، "المبدأ القديم لعالم الكمبيوتر:
يجب أن تحل البرامج فقط الحالات العادية والبساطة يجب أن تظل بسيطة ، ويمكن تحقيقها في نفس الوقت ." يستخدم مؤلف الإطار Python منذ أكثر من تسع سنوات وقد كتبه مع توقع حل "99.9٪ من المهام".
الأوامر والرئيسيةالانتباه إلى التعليقات التوضيحية في العرض وطرق التنفيذ: هذه هي الطريقة التي يوزع بها Plac المعلمات وتساعد الوسيطة ، على التوالي.
شفرة المصدر import plac import todolib class TodoInterface: commands = "add", "show", "done", "remove" def __init__(self): self.app = todolib.TodoApp.fromenv() def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.app.save() def add(self, task): """ Add new task. """ task = self.app.add_task(title=task) print(task, "created with number", task.number, end=".\n") def show(self, show_done: plac.Annotation("Include done tasks", kind="flag")): """ Show current tasks. """ self.app.print_tasks(show_done=show_done) def done(self, number: "Task number"): """ Mark task as done. """ task = self.app.task_done(number=int(number)) print(task, "marked as done.") def remove(self, number: "Task number"): """ Remove task from the list. """ task = self.app.remove_task(number=int(number)) print(task, "removed from the list.") if __name__ == "__main__": plac.Interpreter.call(TodoInterface)
تجريبلسوء الحظ ، لا يسمح اختبار رمز الخروج Plac. لكن اختبار نفسه حقيقي:
def test_plac(capsys): plac.Interpreter.call(todo_plac.TodoInterface, arglist=["add", "test"]) out, _ = capsys.readouterr() assert out == EXPECTED
إيجابيات وسلبياتالايجابيات:
- استخدام بسيط
- الوضع التفاعلي مع دعم readline ؛
- API مستقرة
- وثائق ممتازة
ولكن الشيء الأكثر إثارة للاهتمام حول Plac مخفي في
الاستخدام المتقدم :
- تنفيذ العديد من الأوامر في مؤشرات الترابط والعمليات الفرعية ؛
- الحوسبة المتوازية
- خادم التلنت
سلبيات:
- لا يمكنك اختبار رمز الخروج ؛
- سوء حياة المشروع.
بلومبوم plumbum
جيثبالوثائقوثائق CLIبلومب ، في الواقع ، ليس مثل هذا الإطار غير المعروف - ما يقرب من 2000 نجم ، وهناك شيء ما يستحق العناء له ، لأنه ، على نحو تقريبي ، ينفذ بناء جملة UNIX Shell. حسنا ، مع إضافات:
>>> from plumbum import local >>> output = local["ls"]() >>> output.split("\n")[:3] ['console_examples.egg-info', '__pycache__', 'readme.md']
الأوامر والرئيسيةيحتوي Plumbum أيضًا على أدوات لـ CLI ، ولا يعني أنها مجرد إضافة: هناك nargs والأوامر والألوان:
شفرة المصدر from plumbum import cli, colors class App(cli.Application): """Todo notes on plumbum.""" VERSION = todolib.__version__ verbosity = cli.CountOf("-v", help="Increase verbosity") def main(self, *args): if args: print(colors.red | f"Unknown command: {args[0]!r}.") return 1 if not self.nested_command:
تجريبيختلف اختبار التطبيقات على Plumbum عن التطبيقات الأخرى ، فيما عدا الحاجة إلى تمرير اسم التطبيق أيضًا ، أي الحجة الأولى:
def test_plumbum(capsys): _, code = todo_plumbum.App.run(["todo_plumbum", "add", "test"], exit=False) assert code == 0 out, _ = capsys.readouterr() assert out == "Task test created with number 0.\n"
إيجابيات وسلبياتالايجابيات:
- مجموعة أدوات ممتازة للعمل مع فرق خارجية ؛
- دعم الأنماط والألوان ؛
- API مستقرة
- الحياة النشطة للمشروع ؛
لم يلاحظ أي عيوب.
CMD2
جيثبالوثائقبادئ ذي بدء ، cmd2 هو امتداد على
cmd من المكتبة القياسية ،
أي الغرض منه هو للتطبيقات التفاعلية. ومع ذلك ، يتم تضمينه في المراجعة ، لأنه يمكن أيضًا تكوينه لوضع CLI العادي.
الأوامر والرئيسيةيتطلب cmd2 الامتثال لقاعدة معينة: يجب أن تبدأ الأوامر بالبادئة
do_ ، ولكن خلاف ذلك ، كل شيء واضح:
الوضع التفاعلي import cmd2 import todolib class App(cmd2.Cmd): def __init__(self, **kwargs): super().__init__(**kwargs) self.todoapp = todolib.TodoApp.fromenv() def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.todoapp.save() def do_add(self, title): """Add new task.""" task = self.todoapp.add_task(str(title)) self.poutput(f"{task} created with number {task.number}.") def do_show(self, show_done): """Show current tasks.""" self.todoapp.print_tasks(bool(show_done)) def do_done(self, number): """Mark task as done.""" task = self.todoapp.task_done(int(number)) self.poutput(f"{task} marked as done.") def do_remove(self, number): """Remove task from the list.""" task = self.todoapp.remove_task(int(number)) self.poutput(f"{task} removed from the list.") def main(**kwargs): with App(**kwargs) as app: app.cmdloop() if __name__ == '__main__': main()
وضع غير تفاعليالوضع العادي يتطلب حركة اضافية قليلا.
على سبيل المثال ، يجب عليك الرجوع إلى argparse وكتابة منطق الحالة عندما يتم استدعاء البرنامج النصي بدون معلمات. والآن تحصل الأوامر على
argparse.Namespace .
يتم الحصول على المحلل اللغوي من مثال argparse مع الإضافات الصغيرة - الآن المحللون الفرعيون هم سمات
ArgumentParser الرئيسية.
import cmd2 from todo_argparse import get_parser parser = get_parser(progname="todo_cmd2_cli") class App(cmd2.Cmd): def __init__(self, **kwargs): super().__init__(**kwargs) self.todoapp = todolib.TodoApp.fromenv() def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.todoapp.save() def do_add(self, args): """Add new task.""" task = self.todoapp.add_task(args.title) self.poutput(f"{task} created with number {task.number}.") def do_show(self, args): """Show current tasks.""" self.todoapp.print_tasks(args.show_done) def do_done(self, args): """Mark task as done.""" task = self.todoapp.task_done(args.number) self.poutput(f"{task} marked as done.") def do_remove(self, args): """Remove task from the list.""" task = self.todoapp.remove_task(args.number) self.poutput(f"{task} removed from the list.") parser.add.set_defaults(func=do_add) parser.show.set_defaults(func=do_show) parser.done.set_defaults(func=do_done) parser.remove.set_defaults(func=do_remove) @cmd2.with_argparser(parser) def do_base(self, args): func = getattr(args, "func", None) if func: func(self, args) else: print("No command provided.") print("Call with --help to get available commands.") def main(argv=None): with App() as app: app.do_base(argv or sys.argv[1:]) if __name__ == '__main__': main()
تجريبسيتم اختبار البرنامج النصي التفاعلي فقط.نظرًا لأن اختبار التطبيقات التفاعلية يتطلب عددًا كبيرًا من الموارد البشرية والوقت ، فقد حاول مطورو cmd2 حل هذه المشكلة بمساعدة
النسخ - الملفات النصية مع أمثلة المدخلات والمخرجات المتوقعة. على سبيل المثال:
(Cmd) add test Task 'test' created with number 0.
وبالتالي ، كل ما هو مطلوب هو نقل قائمة الملفات مع النسخ إلى
التطبيق :
def test_cmd2(): todo_cmd2.main(transcript_files=["tests/transcript.txt"])
إيجابيات وسلبياتالايجابيات:
- واجهة برمجة تطبيقات جيدة للتطبيقات التفاعلية ؛
- النامية بنشاط في السنوات الأخيرة ؛
- نهج الأصلي للاختبار.
- وثائق جيدة.
سلبيات:
- يتطلب argparse ورمز إضافي عند كتابة التطبيقات غير التفاعلية ؛
- API غير مستقر
- في بعض الأماكن تكون الوثائق فارغة.
المكافأة: Urwid
جيثبالوثائقالبرنامج التعليميأمثلة البرنامجUrwid هو إطار من عالم مختلف قليلاً - عن لعنات وشاشة npyscreen ، أي من وحدة التحكم / واجهة المستخدم الطرفية. ومع ذلك ، يتم تضمينه في الاستعراض كمكافأة ، لأنه ، في رأيي ، يستحق الاهتمام.
التطبيق والفرقيحتوي Urwid على عدد كبير من عناصر واجهة المستخدم ، لكنه لا يحتوي على مفاهيم مثل نافذة أو أدوات بسيطة للوصول إلى عناصر واجهة التعامل المجاورة. وبالتالي ، إذا كنت ترغب في الحصول على نتيجة جميلة ، فستحتاج إلى تصميم و / أو استخدام حزم أخرى مدروس ، وإلا فسيتعين عليك نقل البيانات في سمات الأزرار ، كما يلي:
شفرة المصدر import urwid from urwid import Button import todolib class App(urwid.WidgetPlaceholder): max_box_levels = 4 def __init__(self): super().__init__(urwid.SolidFill()) self.todoapp = None self.box_level = 0 def __enter__(self): self.todoapp = todolib.TodoApp.fromenv() self.new_menu( "Todo notes on urwid",
الأوامر app = App() def menu(title, *items) -> urwid.ListBox: body = [urwid.Text(title), urwid.Divider()] body.extend(items) return urwid.ListBox(urwid.SimpleFocusListWalker(body)) def add(button): edit = urwid.Edit("Title: ") def handle(button): text = edit.edit_text app.todoapp.add_task(text) app.popup("Task added") app.new_menu("New task", edit, Button("Add", on_press=handle)) def list_tasks(button): tasks = app.todoapp.list_tasks(show_done=True) buttons = [] for task in tasks: status = "done" if task.done else "not done" text = f"{task.title} [{status}]"
رئيسي if __name__ == "__main__": try: with app: urwid.MainLoop(app).run() except KeyboardInterrupt: pass
إيجابيات وسلبياتالايجابيات:
- واجهة برمجة تطبيقات رائعة لكتابة تطبيقات TUI المختلفة ؛
- تاريخ التطوير الطويل (منذ 2010) و API المستقر ؛
- العمارة المختصة
- وثائق جيدة ، وهناك أمثلة.
سلبيات:
- كيفية اختبار غير واضح. فقط tmux إرسال مفاتيح يتبادر إلى الذهن.
- أخطاء غير إعلامية عندما لا يتم ترتيب الحاجيات بشكل صحيح.
* * *
يشبه Cliff إلى حد كبير Cleo و Cement وهو جيد عمومًا للمشاريع الكبيرة.
أنا شخصياً لا أجرؤ على استخدام Plac ، لكنني أوصي بقراءة التعليمات البرمجية المصدر.
يحتوي Plumbum على مجموعة أدوات CLI سهلة الاستخدام وواجهة برمجة تطبيقات رائعة لتنفيذ الأوامر الأخرى ، لذلك إذا كنت تعيد كتابة نصوص shell في Python ، فهذا هو ما تحتاجه.
cmd2 مناسب تمامًا كأساس للتطبيقات التفاعلية ولأولئك الذين يرغبون في الترحيل من cmd القياسي.
ويتميز Urwid بتطبيقات وحدة تحكم جميلة وسهلة الاستخدام.
لم يتم تضمين الحزم التالية في المراجعة:
- aioconsole - لا يوجد وضع غير تفاعلي ؛
- pyCLI - لا يوجد دعم للأوامر الفرعية ؛
- Clint - لا يوجد دعم للأوامر الفرعية ، مستودع في الأرشيف ؛
- سطر الأوامر - إنه قديم جدًا (الإصدار الأخير في 2009) وغير مهتم ؛
- CLIArgs - القديمة (الإصدار الأخير في 2010)
- مفتش - قديم نسبيا (الإصدار الأخير في 2015)