Git Product home page Git Product logo

python-nubia's Introduction

python-nubia

This project has been archived, the README below is kept for archiving purposes. See #88 for more information.


Support Ukraine Nubia Build Coverage PyPI version

Nubia is a lightweight framework for building command-line applications with Python. It was originally designed for the “logdevice interactive shell (aka. ldshell)” at Facebook. Since then it was factored out to be a reusable component and several internal Facebook projects now rely on it as a quick and easy way to get an intuitive shell/cli application without too much boilerplate.

Nubia is built on top of python-prompt-toolkit which is a fantastic toolkit for building interactive command-line applications.

Disclaimer: Nubia is beta for non-ldshell use-cases. Some of the design decisions might sound odd but they fit the ldshell usecase perfectly. We are continuously making changes to make it more consistent and generic outside of the ldshell use-case. Until a fully stable release is published, use it on your own risk.

See the CONTRIBUTING file for how to help out.

If you are curious on the origins of the name, checkout Nubia on Wikipedia with its unique and colourful architecture.

Key Features

  • Interactive mode that offers fish-style auto-completion
  • CLI mode that gets generated from your functions and classes.
  • Optional bash/zsh completions via an external utility ‘nubia-complete’ (experimental)
  • A customisable status-bar in interactive mode.
  • An optional IPython-based interactive shell
  • Arguments with underscores are automatically hyphenated
  • Python3 type annotations are used for input type validation

Interactive mode

The interactive mode in Nubia is what makes it unique. It is very easy to build a unique shell for your program with zero overhead. The interactive shell in its simplistic form offers automatic completions for commands, sub-commands, arguments, and values. It also offers a great deal of control for developers to take control over auto-completions, even for commands that do not fall under the typical format. An example is the “select” command in ldshell which is expressed as a SQL-query. We expect that most use cases of Nubia will not need such control and the AutoCommand will be enough without further customisation.

If you start a nubia-based program without a command, it automatically starts an interactive shell. The interactive mode looks like this:

Interactive Demo

Non-interactive mode

The CLI mode works exactly like any traditional unix-based command line utility. Non-interactive Demo

Have your @command decorated function return an int to send that value as the Unix return code for your non interactive CLI.

Examples

It starts with a function like this:

import socket
import typing

from termcolor import cprint
from nubia import argument, command, context

@command
@argument("hosts", description="Hostnames to resolve", aliases=["i"])
@argument("bad_name", name="nice", description="testing")
async def lookup(hosts: typing.List[str], bad_name: int) -> int:
    """
    This will lookup the hostnames and print the corresponding IP addresses
    """
    ctx = context.get_context()

    if not hosts:
        cprint("No hosts supplied via --hosts")
        return 1

    print(f"hosts: {hosts}")
    cprint(f"Verbose? {ctx.verbose}")

    for host in hosts:
        cprint(f"{host} is {socket.gethostbyname(host)}")

    return 0

Requirements

Nubia-based applications require Python 3.7+ and works with both Mac OS X or Linux. While in theory it should work on Windows, it has never been tried.

Installing Nubia

If you are installing nubia for your next project, you should be able to easily use pip for that:

pip install python-nubia

Building Nubia from source

poetry build

Running example in virtualenv:

We recommend setting up a separate Python environment using a tool like virtualenv, pyenv-virtualenv, or poetry shell.

If you would like to run the example, install the dependencies and run the example module as a script.

poetry install
cd example
python -m nubia_example

To run the unit tests:

poetry run nosetests

Getting Started

See the getting started guide to learn how to build a simple application with Nubia.

License

python-nubia is BSD licensed, as found in the LICENSE file.

python-nubia's People

Contributors

adamjernst avatar aetherunbound avatar ahmedsoliman avatar andreasbackx avatar cooperlees avatar david-y-lam avatar davidvigier avatar ddutt avatar dmitryvinn avatar docent-net avatar facebook-github-bot avatar fried avatar gfpeltier avatar lanza avatar madelyneriksen avatar marimar96 avatar michael-k avatar mohamedbassem avatar selleslaghgianni avatar stanislavglebik avatar stroxler avatar timgates42 avatar toti1212 avatar vrizopoulos avatar xtenzo avatar yoavtzelnick avatar zepheus avatar zpao avatar zpriddy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-nubia's Issues

can't extend example with self commands

Hi!
Great work! Can u help me with my little trouble?
I try to add my own command to the example and later I don't see it in the list of commands. (
i add this fn into example/commands/sample_commans.py:
@command
@argument("number", type=int)
async def twiced(number):
"Calculates the triple of the input value"
cprint("Input is {}".format(number))
cprint("Type of input is {}".format(type(number)))
cprint("{} * 2 = {}".format(number, number * 2))
await asyncio.sleep(2)

in interactive:
(venv) sabrus@fly:~/GithubProjects/python-nubia$ python example/nubia_example.py
Logging to /tmp/nubia_example-k5zlddp5
sabrus> twiced

Unknown Command 'twiced', type :help to see all available commands

what i missed to do?
in debug i see what twiced cmd is founded and handled, but at runtime cmd is absent...
Thank for your Job!

Configurable editing mode

Hej guys,

did some quick experimenting, looks great! One question though: do you think it's possible to somehow change the editing mode of an interactive numba run? The default mode for the prompt-toolkit Application is emacs (https://github.com/facebookincubator/python-nubia/blob/537e6c44834f7c4ecb7b037d7b28edf0bf49925e/nubia/internal/interactive.py#L102), would be sweet to be able to configure this to, uhmm, say, vi :'D
Also eager to do a PR if we come up with a strategy for implementation.

Add pager to shell mode

When executing inside python-nubia, it'd be useful to have a pager to pause the display, much like less or more. People who're comfortable with the completions and support offered by nubia inside the nubia shell are uncomfortable typing the long command from bash and piping it through more.

It seems there's a pager support using prompt-toolkit, https://github.com/prompt-toolkit/pypager. Is it possible to incorporate this functionality into nubia?

I'm willing to help.

Can't disable all built-in command

Hi ,

Is there anyway to disable built-in command, I tried the following method, but it seems some of the commands can be disable but some are not, eg exit.

invisable_executable_list = ['connect', 'exit', ':verbose']

....

    for cmd_abandoned in invisable_executable_list:
        if cmd_abandoned in cmdline._registry._completer.words:
            cmdline._registry._completer.words.remove(cmd_abandoned)
            cmdline._registry.get_all_commands_map().pop(cmd_abandoned)

I want to disable exit command, bcos my code need to use that word. many thanks

To replace FileHistory with InMemoryHistory

I have created my first ticket here before, but I forgot to thank you guys for the awesome work. It has worked very well even in its humble beta version!

My question today is about command history. For security/privacy reason, I need to use InMemoryHistory instead, but it looks like I'd have to change Nubia source code. I'm looking for any other possible ways without changing the source code. any suggestions? or does it make sense to make the command history configurable in Nubia?

Command @argument description required?

Got a problem when using the following decorators set:

    @argument("profile", positional=True, choices=aws_profiles)
    @argument("host", positional=True)
    def ssh(self, profile, host):

When invoking app got:

  File "/home/somewhere-else/cyk/cyk/app.py", line 9, in <module>
    shell = Nubia(name="cyk_shell", plugin=plugin)
  File "/home/somewhere/pyenv/versions/cyk/lib/python3.6/site-packages/nubia/internal/nubia.py", line 127, in __init__
    self._registry.register_command(cmd, override=True)
  File "/home/somewhere/pyenv/versions/cyk/lib/python3.6/site-packages/nubia/internal/registry.py", line 67, in register_command
    cmd_instance.add_arguments(self._parser)
  File "/home/somewhere/pyenv/versions/cyk/lib/python3.6/site-packages/nubia/internal/cmdbase.py", line 485, in add_arguments
    register_command(parser, self.metadata)
  File "/home/somewhere/pyenv/versions/cyk/lib/python3.6/site-packages/nubia/internal/typing/argparse.py", line 143, in register_command
    register_command(subcommand_parsers, v)
  File "/home/somewhere/pyenv/versions/cyk/lib/python3.6/site-packages/nubia/internal/typing/argparse.py", line 114, in register_command
    arg
  File "/home/somewhere/pyenv/versions/cyk/lib/python3.6/site-packages/nubia/internal/typing/argparse.py", line 228, in _argument_to_argparse_input
    ", ".join(map(str, arg.choices))
TypeError: unsupported operand type(s) for +=: 'NoneType' and 'str'

And it makes sense; looks like add_argument_kwargs["help"] is NoneType when command description is not provided:

add_argument_kwargs["help"] += " (choose from {})".format(
            ", ".join(map(str, arg.choices))
        )

Not sure now if this is intended (thus documentation lacks information about required options) or not. I could fix this in argparse.py and make description optional or simply update docs :)

printing to screen from a background process while prompt is active

Hi.

I would like to know if it is possible to refresh the screen buffer every time I have a console message be printed from a Thread operation.
Currently what happens is that when I do a cprint() in my application it prints to the screen but the text does not align with the statusbar and it looks bad in general, only when I press enter on the keyboard it seems to refresh and the screen looks good again.
So I am wondering if there is a method either in Nubia or in prompt-toolkit that allows for automatic refresh when the screen buffer has been written to with data that has not originated from the prompt commands.

Thanks in advance.

There is a problem with the example

python3 -m pip install python-nubia
cd example

python3 -m nubia_example

result

/usr/local/lib/python3.9/site-packages/nubia/internal/nubia.py:203: RuntimeWarning: coroutine 'NubiaExampleContext.on_interactive' was never awaited
  self._ctx.on_interactive(args)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

Unhandled exception in event loop:
  File "/usr/local/Cellar/[email protected]/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/eventloop/utils.py", line 75, in schedule
    func()
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/application/application.py", line 484, in redraw
    self._redraw()
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/application/application.py", line 556, in _redraw
    self.context.copy().run(run_in_context)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/application/application.py", line 539, in run_in_context
    self.renderer.render(self, self.layout)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/renderer.py", line 642, in render
    layout.container.preferred_height(size.columns, size.rows).preferred,
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 326, in preferred_height
    dimensions = [
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 327, in <listcomp>
    c.preferred_height(width, max_available_height) for c in self._all_children
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 2643, in preferred_height
    return self.content.preferred_height(width, max_available_height)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 1624, in preferred_height
    return self._merge_dimensions(
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 1652, in _merge_dimensions
    preferred = get_preferred()
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 1617, in preferred_content_height
    return self.content.preferred_height(
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/controls.py", line 370, in preferred_height
    content = self.create_content(width, None)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/controls.py", line 383, in create_content
    fragments_with_mouse_handlers = self._get_formatted_text_cached()
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/controls.py", line 347, in _get_formatted_text_cached
    return self._fragment_cache.get(
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/cache.py", line 41, in get
    value = getter_func()
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/controls.py", line 348, in <lambda>
    get_app().render_counter, lambda: to_formatted_text(self.text, self.style)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/formatted_text/base.py", line 79, in to_formatted_text
    return to_formatted_text(value(), style=style)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/formatted_text/base.py", line 79, in to_formatted_text
    return to_formatted_text(value(), style=style)
  File "/usr/local/lib/python3.9/site-packages/nubia/internal/interactive.py", line 81, in _get_bottom_toolbar
    return PygmentsTokens(self._status_bar.get_tokens())
  File "/Users/zero/python-nubia/example/nubia_statusbar.py", line 29, in get_tokens
    if context.get_context().verbose:

Exception 'NubiaExampleContext' object has no attribute 'verbose'
Press ENTER to continue...

Unhandled exception in event loop:
  File "/usr/local/Cellar/[email protected]/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/eventloop/utils.py", line 75, in schedule
    func()
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/application/application.py", line 484, in redraw
    self._redraw()
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/application/application.py", line 556, in _redraw
    self.context.copy().run(run_in_context)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/application/application.py", line 539, in run_in_context
    self.renderer.render(self, self.layout)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/renderer.py", line 642, in render
    layout.container.preferred_height(size.columns, size.rows).preferred,
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 326, in preferred_height
    dimensions = [
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 327, in <listcomp>
    c.preferred_height(width, max_available_height) for c in self._all_children
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 2643, in preferred_height
    return self.content.preferred_height(width, max_available_height)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 1624, in preferred_height
    return self._merge_dimensions(
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 1652, in _merge_dimensions
    preferred = get_preferred()
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/containers.py", line 1617, in preferred_content_height
    return self.content.preferred_height(
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/controls.py", line 370, in preferred_height
    content = self.create_content(width, None)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/controls.py", line 383, in create_content
    fragments_with_mouse_handlers = self._get_formatted_text_cached()
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/controls.py", line 347, in _get_formatted_text_cached
    return self._fragment_cache.get(
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/cache.py", line 41, in get
    value = getter_func()
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/layout/controls.py", line 348, in <lambda>
    get_app().render_counter, lambda: to_formatted_text(self.text, self.style)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/formatted_text/base.py", line 79, in to_formatted_text
    return to_formatted_text(value(), style=style)
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/formatted_text/base.py", line 79, in to_formatted_text
    return to_formatted_text(value(), style=style)
  File "/usr/local/lib/python3.9/site-packages/nubia/internal/interactive.py", line 81, in _get_bottom_toolbar
    return PygmentsTokens(self._status_bar.get_tokens())
  File "/Users/zero/python-nubia/example/nubia_statusbar.py", line 29, in get_tokens
    if context.get_context().verbose:

Exception 'NubiaExampleContext' object has no attribute 'verbose'
Press ENTER to continue...
zero> Traceback (most recent call last):
  File "/usr/local/Cellar/[email protected]/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/local/Cellar/[email protected]/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/Users/zero/python-nubia/example/nubia_example.py", line 27, in <module>
    sys.exit(shell.run())
  File "/usr/local/lib/python3.9/site-packages/nubia/internal/nubia.py", line 312, in run
    return self.start_interactive(args)
  File "/usr/local/lib/python3.9/site-packages/nubia/internal/nubia.py", line 212, in start_interactive
    io_loop.run()
  File "/usr/local/lib/python3.9/site-packages/nubia/internal/interactive.py", line 147, in run
    text = prompt.prompt(
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/shortcuts/prompt.py", line 1033, in prompt
    return self.app.run(
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/application/application.py", line 937, in run
    return loop.run_until_complete(
  File "/usr/local/Cellar/[email protected]/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/application/application.py", line 664, in run_async
    assert not self._is_running, "Application is already running."
AssertionError: Application is already running.

Is there a problem with my method of operation?

Error when starting with ipython

when run the Nubia with ipython=True, I got the error log:

File ".../nubia/internal/ipython.py", line 39, in start_interactive_python
if ctx.target:
AttributeError: 'Context' object has no attribute 'target'

Releases?

My project netenglabs/suzieq relies on nubia, but cannot use it via python packages due to infrequent releases. Would it be possible to get into a regular release cadence (into package upstream) even if all it has are bug fixes? The main benefit among other things is that I can stop relying on my fork of this repo. Thanks.

Add ability to customize prompt

Hey, I have been playing around with this lib a little and it's awesome! This is just an idea for a minor improvement. In interactive mode, it would be nice to have the ability to customize the prompt similar to cmd instead of it always being the user name. Perhaps also add the ability to add an intro as well? Although I suppose that could simply be done from a print statement in Context.on_interactive()

https://github.com/facebookincubator/python-nubia/blob/87a59d2daf3118645eaed844fe3b771806acebfd/nubia/internal/interactive.py#L115-L120

Python 3.7 compatibility

It would be great if this was fully compatible with Python 3.7, but it unfortunately isn't. The problem seems mostly linked with accessing private members of the typingmodule:

>>> import nubia
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/__init__.py", line 12, in <module>
    from .internal.deprecation import deprecated
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/internal/deprecation.py", line 15, in <module>
    from nubia.internal.typing import inspect_object
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/internal/typing/__init__.py", line 68, in <module>
    from nubia.internal.helpers import (
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/internal/helpers.py", line 17, in <module>
    from typing import _Union, Any, Iterable  # noqa T484
ImportError: cannot import name '_Union' from 'typing' (/usr/lib/python3.7/typing.py)

After importing Union instead (which I'm sure doesn't really fix the problem), it seemingly works fine, however there might be hiding more incompatibilities. For example, I suspect this is one:

user> pick code=14 stuff=red style=toast                                                                                                      Error: 3
Traceback (most recent call last):
  File "nubia_example.py", line 17, in <module>
    sys.exit(shell.run())
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/internal/nubia.py", line 296, in run
    return self.start_interactive(args)
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/internal/nubia.py", line 196, in start_interactive
    io_loop.run()
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/internal/interactive.py", line 181, in run
    self.parse_and_evaluate(stdout, text)
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/internal/interactive.py", line 127, in parse_and_evaluate
    return self.evaluate_command(stdout, cmd, args, input)
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/internal/interactive.py", line 159, in evaluate_command
    result = cmd_instance.run_interactive(cmd, args, raw)
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/internal/cmdbase.py", line 340, in run_interactive
    new_value = apply_typing(value, target_type)
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/internal/typing/builder.py", line 34, in apply_typing
    return get_typing_function(type)(value)
  File "/home/user/.virtualenvs/toolbox/lib/python3.7/site-packages/nubia/internal/typing/builder.py", line 195, in wrapped
    return function(string, *args_builders)
  File "/usr/lib/python3.7/typing.py", line 668, in __call__
    raise TypeError(f"Type {self._name} cannot be instantiated; "
TypeError: Type List cannot be instantiated; use list() instead

General state of Nubia

Hi,

Just wondering what the general state of this repo is. Planned roadmap/updates? Planned to archive since logdevice is archived? Loose community-driven development? Thanks.

More flexible handling for boolean arguments

In argparse, we only generate an argument for the boolean to set to true but there is no way for the user to explicitly switch it off (pass False).

Our options:

  1. Generate a '--no-X' argument automatically to toggle the boolean. (opinionated decision)
  2. Give the user better control through the @argument to define the behaviour (needs spec)

Nubia crashes when a subcommand has no docstring

After applying the patch from this gist:

.venv ❯ env PYTHONPATH=(pwd) python example/nubia_example.py super-command do-stuff
Traceback (most recent call last):
  File "example/nubia_example.py", line 17, in <module>
    options=Options(persistent_history=False))
  File "/Users/afomin/github/python-nubia/nubia/internal/nubia.py", line 126, in __init__
    for cmd in self._plugin.get_commands():
  File "/Users/afomin/github/python-nubia/example/nubia_plugin.py", line 51, in get_commands
    AutoCommand(sample_commands.SuperCommand),
  File "/Users/afomin/github/python-nubia/nubia/internal/cmdbase.py", line 172, in __init__
    inspection.command.help
  File "/opt/homebrew/Cellar/python36/3.6.2+_254.20170915/Frameworks/Python.framework/Versions/3.6/lib/python3.6/textwrap.py", line 430, in dedent
    text = _whitespace_only_re.sub('', text)
TypeError: expected string or bytes-like object

Please release latest version

When running the version in pypi, I get the following error. It only occurs after typing a registered command at the interactive shell and pressing the spacebar.

Traceback (most recent call last):
  File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/prompt_toolkit/interface.py", line 865, in run
    completions = list(buffer.completer.get_completions(document, complete_event))
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/nubia/internal/interactive.py", line 220, in get_completions
    complete_event,
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/nubia/internal/cmdbase.py", line 503, in get_completions
    return state_machine.get_completions()
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/nubia/internal/completion.py", line 133, in get_completions
    self.doc.text, expect_subcommand=self.cmd.super_command
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/nubia/internal/parser.py", line 119, in parse
    partial_result = expected_pattern.parseString(text, parseAll=False)
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 1828, in parseString
    raise exc
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 1818, in parseString
    loc, tokens = self._parse( instring, 0 )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 1562, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 3752, in parseImpl
    loc, exprtokens = e._parse( instring, loc, doActions )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 1562, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 4098, in parseImpl
    return self.expr._parse( instring, loc, doActions, callPreParse=False )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 1562, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 4322, in parseImpl
    loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 1562, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 4098, in parseImpl
    return self.expr._parse( instring, loc, doActions, callPreParse=False )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 1562, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 3735, in parseImpl
    loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 1562, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/sharmon/PycharmProjects/syseng-toolbox-test/venv/lib/python3.6/site-packages/pyparsing.py", line 2935, in parseImpl
    raise ParseException(instring, loc, self.errmsg, self)

pyparsing.ParseException: Expected W:(ABCD...,ABCD...) (at char 9), (line:1, col:10)

It seems to be related to the parsing of the shell text to acquire completion information. It doesn't occur when running from a bash shell and unittests don't have the issue using either using either run_cli_line or run_interactive_line. If I type through the spewing errors at the interactive shell, commands return as expected.

certifi==2019.3.9
chardet==3.0.4
dataclasses==0.6
idna==2.8
pipenv==2018.11.26
pkg-resources==0.0.0
prettytable==0.7.2
prompt-toolkit==1.0.16
Pygments==2.3.1
pyparsing==2.4.0
python-Levenshtein==0.12.0
python-nubia==0.1b4
requests==2.21.0
six==1.12.0
termcolor==1.1.0
urllib3==1.24.1
virtualenv==16.4.3
virtualenv-clone==0.5.3
wcwidth==0.1.7

python 3.6.3
ubuntu 18.10

Unable to run the example

Hi team,

I was unable to run the example.

The error is:

  File "./nubia_example.py", line 23, in <module>
    sys.exit(shell.run())
  File "/home/myhome/.local/lib/python3.6/site-packages/nubia/internal/nubia.py", line 311, in run
    return self.start_interactive(args)
  File "/home/myhome/.local/lib/python3.6/site-packages/nubia/internal/nubia.py", line 211, in start_interactive
    io_loop.run()
  File "/home/myhome/.local/lib/python3.6/site-packages/nubia/internal/interactive.py", line 134, in run
    prompt = self._build_cli()
  File "/home/myhome/.local/lib/python3.6/site-packages/nubia/internal/interactive.py", line 64, in _build_cli
    os.environ.get("EDITOR", EditingMode.EMACS).upper(),
AttributeError: 'EditingMode' object has no attribute 'upper'

Thanks!

Positional argument not parsed when passing additional args

foo> config open foo                                                                                                                     
<Result>

foo> config open foo edit=True                                                                                                           
Error parsing command
config open foo edit=True
                ^
Expected end of text (at char 9), (line:1, col:10)

foo> config open cfg=foo edit=True
<Result>

cfg is positional and edit is non-positional with a default value

Typo in readme

If you are curious on the origins of the name, checkout Nubia on Wikipedia with it its unique and colourful architecture.

Subcommand aliases are broken

After applying the patch from this gist:

~/github/python-nubia master*
.venv ❯ env PYTHONPATH=(pwd) python example/nubia_example.py super-command do
usage: nubia_example.py super-command [-h] [--config CONFIG] [--verbose]
                                      [--stderr] [--shared INT]
                                      [subcommand] ...
nubia_example.py super-command: error: argument [subcommand]: invalid choice: 'do' (choose from 'do-stuff', 'print-name')

Allow for a default subcommand

First and foremost:

  • My english sucks
  • This library is amazing! (I was looking for a way to quickly make CLIs with interactive + non-interactive mode without repeating logic and this was exactly what I was looking for :) ).

Given something like this:

@command
class Token:
  @command
  def provider_a():
    get_token_for_provider_a()

  @command
  def provider_b():
    get_token_for_provider_b()

I'd like to provide a default subcommand to call when no arguments are passed, so I could call that command like:

> cli token

And behind the scenes token.provider_b() would be called, for example.

This is because doing something like cli token --provider=b is kinda verbose (at least for my use case).

Does this make sense? Or I'm trying to use subcommands for something that they are not intended for?

Incorrect flagging of duplicate argument

I seem to be running into what seems an invalid enforcement. Nothing bad seems to happen (choices provided are correct, help string is correct and so on) if we skip the enforcement. So, I'm not sure if this is a bug with consequences I'm not spotting, or is it just a wrong check.

I have a class SqCommand whose class decorator takes a set of arguments that apply to all the subcommands of that class. So SqCommand represents a super command. I then define two subclasses of this SqCommand, lets say super1 and super2. Both their classes take arguments that apply to all subcommands of those two super commands. If I define an argument with the same name, say "state" for both super1 and super2, Nubia complains that I've defined a duplicate of the same variable, "state", This happens around line 161 in nubia/internal/typing/init.py. If I comment out that check, everything seems to work as expected.

I'm attaching a skeleton to explain what I'm seeing as the problem (state is flagged as a duplicate var). I've tried both command_pkgs and get_cmds() methods to register Super1 and Super2 and both have the same issue.

Thanks,

Dinesh

@argument('var1', desciption='var1')
class SqCommand:
    def __initi__(self, var1):
        self.var1 = var1
    
   @command('show')
    def show(self):
       do_something(self.var1)

@command('super1')
@argument('state', description='super1 state', choices=['a', 'b'])
class Super1(SqCommand):
    def __init__(self, state):
        self.state = state

   @command('show')
   def show(self):
        do_something(self.var1, self.state)

@command('super1')
@argument('state', description='super2 state', choices=['c', 'd'])
class Super2(SqCommand):
    def __init__(self, state):
        self.state = state

   @command('show')
   def show(self):
        do_something(self.var1, self.state)

Setup.py needs to pin pyparsing 2.4.7

Hi,

I just spent the week troubleshoot a big problem for my tool based on Nubia and I have finally discovered the issue.

Nubia has a dependency on pyparsing>=2.2.0,<3 as defined in the requirements.txt, but neither the setup.py nor the pyproject.toml reflect this. I use Poetry as my dev environment and it has a great dependency resolving system. When developing my tool, everything works great. However, when I try to pip install my tool to test it, subcommand arguments do not work. I dug through Nubia and found the location of the issue starts here:

https://github.com/facebookincubator/python-nubia/blob/9cb98e220a6fb7e837631f5b0f75a8b1363e5a0c/nubia/internal/parser.py#L47

It seems like there's been some change in pyparsing from 2.X to 3.X. Here is what happens when I run the command dump heap filter=[Azure] with each version. On the left is a working example using 2.4.7, and on the right is pyparsing 3.0.3.

image

Because the identifier is different, subcommands get parsed as normal commands when pyparsing 3.X is used.

Pip doesn't know that the requirement for Nubia is pyparsing>=2.2.0,<3 because pip has changed drastically over the years and requirements.txt doesn't really hold much of a priority anymore. At a minimum, setup.py needs to reflect this version limit anyway so the dev using Nubia doesn't install pyparsing >=3. Ideally, the parsing should be rewritten to handle 3.0 but that is probably a little more involved.

It would be ideal to just move to Poetry since pyproject.toml is generally easier to handle than setup.py but that's up to you. It was requested here (#21) previously.

TL;DR - Please pin pyparsing version in setup.py or upgrade version.

Add support for optional arguments

The argument decorator lacks an option to set whether an argument is required or not. It is a very handy feature which is not present in the tool

Enforce type checking on the codebase

It would be nice to make Nubia strictly type checked so projects that use it could rely on its type safety.

Most likely we'll have to support both mypy and Pyre.

Creating arguments with '_' in name doesn't work

If I create an argument called start-time, its rejected by nubia saying that there isn't a matching arg in the decorated function. I cannot use '-' in name because that's not a valid character for python var name.

If I use 'start_time', nubia correctly converts that into '-' when displaying allowed arguments. It also matches the python var name and so life is good so far. However, attempting to use start-time results in "unknown argument" at runtime as the search for matching arguments doesn't convert '-' to '_' during match. The bug is on line 193 in cmdbase.py.

I'll submit a PR for this issue shortly.

Light theme style

The style only works on dark themes, the text is almost invisible on light themes.

image

Move to asyncio everywhere

After migrating to prompt-toolkit 2+ we should redefine the internal workflow around awaitables instead of plain-old function calls. This unblocks the work for background dispatch idea and make sure that users have a nice experience writing async commands first-level.

[RFC] Use Poetry for dependency management and packaging

I've been recently looking into some aspects of Nubia development and I feel that our choice of dependency management infra was somehow rushed and I want to suggest another option that may be better for this particular project.

First of all, let me explain why I'm not happy with Pipenv.

  1. We can support only one version of Python at a time with Pipenv which is unacceptable for a library. Pipenv is intended to be used by applications, not libraries, and things are unlikely to change (see pypa/pipenv#1050). Thus, the only option for supporting multiple versions with Pipenv is to remove Python version requirement and rely on runtime checks.
  2. Pipenv is quite fragile when it comes to dependency resolution. Below are my most important observations.
    • Try to add Black as a development dependency and tell me about your experience 😉
      • If you made it to pre-release dependencies, then I have some bad news: you can only enable them globally, which is not what we want (see pypa/pipenv#1760).
      • If you want to workaround this by using an exact version specifier, then you're still doomed as Pipenv refuses to recognise something like 18.9b0.
    • Try to check out our master branch and run pipenv sync. It could be a corrupted Pipenv.lock file or an incompatibility with a recent Pipenv version – in either case, I don't think that I want to see this kind of behaviour from a dependency management tool (it caused #17).
  3. Additional complexity imposed by the necessity of managing a dedicated configuration file along with setup.py and friends. There's a trend toward using pyproject.toml as a source of truth for Python projects configuration (see PEP518 for details), and we already leverage it for storing Black configuration.

With this in mind, I would like us to consider an alternative solution, namely Poetry.

  1. Poetry uses the same (nice and flexible) versioning model for all the dependencies which makes it possible to support multiple Python versions at the same time.
  2. Poetry dependency resolution is more robust (see the comparison with Pipenv here).
  3. Poetry uses aforementioned pyproject.toml thus enabling us to have all the configuration in the same file (modulo lock file).
  4. Poetry provides a convenient way of building and even publishing the package.

So far I've had a very pleasant experience with it, and the only minor caveat I see is that it's not available via Homebrew at the moment. I could submit a formula PR, but it's blocked on pypa/pip#5739 at the moment (although it probably will be solved in the next release).

@AhmedSoliman, @calebmarchent, I'm especially interested in your opinion on this.
If you guys find it reasonable, I'm happy to submit a PR as I have it almost ready 🙂

Make command available exlusively to interactive mode

Hello!

I was wondering if there was a way of specifying a command to only be available in interactive mode? It seems like I might be able to do this by pulling some hackery in the Context.on_cli() method but I was wondering if there's a better way?

Cheers

Project status?

Hi maintainers,

What is the status of this project? I opened a ticket a while back and I see no response to it. I see the build has been failing since Dec 18, but there's been no fixes. IIRC, I saw an issue filed by Github asking if this project is to be archived due to inactivity.

Could you please let me know what the status is? This is a useful project to me, and I can fork it and maintain it for my own purposes (I'll keep it open source of course, and do what I can to incorporate requests and fixes).

Thanks,

Dinesh

Automatic command collection?

At first seeing nubia, I was excited of what I believed an easy way to glue together a CLI application …
however after trying to run the example from the README (by the way, the line cprint(f"Verbose? {}".format(ctx.verbose), "yellow") contains a SyntaxError causing f) by attaching a

from nubia import Nubia
Nubia(name="example").run()

to the example snippet from the README, no custom commands were usable.

Now taking a look at the example files, I see that, apparently without a custom Plugin, nothing happens, and furthermore, the commands have to collected manually in that class.

Why is that the case? Was it deliberately decided against, or was it just not implemented? The decorator could just register commands (and if no particular selection is needed, all could be usable by default), reducing the barrier of using nubia.

.exe built with pyinstaller does not show commands

After importing nubia and create my commnads module with a simple command hello, I can run it with python example.py and the hello command is prompted.

However if create a distribution using pyinstaller, when I run the example.exe, the hello command is not prompted anymore (only nubia commands are prompted)

I am assuming it will have to do with the way nubia searches commands within the command package. But I've tried running pyinstaller with --hidden-import path.to.my.command_module and still got the same result

Dynamic values for command arguments & auto suggestions

I've been looking for a way to dynamically fetch values into command argument values in interactive mode (could also work for non - interactive via shell completion).

For instance having:

@command(name_or_function="aws")
class AWS:
    @command
    @argument("profile", positional=True)
    @argument("host", positional=True)
    def ssh(self, profile: str, host: str):

I'd like user working in interactive mode have auto - suggestion while entering host value. This auto suggestion could be an async call to some defined method which basically fetches required data.

In practice I'm looking for a way to fetch AWS instance names and autocomplete user input so he/she may choose where he wants to ssh.

I know how it's done in aws-shell via python-prompt-toolkit auto suggestion feature. However before I try to mimic that behavior I wanted to ask you if you already have some thoughts about it.

Thanks,
Maciek

can i change argparse arguments at runtime?

how do I make it work (at runtime):

from nubia import command, argument, context
...
@command
@argument("level", name='level', type=int, description='verbose level')
def change_verbose(level: int):
    "change verbose at runtime"
    ctx = context.get_context()
    ctx.set_verbose(level)

my question is: is it possible to change the startup parameters during execution?

Nubia doesn't do anything

I want to create a CLI interface as an enhancement to an existing project.

I have placed my Nubia code in the module: myproject/cli.py.

When I try to execute the CLI using: python -m myproject.cli, nothing happens.

My example code:

from nubia import command, argument


@command
@argument(
    "event_type",
    description="The event type to trigger webhooks for",
    positional=True,
)
def trigger_webhooks(event_type: str) -> None:
    print("Success")

I don't want to execute the Python module directly (like python3 myproject/cli.py because, long story short, it messes with module paths and makes my application crash.


EDIT: I should have done some more testing before creating a bug report. It turns out I can't get Nubia to do anything no matter what I do. I have reduced my test file to the bare minimum:

#!/usr/bin/env python3

from nubia import command


@command
def trigger_webhooks() -> int:
    print("Success")

    return 0

I've marked the file as executable and I'm running ./cli.py, nothing happens. The command exits with status code 0 and no output. Have I completely misunderstood something here?

I'm using CPython 3.9.1 with python-nubia 0.2b5.

Blacklisted commands still show up in completions

Maybe I'm misunderstanding what blacklisted commands are for, but I was surprised to see that the blacklisted command in the example project still showed up in the list of available commands, and in tab completion, etc. It won't actually be executed, but it's still visible.
Is that desired behavior?

If not, I'm happy to submit a PR ... perhaps excluding it in CommandRegistry.registerCommand? But more likely I'm just misunderstanding the purpose of the blacklist.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.