Exemple #1
0
class HummingbotCLI:
    def __init__(self, input_handler: Callable, bindings: KeyBindings,
                 completer: Completer):
        use_asyncio_event_loop()
        self.input_field = create_input_field(completer=completer)
        self.output_field = create_output_field()
        self.log_field = create_log_field()
        self.layout = generate_layout(self.input_field, self.output_field,
                                      self.log_field)

        self.bindings = bindings
        self.input_handler = input_handler
        self.input_field.accept_handler = self.accept
        self.app = Application(layout=self.layout,
                               full_screen=True,
                               key_bindings=self.bindings,
                               style=load_style(),
                               mouse_support=True,
                               clipboard=PyperclipClipboard())
        self.log_lines: Deque[str] = deque()
        self.log(HEADER)

        # settings
        self.prompt_text = ">>> "
        self.pending_input = None
        self.input_event = None
        self.hide_input = False

    async def run(self):
        await self.app.run_async().to_asyncio_future()

    def accept(self, buff):
        self.pending_input = self.input_field.text.strip()

        if self.input_event:
            self.input_event.set()

        try:
            if self.hide_input:
                output = ''
            else:
                output = '\n>>>  {}'.format(self.input_field.text, )
                self.input_field.buffer.append_to_history()
        except BaseException as e:
            output = str(e)

        self.log(output)
        self.input_handler(self.input_field.text)

    def clear_input(self):
        self.pending_input = None

    def log(self, text: str):
        self.log_lines.extend(str(text).split('\n'))
        while len(self.log_lines) > MAXIMUM_OUTPUT_PANE_LINE_COUNT:
            self.log_lines.popleft()
        new_text: str = "\n".join(self.log_lines)
        self.output_field.buffer.document = Document(
            text=new_text, cursor_position=len(new_text))

    def change_prompt(self, prompt: str, is_password: bool = False):
        self.prompt_text = prompt
        processors = []
        if is_password:
            processors.append(PasswordProcessor())
        processors.append(BeforeInput(prompt))
        self.input_field.control.input_processors = processors

    async def prompt(self, prompt: str, is_password: bool = False) -> str:
        self.change_prompt(prompt, is_password)
        self.app.invalidate()
        self.input_event = asyncio.Event()
        await self.input_event.wait()

        temp = self.pending_input
        self.clear_input()
        self.input_event = None

        if is_password:
            masked_string = "*" * len(temp)
            self.log(f"{prompt}{masked_string}")
        else:
            self.log(f"{prompt}{temp}")
        return temp

    def set_text(self, new_text: str):
        self.input_field.document = Document(text=new_text,
                                             cursor_position=len(new_text))

    def toggle_hide_input(self):
        self.hide_input = not self.hide_input

    def exit(self):
        self.app.exit()
class HummingbotCLI:
    def __init__(self, input_handler: Callable, bindings: KeyBindings,
                 completer: Completer):
        self.search_field = create_search_field()
        self.input_field = create_input_field(completer=completer)
        self.output_field = create_output_field()
        self.log_field = create_log_field(self.search_field)
        self.timer = create_timer()
        self.process_usage = create_process_monitor()
        self.trade_monitor = create_trade_monitor()
        self.layout = generate_layout(self.input_field, self.output_field,
                                      self.log_field, self.search_field,
                                      self.timer, self.process_usage,
                                      self.trade_monitor)
        # add self.to_stop_config to know if cancel is triggered
        self.to_stop_config: bool = False

        self.live_updates = False
        self.bindings = bindings
        self.input_handler = input_handler
        self.input_field.accept_handler = self.accept
        self.app: Optional[Application] = None

        # settings
        self.prompt_text = ">>> "
        self.pending_input = None
        self.input_event = None
        self.hide_input = False

        # start ui tasks
        loop = asyncio.get_event_loop()
        loop.create_task(start_timer(self.timer))
        loop.create_task(start_process_monitor(self.process_usage))
        loop.create_task(start_trade_monitor(self.trade_monitor))

    async def run(self):
        self.app = Application(layout=self.layout,
                               full_screen=True,
                               key_bindings=self.bindings,
                               style=load_style(),
                               mouse_support=True,
                               clipboard=PyperclipClipboard())
        await self.app.run_async()

    def accept(self, buff):
        self.pending_input = self.input_field.text.strip()

        if self.input_event:
            self.input_event.set()

        try:
            if self.hide_input:
                output = ''
            else:
                output = '\n>>>  {}'.format(self.input_field.text, )
                self.input_field.buffer.append_to_history()
        except BaseException as e:
            output = str(e)

        self.log(output)
        self.input_handler(self.input_field.text)

    def clear_input(self):
        self.pending_input = None

    def log(self, text: str, save_log: bool = True):
        if save_log:
            if self.live_updates:
                self.output_field.log(text, silent=True)
            else:
                self.output_field.log(text)
        else:
            self.output_field.log(text, save_log=False)

    def change_prompt(self, prompt: str, is_password: bool = False):
        self.prompt_text = prompt
        processors = []
        if is_password:
            processors.append(PasswordProcessor())
        processors.append(BeforeInput(prompt))
        self.input_field.control.input_processors = processors

    async def prompt(self, prompt: str, is_password: bool = False) -> str:
        self.change_prompt(prompt, is_password)
        self.app.invalidate()
        self.input_event = asyncio.Event()
        await self.input_event.wait()

        temp = self.pending_input
        self.clear_input()
        self.input_event = None

        if is_password:
            masked_string = "*" * len(temp)
            self.log(f"{prompt}{masked_string}")
        else:
            self.log(f"{prompt}{temp}")
        return temp

    def set_text(self, new_text: str):
        self.input_field.document = Document(text=new_text,
                                             cursor_position=len(new_text))

    def toggle_hide_input(self):
        self.hide_input = not self.hide_input

    def exit(self):
        self.app.exit()
Exemple #3
0
class ProgressBar(object):
    """
    Progress bar context manager.

    Usage ::

        with ProgressBar(...) as pb:
            for item in pb(data):
                ...

    :param title: Text to be displayed above the progress bars. This can be a
        callable or formatted text as well.
    :param formatters: List of :class:`.Formatter` instances.
    :param bottom_toolbar: Text to be displayed in the bottom toolbar. This
        can be a callable or formatted text.
    :param style: :class:`prompt_toolkit.styles.BaseStyle` instance.
    :param key_bindings: :class:`.KeyBindings` instance.
    :param file: The file object used for rendering, by default `sys.stderr` is used.

    :param color_depth: `prompt_toolkit` `ColorDepth` instance.
    :param output: :class:`~prompt_toolkit.output.Output` instance.
    :param input: :class:`~prompt_toolkit.input.Input` instance.
    """
    def __init__(self,
                 title=None,
                 formatters=None,
                 bottom_toolbar=None,
                 style=None,
                 key_bindings=None,
                 file=None,
                 color_depth=None,
                 output=None,
                 input=None):
        assert formatters is None or (isinstance(formatters, list) and all(
            isinstance(fo, Formatter) for fo in formatters))
        assert style is None or isinstance(style, BaseStyle)
        assert key_bindings is None or isinstance(key_bindings, KeyBindings)

        self.title = title
        self.formatters = formatters or create_default_formatters()
        self.bottom_toolbar = bottom_toolbar
        self.counters = []
        self.style = style
        self.key_bindings = key_bindings

        # Note that we use __stderr__ as default error output, because that
        # works best with `patch_stdout`.
        self.color_depth = color_depth
        self.output = output or create_output(stdout=file or sys.__stderr__)
        self.input = input or get_default_input()

        self._thread = None

        self._loop = get_event_loop()
        self._previous_winch_handler = None
        self._has_sigwinch = False

    def __enter__(self):
        # Create UI Application.
        title_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.title),
                   height=1,
                   style='class:progressbar,title'),
            filter=Condition(lambda: self.title is not None))

        bottom_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.bottom_toolbar,
                                        style='class:bottom-toolbar.text'),
                   style='class:bottom-toolbar',
                   height=1),
            filter=~is_done & renderer_height_is_known
            & Condition(lambda: self.bottom_toolbar is not None))

        def width_for_formatter(formatter):
            # Needs to be passed as callable (partial) to the 'width'
            # parameter, because we want to call it on every resize.
            return formatter.get_width(progress_bar=self)

        progress_controls = [
            Window(content=_ProgressControl(self, f),
                   width=functools.partial(width_for_formatter, f))
            for f in self.formatters
        ]

        self.app = Application(
            min_redraw_interval=.05,
            layout=Layout(
                HSplit([
                    title_toolbar,
                    VSplit(progress_controls,
                           height=lambda: D(preferred=len(self.counters),
                                            max=len(self.counters))),
                    Window(),
                    bottom_toolbar,
                ])),
            style=self.style,
            key_bindings=self.key_bindings,
            color_depth=self.color_depth,
            output=self.output,
            input=self.input)

        # Run application in different thread.
        def run():
            with _auto_refresh_context(self.app, .3):
                try:
                    self.app.run()
                except BaseException as e:
                    traceback.print_exc()
                    print(e)

        self._thread = threading.Thread(target=run)
        self._thread.start()

        # Attach WINCH signal handler in main thread.
        # (Interrupt that we receive during resize events.)
        self._has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread()
        if self._has_sigwinch:
            self._previous_winch_handler = self._loop.add_signal_handler(
                signal.SIGWINCH, self.app.invalidate)

        return self

    def __exit__(self, *a):
        # Quit UI application.
        if self.app.is_running:
            self.app.exit()

        # Remove WINCH handler.
        if self._has_sigwinch:
            self._loop.add_signal_handler(signal.SIGWINCH,
                                          self._previous_winch_handler)

        self._thread.join()

    def __call__(self,
                 data=None,
                 label='',
                 remove_when_done=False,
                 total=None):
        """
        Start a new counter.

        :param label: Title text or description for this progress. (This can be
            formatted text as well).
        :param remove_when_done: When `True`, hide this progress bar.
        :param total: Specify the maximum value if it can't be calculated by
            calling ``len``.
        """
        assert is_formatted_text(label)
        assert isinstance(remove_when_done, bool)

        counter = ProgressBarCounter(self,
                                     data,
                                     label=label,
                                     remove_when_done=remove_when_done,
                                     total=total)
        self.counters.append(counter)
        return counter

    def invalidate(self):
        self.app.invalidate()
Exemple #4
0
class View(object):
    def __init__(self, configuration, exchange):
        self._project_name = configuration.project.name
        self._local_dir = configuration.local_dir
        self._remote_directory = None
        self._exchange = exchange
        self._loop = get_event_loop()

        self._current_screen = None
        self.root_container = HSplit([Window()])
        self.layout = Layout(container=self.root_container)
        self._render()

        self.bindings = self._create_bindings()

        self.application = Application(layout=self.layout,
                                       key_bindings=self.bindings,
                                       full_screen=True)

        self._exchange.subscribe(Messages.REMOTE_DIRECTORY_SET,
                                 self._set_remote_dir)

    def _render(self):
        top_toolbar = self._render_top_toolbar()
        if self._current_screen is not None:
            main_container = self._current_screen.main_container
        else:
            main_container = Window()
        self.root_container.children = [top_toolbar, main_container]

    def mount(self, screen):
        """
        Mount a screen into the view.

        The screen must have a `main_container` attribute and,
        optionally, a `bindings` attribute.
        """
        if self._current_screen is not None:
            self._current_screen.stop()
        self._current_screen = screen
        if screen.bindings is not None:
            if screen.use_default_bindings:
                merged_key_bindings = merge_key_bindings(
                    [self.bindings, screen.bindings])
                self.application.key_bindings = merged_key_bindings
            else:
                self.application.key_bindings = screen.bindings
        else:
            # Screen does not define additional keybindings
            self.application.key_bindings = self.bindings
        self._render()
        screen.on_mount(self.application)

    def start(self):
        def run():
            try:
                self.application.run()
            except Exception as e:
                traceback.print_exc()
                print(e)

        self._thread = threading.Thread(target=run)
        self._thread.start()
        self._register_resize_handler()

    def stop(self):
        if self.application.is_running:
            self.application.exit()
            self._remove_resize_handler()

    def _register_resize_handler(self):
        # The application receives the signal SIGWINCH
        # when the terminal has been resized.
        self._has_sigwinch = hasattr(signal, "SIGWINCH")
        if self._has_sigwinch:
            self._previous_winch_handler = self._loop.add_signal_handler(
                signal.SIGWINCH, self._on_resize)

    def _remove_resize_handler(self):
        # Remove WINCH handler.
        if self._has_sigwinch:
            self._loop.add_signal_handler(signal.SIGWINCH,
                                          self._previous_winch_handler)

    def _on_resize(self):
        logging.info("Handling application resize event.")
        self.application.invalidate()

    def _render_top_toolbar(self):
        remote_directory_text = (":{}".format(self._remote_directory)
                                 if self._remote_directory is not None else "")
        top_text = ("[Faculty Platform synchronizer]  "
                    "{local_dir} -> "
                    "{project_name}{remote_directory_text}").format(
                        local_dir=self._local_dir,
                        project_name=self._project_name,
                        remote_directory_text=remote_directory_text,
                    )
        top_toolbar = Window(FormattedTextControl(top_text),
                             height=1,
                             style="reverse")
        return top_toolbar

    def _create_bindings(self):
        bindings = KeyBindings()

        @bindings.add("c-c")
        @bindings.add("q")
        def _(event):
            self._exchange.publish(Messages.STOP_CALLED)

        return bindings

    def _set_remote_dir(self, directory):
        self._remote_directory = directory
        self._render()
Exemple #5
0
class App:
    """Main app class to render the UI and run the application.

    It holds the top level layout of the UI and also contains several
    useful public method that user can leverage in their own customization.

    Its a bridge between all UI element and acts like a root level which
    has access to the entire application states. Similar to a app.js
    in React.js.

    Args:
        config: A :class:`~s3fm.api.config.Config` instance.
        no_history: Skip reading history.
            :class:`~s3fm.api.history.History` won't be loaded.
    """
    def __init__(self,
                 config: Config = None,
                 no_history: bool = False) -> None:
        config = config or Config()
        self._style = Style.from_dict(dict(config.style))
        self._rendered = False
        self._no_history = no_history
        self._layout_mode = LayoutMode.vertical
        self._border = config.app.border
        self._current_focus = Pane.left
        self._previous_focus = None
        self._filepane_focus = Pane.left
        self._custom_effects = config.app.custom_effects
        self._history = History(
            dir_max_size=config.history.dir_max_size,
            cmd_max_size=config.history.cmd_max_size,
        )
        self._kb_mode = KBMode.normal

        self._error_mode = Condition(lambda: self._kb_mode == KBMode.error)
        self._command_mode = Condition(lambda: self._kb_mode == KBMode.command)
        self._normal_mode = Condition(lambda: self._kb_mode == KBMode.normal)
        self._search_mode = Condition(lambda: self._kb_mode == KBMode.search)
        self._reverse_search_mode = Condition(
            lambda: self._kb_mode == KBMode.reverse_search)

        self._error = ""
        self._error_type = ErrorType.error
        self._error_pane = ErrorPane(
            error=self._error_mode,
            message=lambda: self._error,
            error_type=lambda: self._error_type,
        )

        self._layout_single = Condition(
            lambda: self._layout_mode == LayoutMode.single)
        self._layout_vertical = Condition(
            lambda: self._layout_mode == LayoutMode.vertical)

        self._left_pane = FilePane(
            pane_id=Pane.left,
            spinner_config=config.spinner,
            linemode_config=config.linemode,
            app_config=config.app,
            redraw=self.redraw,
            layout_single=self._layout_single,
            layout_vertical=self._layout_vertical,
            focus=lambda: self._filepane_focus,
            history=self._history,
            set_error=self.set_error,
        )
        self._right_pane = FilePane(
            pane_id=Pane.right,
            spinner_config=config.spinner,
            linemode_config=config.linemode,
            app_config=config.app,
            redraw=self.redraw,
            layout_single=self._layout_single,
            layout_vertical=self._layout_vertical,
            focus=lambda: self._filepane_focus,
            history=self._history,
            set_error=self.set_error,
        )
        self._command_pane = CommandPane(app=self)
        self._option_pane = OptionPane()

        self._kb = KB(
            app=self,
            kb_maps=config.kb.kb_maps,
            custom_kb_maps=config.kb.custom_kb_maps,
            custom_kb_lookup=config.kb.custom_kb_lookup,
        )

        self._app = Application(
            layout=self.layout,
            full_screen=True,
            after_render=self._after_render,
            style=self._style,
            key_bindings=self._kb,
        )

    def redraw(self) -> None:
        """Instruct the app to redraw itself to the terminal.

        This is useful when trying to force an UI update of the :class:`App`.
        """
        self._app.invalidate()

    async def _load_pane_data(self, pane: FilePane) -> None:
        """Load the data for the target pane and refersh the app.

        Args:
            pane: A `FilePane` instance to load data.
        """
        await pane.load_data()
        self.redraw()

    async def _render_task(self) -> None:
        """Read history and instruct left/right pane to load appropriate data.

        When `App` is created, `KB` is not activated and will only be activated
        once `History` is processed. This decision is made because `Hache` may
        cause the `App` UI to change and confuse the user.
        """
        if not self._no_history:
            await self._history.read()
        self._left_pane.mode = self._history.left_mode
        self._right_pane.mode = self._history.right_mode
        self._left_pane.selected_file_index = self._history.left_index
        self._right_pane.selected_file_index = self._history.right_index
        self._left_pane.path = self._history.left_path
        self._right_pane.path = self._history.right_path
        self.pane_focus(self._history.focus)
        self.layout_switch(self._history.layout)
        self._kb.activated = True
        await asyncio.gather(
            self._load_pane_data(pane=self._left_pane),
            self._load_pane_data(pane=self._right_pane),
        )

    def _after_render(self, _) -> None:
        """Run this function every time the `App` is re-rendered, same as `useEffect` in react.js.

        Using a class state `self._rendered` to force this function to only run once when
        the `App` is first created.

        Loading all relevant data in this method can turn the whole data loading into an
        async experience.
        """
        for use_effect in self._custom_effects:
            use_effect(self)
        if not self._rendered:
            self._rendered = True
            self._left_pane.loading = True
            self._right_pane.loading = True
            asyncio.create_task(self._render_task())

    async def run(self) -> None:
        """Start the application in async mode."""
        await self._app.run_async()

    def pane_focus(self, pane: Pane) -> None:
        """Focus specified pane and set the focus state.

        Args:
            pane: Target pane to focus.

        Examples:
            >>> from s3fm.app import App
            >>> from s3fm.enums import Pane
            >>> app = App() # doctest: +SKIP
            >>> app.pane_focus(Pane.left) # doctest: +SKIP
        """
        if pane in self.filepanes:
            self._kb_mode = KBMode.normal
            self._filepane_focus = pane
        else:
            _kb_mode_map = {
                CommandMode.command: KBMode.command,
                CommandMode.search: KBMode.search,
                CommandMode.reverse_search: KBMode.reverse_search,
            }
            self._kb_mode = _kb_mode_map.get(self._command_pane.mode,
                                             KBMode.command)
        self._previous_focus = self._current_focus
        self._current_focus = pane
        self._app.layout.focus(self.current_focus)

    def pane_focus_other(self) -> None:
        """Focus the other filepane.

        Theres only a maximum of 2 filepane in the app currently. Use
        this method to focus the other filepane.

        This method won't have any effect if the current UI only have
        one filepane.
        """
        if not self._layout_single():
            self.pane_focus(Pane.left if self._current_focus ==
                            Pane.right else Pane.right)

    def cmd_focus(self, mode=CommandMode.command) -> None:
        """Focus the commandpane.

        Args:
            mode: Command mode to set for the commandpane.
        """
        self._command_pane.mode = mode
        self._command_pane.buffer.text = ""
        self.pane_focus(Pane.cmd)

    def cmd_exit(self) -> None:
        """Exit the commandpane and refocus the last focused filepane."""
        self._command_pane.mode = CommandMode.clear
        self._command_pane.buffer.text = ""
        self.current_filepane.searched_indices = None
        self.pane_focus(self._previous_focus or Pane.left)

    def exit(self) -> None:
        """Exit the application and kill all spawed processes."""
        self._history.left_mode = self._left_pane.mode
        self._history.right_mode = self._right_pane.mode
        self._history.left_index = self._left_pane.selected_file_index
        self._history.right_index = self._right_pane.selected_file_index
        self._history.left_path = self._left_pane.path
        self._history.right_path = self._right_pane.path
        self._history.focus = self._filepane_focus
        self._history.layout = self._layout_mode
        if not self._no_history:
            self._history.write()
        self._app.exit()

    def layout_switch(self, layout: LayoutMode) -> None:
        """Switch to a different layout.

        Args:
            layout: Desired layout mode to switch.

        Examples:
            >>> from s3fm.app import App
            >>> from s3fm.enums import LayoutMode
            >>> app = App() # doctest: +SKIP
            >>> app.layout_switch(LayoutMode.vertical) # doctest: +SKIP
        """
        self._layout_mode = layout
        if layout != LayoutMode.single:
            self._app.layout = self.layout
            self.pane_focus(self._current_focus)

    def pane_swap(self, direction: Direction, layout: LayoutMode) -> None:
        """Swap panes left/right/up/down.

        This has side effects where it may cuase layout to change.
        When current layout is `LayoutMode.vertical` and switching
        up/down, layout will be changed to `LayoutMode.horizontal`.

        This function won't have any effect when theres only one filepane.

        Args:
            direction: Desired direction to swap.
            layout: Desired layout.

        Examples:
            >>> from s3fm.app import App
            >>> from s3fm.enums import Direction, LayoutMode
            >>> app = App() # doctest: +SKIP
            >>> app.pane_swap(Direction.left, LayoutMode.vertical) # doctest: +SKIP
        """
        if self._layout_single():
            return
        if (self._current_focus == Pane.right and
            (direction == Direction.right or direction == Direction.down)
                and self._layout_mode == layout):
            return
        if (self._current_focus == Pane.left
                and (direction == Direction.left or direction == Direction.up)
                and self._layout_mode == layout):
            return
        pane_swapped = False
        if not (self._current_focus == Pane.right and
                (direction == Direction.right or direction == Direction.down)
                and self._layout_mode != layout) and not (
                    self._current_focus == Pane.left and
                    (direction == Direction.left or direction == Direction.up)
                    and self._layout_mode != layout):
            pane_swapped = True
            self._left_pane, self._right_pane = self._right_pane, self._left_pane
            self._left_pane.id, self._right_pane.id = (
                self._right_pane.id,
                self._left_pane.id,
            )
        self._layout_mode = layout
        self._app.layout = self.layout
        if pane_swapped:
            self.pane_focus_other()
        else:
            self.pane_focus(self._current_focus)

    def set_error(self, exception: Optional["Notification"] = None) -> None:
        """Configure error notification for the application.

        This should only be used to set non-application error.

        Args:
            exception: A :class:`~s3fm.exceptions.Notification` instance.
        """
        if not exception:
            self._kb_mode = KBMode.normal
            self._error = ""
        else:
            self._kb_mode = KBMode.error
            self._error = str(exception)
            self._error_type = exception.type

    @property
    def command_mode(self) -> Condition:
        """:class:`prompt_toolkit.filters.Condition`: A callable if current focus is commandpane."""
        return self._command_mode

    @property
    def normal_mode(self) -> Condition:
        """:class:`prompt_toolkit.filters.Condition`: A callable if current focus is a filepane."""
        return self._normal_mode

    @property
    def error_mode(self) -> Condition:
        """:class:`prompt_toolkit.filters.Condition`: A callable if the application has error."""
        return self._error_mode

    @property
    def search_mode(self) -> Condition:
        """:class:`prompt_toolkit.filters.Condition`: A callable if the application is searching."""
        return self._search_mode

    @property
    def reverse_search_mode(self) -> Condition:
        """:class:`prompt_toolkit.filters.Condition`: A callable if the application is reverse searching."""
        return self._reverse_search_mode

    @property
    def current_focus(self) -> "Container":
        """:class:`prompt_toolkit.layout.Container`: Get current focused pane."""
        try:
            return {
                **self.filepanes,
                Pane.cmd: self._command_pane,
                Pane.error: self._error_pane,
            }[self._current_focus]
        except KeyError:
            self.set_error(
                Notification("Unexpected focus.",
                             error_type=ErrorType.warning))
            self.pane_focus(Pane.left)
            return self.current_focus

    @property
    def current_filepane(self) -> FilePane:
        """:class:`~s3fm.ui.filepane.FilePane`: Get current focused filepane."""
        try:
            return self.filepanes[self._filepane_focus]
        except KeyError:
            self.set_error(
                Notification("Unexpected focus.",
                             error_type=ErrorType.warning))
            self._filepane_focus = Pane.left
            return self.current_filepane

    @property
    def file_pane_focus(self) -> Pane:
        """Pane: Focused pane ID."""
        return self._filepane_focus

    @property
    def filepanes(self) -> Dict[Pane, FilePane]:
        """Dict[Pane, FilePane]: Get pane mappings."""
        return {
            Pane.left: self._left_pane,
            Pane.right: self._right_pane,
        }

    @property
    def layout(self) -> Layout:
        """:class:`prompt_toolkit.layout.Layout`: Get app layout dynamically."""
        if self._layout_mode == LayoutMode.vertical:
            layout = HSplit([
                VSplit([self._left_pane, self._right_pane]), self._command_pane
            ])

        elif (self._layout_mode == LayoutMode.horizontal
              or self._layout_mode == LayoutMode.single):
            layout = HSplit(
                [self._left_pane, self._right_pane, self._command_pane])
        else:
            self._layout_mode = LayoutMode.vertical
            self.set_error(
                Notification("Unexpected layout.",
                             error_type=ErrorType.warning))
            return self.layout
        if self._border:
            layout = Frame(layout)
        return Layout(
            FloatContainer(
                content=layout,
                floats=[Float(content=self._option_pane), self._error_pane],
            ))

    @property
    def kb(self) -> KB:
        """:class:`~s3fm.api.kb.KB`: KeyBindings."""
        return self._kb

    @property
    def rendered(self) -> bool:
        """bool: :class:`App` rendered status."""
        return self._rendered
class ProgressBar(object):
    """
    Progress bar context manager.

    Usage ::

        with ProgressBar(...) as pb:
            for item in pb(data):
                ...

    :param title: Text to be displayed above the progress bars. This can be a
        callable or formatted text as well.
    :param formatters: List of :class:`.Formatter` instances.
    :param bottom_toolbar: Text to be displayed in the bottom toolbar. This
        can be a callable or formatted text.
    :param style: :class:`prompt_toolkit.styles.BaseStyle` instance.
    :param key_bindings: :class:`.KeyBindings` instance.
    :param file: The file object used for rendering, by default `sys.stderr` is used.

    :param color_depth: `prompt_toolkit` `ColorDepth` instance.
    :param output: :class:`~prompt_toolkit.output.Output` instance.
    :param input: :class:`~prompt_toolkit.input.Input` instance.
    """
    def __init__(self, title=None, formatters=None, bottom_toolbar=None,
                 style=None, key_bindings=None, file=None, color_depth=None,
                 output=None, input=None):
        assert formatters is None or (
            isinstance(formatters, list) and all(isinstance(fo, Formatter) for fo in formatters))
        assert style is None or isinstance(style, BaseStyle)
        assert key_bindings is None or isinstance(key_bindings, KeyBindings)

        self.title = title
        self.formatters = formatters or create_default_formatters()
        self.bottom_toolbar = bottom_toolbar
        self.counters = []
        self.style = style
        self.key_bindings = key_bindings

        # Note that we use __stderr__ as default error output, because that
        # works best with `patch_stdout`.
        self.color_depth = color_depth
        self.output = output or create_output(stdout=file or sys.__stderr__)
        self.input = input or get_default_input()

        self._thread = None

        self._loop = get_event_loop()
        self._previous_winch_handler = None
        self._has_sigwinch = False

    def __enter__(self):
        # Create UI Application.
        title_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.title), height=1, style='class:progressbar,title'),
            filter=Condition(lambda: self.title is not None))

        bottom_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.bottom_toolbar,
                                        style='class:bottom-toolbar.text'),
                   style='class:bottom-toolbar',
                   height=1),
            filter=~is_done & renderer_height_is_known &
                Condition(lambda: self.bottom_toolbar is not None))

        def width_for_formatter(formatter):
            # Needs to be passed as callable (partial) to the 'width'
            # parameter, because we want to call it on every resize.
            return formatter.get_width(progress_bar=self)

        progress_controls = [
            Window(
                content=_ProgressControl(self, f),
                width=functools.partial(width_for_formatter, f))
            for f in self.formatters
        ]

        self.app = Application(
            min_redraw_interval=.05,
            layout=Layout(HSplit([
                title_toolbar,
                VSplit(progress_controls,
                       height=lambda: D(
                           preferred=len(self.counters),
                           max=len(self.counters))),
                Window(),
                bottom_toolbar,
            ])),
            style=self.style,
            key_bindings=self.key_bindings,
            color_depth=self.color_depth,
            output=self.output,
            input=self.input)

        # Run application in different thread.
        def run():
            with _auto_refresh_context(self.app, .3):
                try:
                    self.app.run()
                except BaseException as e:
                    traceback.print_exc()
                    print(e)

        self._thread = threading.Thread(target=run)
        self._thread.start()

        # Attach WINCH signal handler in main thread.
        # (Interrupt that we receive during resize events.)
        self._has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread()
        if self._has_sigwinch:
            self._previous_winch_handler = self._loop.add_signal_handler(
                signal.SIGWINCH, self.app.invalidate)

        return self

    def __exit__(self, *a):
        # Quit UI application.
        if self.app.is_running:
            self.app.exit()

        # Remove WINCH handler.
        if self._has_sigwinch:
            self._loop.add_signal_handler(signal.SIGWINCH, self._previous_winch_handler)

        self._thread.join()

    def __call__(self, data=None, label='', remove_when_done=False, total=None):
        """
        Start a new counter.

        :param label: Title text or description for this progress. (This can be
            formatted text as well).
        :param remove_when_done: When `True`, hide this progress bar.
        :param total: Specify the maximum value if it can't be calculated by
            calling ``len``.
        """
        assert is_formatted_text(label)
        assert isinstance(remove_when_done, bool)

        counter = ProgressBarCounter(
            self, data, label=label, remove_when_done=remove_when_done, total=total)
        self.counters.append(counter)
        return counter

    def invalidate(self):
        self.app.invalidate()
Exemple #7
0
class HummingbotCLI(PubSub):
    def __init__(self, input_handler: Callable, bindings: KeyBindings,
                 completer: Completer, command_tabs: Dict[str, CommandTab]):
        super().__init__()
        self.command_tabs = command_tabs
        self.search_field = create_search_field()
        self.input_field = create_input_field(completer=completer)
        self.output_field = create_output_field()
        self.log_field = create_log_field(self.search_field)
        self.right_pane_toggle = create_log_toggle(self.toggle_right_pane)
        self.live_field = create_live_field()
        self.log_field_button = create_tab_button("Log-pane",
                                                  self.log_button_clicked)
        self.timer = create_timer()
        self.process_usage = create_process_monitor()
        self.trade_monitor = create_trade_monitor()
        self.layout, self.layout_components = generate_layout(
            self.input_field, self.output_field, self.log_field,
            self.right_pane_toggle, self.log_field_button, self.search_field,
            self.timer, self.process_usage, self.trade_monitor,
            self.command_tabs)
        # add self.to_stop_config to know if cancel is triggered
        self.to_stop_config: bool = False

        self.live_updates = False
        self.bindings = bindings
        self.input_handler = input_handler
        self.input_field.accept_handler = self.accept
        self.app: Optional[Application] = None

        # settings
        self.prompt_text = ">>> "
        self.pending_input = None
        self.input_event = None
        self.hide_input = False

        # stdout redirection stack
        self._stdout_redirect_context: ExitStack = ExitStack()

        # start ui tasks
        loop = asyncio.get_event_loop()
        loop.create_task(start_timer(self.timer))
        loop.create_task(start_process_monitor(self.process_usage))
        loop.create_task(start_trade_monitor(self.trade_monitor))

    def did_start_ui(self):
        self._stdout_redirect_context.enter_context(
            patch_stdout(log_field=self.log_field))

        log_level = global_config_map.get("log_level").value
        init_logging("hummingbot_logs.yml", override_log_level=log_level)

        self.trigger_event(HummingbotUIEvent.Start, self)

    async def run(self):
        self.app = Application(layout=self.layout,
                               full_screen=True,
                               key_bindings=self.bindings,
                               style=load_style(),
                               mouse_support=True,
                               clipboard=PyperclipClipboard())
        await self.app.run_async(pre_run=self.did_start_ui)
        self._stdout_redirect_context.close()

    def accept(self, buff):
        self.pending_input = self.input_field.text.strip()

        if self.input_event:
            self.input_event.set()

        try:
            if self.hide_input:
                output = ''
            else:
                output = '\n>>>  {}'.format(self.input_field.text, )
                self.input_field.buffer.append_to_history()
        except BaseException as e:
            output = str(e)

        self.log(output)
        self.input_handler(self.input_field.text)

    def clear_input(self):
        self.pending_input = None

    def log(self, text: str, save_log: bool = True):
        if save_log:
            if self.live_updates:
                self.output_field.log(text, silent=True)
            else:
                self.output_field.log(text)
        else:
            self.output_field.log(text, save_log=False)

    def change_prompt(self, prompt: str, is_password: bool = False):
        self.prompt_text = prompt
        processors = []
        if is_password:
            processors.append(PasswordProcessor())
        processors.append(BeforeInput(prompt))
        self.input_field.control.input_processors = processors

    async def prompt(self, prompt: str, is_password: bool = False) -> str:
        self.change_prompt(prompt, is_password)
        self.app.invalidate()
        self.input_event = asyncio.Event()
        await self.input_event.wait()

        temp = self.pending_input
        self.clear_input()
        self.input_event = None

        if is_password:
            masked_string = "*" * len(temp)
            self.log(f"{prompt}{masked_string}")
        else:
            self.log(f"{prompt}{temp}")
        return temp

    def set_text(self, new_text: str):
        self.input_field.document = Document(text=new_text,
                                             cursor_position=len(new_text))

    def toggle_hide_input(self):
        self.hide_input = not self.hide_input

    def toggle_right_pane(self):
        if self.layout_components["pane_right"].filter():
            self.layout_components["pane_right"].filter = lambda: False
            self.layout_components["item_top_toggle"].text = '< log pane'
        else:
            self.layout_components["pane_right"].filter = lambda: True
            self.layout_components["item_top_toggle"].text = '> log pane'

    def log_button_clicked(self):
        for tab in self.command_tabs.values():
            tab.is_selected = False
        self.redraw_app()

    def tab_button_clicked(self, command_name: str):
        for tab in self.command_tabs.values():
            tab.is_selected = False
        self.command_tabs[command_name].is_selected = True
        self.redraw_app()

    def exit(self):
        self.app.exit()

    def redraw_app(self):
        self.layout, self.layout_components = generate_layout(
            self.input_field, self.output_field, self.log_field,
            self.right_pane_toggle, self.log_field_button, self.search_field,
            self.timer, self.process_usage, self.trade_monitor,
            self.command_tabs)
        self.app.layout = self.layout
        self.app.invalidate()

    def tab_navigate_left(self):
        selected_tabs = [
            t for t in self.command_tabs.values() if t.is_selected
        ]
        if not selected_tabs:
            return
        selected_tab: CommandTab = selected_tabs[0]
        if selected_tab.tab_index == 1:
            self.log_button_clicked()
        else:
            left_tab = [
                t for t in self.command_tabs.values()
                if t.tab_index == selected_tab.tab_index - 1
            ][0]
            self.tab_button_clicked(left_tab.name)

    def tab_navigate_right(self):
        current_tabs = [
            t for t in self.command_tabs.values() if t.tab_index > 0
        ]
        if not current_tabs:
            return
        selected_tab = [t for t in current_tabs if t.is_selected]
        if selected_tab:
            right_tab = [
                t for t in current_tabs
                if t.tab_index == selected_tab[0].tab_index + 1
            ]
        else:
            right_tab = [t for t in current_tabs if t.tab_index == 1]
        if right_tab:
            self.tab_button_clicked(right_tab[0].name)

    def close_buton_clicked(self, command_name: str):
        self.command_tabs[command_name].button = None
        self.command_tabs[command_name].close_button = None
        self.command_tabs[command_name].output_field = None
        self.command_tabs[command_name].is_selected = False
        for tab in self.command_tabs.values():
            if tab.tab_index > self.command_tabs[command_name].tab_index:
                tab.tab_index -= 1
        self.command_tabs[command_name].tab_index = 0
        if self.command_tabs[command_name].task is not None:
            self.command_tabs[command_name].task.cancel()
            self.command_tabs[command_name].task = None
        self.redraw_app()

    def handle_tab_command(self, hummingbot: "HummingbotApplication",
                           command_name: str, kwargs: Dict[str, Any]):
        if command_name not in self.command_tabs:
            return
        cmd_tab = self.command_tabs[command_name]
        if "close" in kwargs and kwargs["close"]:
            if cmd_tab.close_button is not None:
                self.close_buton_clicked(command_name)
            return
        if "close" in kwargs:
            kwargs.pop("close")
        if cmd_tab.button is None:
            cmd_tab.button = create_tab_button(
                command_name, lambda: self.tab_button_clicked(command_name))
            cmd_tab.close_button = create_tab_button(
                "x", lambda: self.close_buton_clicked(command_name), 1, '',
                ' ')
            cmd_tab.output_field = create_live_field()
            cmd_tab.tab_index = max(t.tab_index
                                    for t in self.command_tabs.values()) + 1
        self.tab_button_clicked(command_name)
        self.display_tab_output(cmd_tab, hummingbot, kwargs)

    def display_tab_output(self, command_tab: CommandTab,
                           hummingbot: "HummingbotApplication",
                           kwargs: Dict[Any, Any]):
        if command_tab.task is not None and not command_tab.task.done():
            return
        if threading.current_thread() != threading.main_thread():
            hummingbot.ev_loop.call_soon_threadsafe(self.display_tab_output,
                                                    command_tab, hummingbot,
                                                    kwargs)
            return
        command_tab.task = safe_ensure_future(
            command_tab.tab_class.display(command_tab.output_field, hummingbot,
                                          **kwargs))
Exemple #8
0
class NoobitCLI:
    def __init__(self,
                #  input_handler: Callable,
                #  completer: Completer):
                ):

        completer = None
        self.process_usage = create_process_monitor()
        
        self.search_log_field = create_search_field("logs")
        self.search_out_field = create_search_field("ouput")
        self.input_field = create_input_field(completer=completer)
        self.output_field = create_output_field(self.search_out_field)

        # right hand window
        self.log_field = create_log_field(self.search_log_field)

        self.timer = create_timer()
        self.layout = generate_layout(self.input_field, self.output_field, self.log_field, self.search_log_field, self.search_out_field, self.timer, self.process_usage)
        # add self.to_stop_config to know if cancel is triggered
        self.to_stop_config: bool = False

        self.live_updates = False
        self.bindings = load_key_bindings(self)

        self.input_handler = self._input_handler
        self.input_field.accept_handler = self.accept
        self.app = Application(layout=self.layout, full_screen=True, key_bindings=self.bindings, style=load_style(),
                               mouse_support=True, clipboard=PyperclipClipboard())

        # settings
        self.prompt_text = ">>> "
        self.pending_input = None
        self.input_event = None
        self.hide_input = False

        # start ui tasks
        self.loop = asyncio.get_event_loop()
        self.loop.create_task(start_timer(self.timer))

        #! maximaus added
        self.argparser = load_parser(self)
        self.client = httpx.AsyncClient()
        # TODO update type annotation
        self.ws: typing.Dict[str, KrakenWsPublic] = {}
        # TODO we dont want to hardcode this for every exchange
        self.symbols: typing.Dict = {}
        #     "KRAKEN": None,
        #     "BINANCE": None
        # }



    def _input_handler(self, raw_command):
        """parse input and map it to functions
        """
        try:
            _handle_commands(self, raw_command)
        except Exception as e:
            self.log(e)


    async def run(self):
        await self.app.run_async()


    def accept(self, buff):
        self.pending_input = self.input_field.text.strip()

        if self.input_event:
            self.input_event.set()

        try:
            if self.hide_input:
                output = ''
            else:
                output = '\n>>>  {}'.format(self.input_field.text,)
                self.input_field.buffer.append_to_history()
        except BaseException as e:
            output = str(e)

        self.log(output)
        self.input_handler(self.input_field.text)


    def clear_input(self):
        self.pending_input = None


    def log(self, text: str, save_log: bool = True):
        if save_log:
            if self.live_updates:
                self.output_field.log(text, silent=True)
            else:
                self.output_field.log(text)
        else:
            self.output_field.log(text, save_log=False)


    def clear(self):
        self.output_field.clear()


    def change_prompt(self, prompt: str, is_password: bool = False):
        self.prompt_text = prompt
        processors: typing.List[typing.Any] = []
        if is_password:
            processors.append(PasswordProcessor())
        processors.append(BeforeInput(prompt))
        self.input_field.control.input_processors = processors


    async def prompt(self, prompt: str, is_password: bool = False) -> str:
        self.change_prompt(prompt, is_password)
        self.app.invalidate()
        self.input_event = asyncio.Event()
        await self.input_event.wait()

        temp = self.pending_input
        self.clear_input()
        self.input_event = None

        if is_password:
            masked_string = "*" * len(temp)
            self.log(f"{prompt}{masked_string}")
        else:
            self.log(f"{prompt}{temp}")
        return temp


    def set_text(self, new_text: str):
        self.input_field.document = Document(text=new_text, cursor_position=len(new_text))


    def toggle_hide_input(self):
        self.hide_input = not self.hide_input


    def exit(self):
        self.app.exit()


    # ========================================
    # == COMMANDS


    def set_vars(self, exchange: str, symbol: str, ordType: str, orderQty: float):
        if exchange: settings.EXCHANGE = exchange.upper()
        if symbol: settings.SYMBOL = symbol.upper()
        if ordType: settings.ORDTYPE = ordType.upper()
        if orderQty: settings.ORDQTY = orderQty



    # ========================================
    # == TESTING THAT COUNTING WORKS

    def count(self, start: int, finish: int, step: int):

        async def enum(start, finish, step):
            n = start
            while n < finish:
                # write to a different field
                # save_log option means we will display the record, otherwise log gets cleared on each updated and replaced
                self.log_field.log(text=n, save_log=True)
                n += 1
                await asyncio.sleep(1)

        # async def _await(start, finish, step):
        #     await enum(start, finish, step)

        safe_ensure_future(self, enum(start, finish, step))

    
    async def acount(self, start: int, finish: int, step: int):

        n = start
        while n < finish:
            self.log_field.log(text=n, save_log=True)
            n+=1
            await asyncio.sleep(1)


    # ========================================
    # == UTIL

    def check_symbols(self):
        _ok = True
        for exchange in ntypes.EXCHANGE:
            self.log_field.log(f"Checking symbols for {exchange}")
            if not self.symbols[exchange]:
                self.log_field.log(f"Please initialize symbols for {exchange}")
                _ok = False
        
        return _ok




    # ========================================
    # == HELP

    async def help(
            self,  #type: NoobitCLI
            command: str
            ):
        if command == 'all':
            self.log(self.argparser.format_help())
        else:
            subparsers_actions = [
                action for action in self.argparser._actions if isinstance(action, argparse._SubParsersAction)]

            for subparsers_action in subparsers_actions:
                subparser = subparsers_action.choices.get(command)
                if subparser:
                    self.log(subparser.format_help())

    
    async def list(
        self
    ):
        await self.help("all")



    # ========================================
    # ADD API KEYS

    async def add_keys(self, exchange: str, key: str, secret: str):

        if not exchange: exchange = settings.EXCHANGE 
        else: exchange = exchange.upper()

        from noobit_markets.path import APP_PATH
        import time
        import os

        self.log(APP_PATH)

        path = os.path.join(APP_PATH, "exchanges", exchange.lower(), "rest", ".env")
        self.log(path)

        with open(path, "a") as file:
            timeid = int(time.time())
            file.write("\n")
            file.write(f"\n{exchange}_API_KEY_{timeid} = {key}")
            file.write(f"\n{exchange}_API_SECRET_{timeid} = {secret}")

            # TODO we should probably make a bogus private rest call to check if it works

            self.log_field.log("API Credentials added\n")


    # ========================================
    # PUBLIC ENDPOINTS COMMANDS


    async def fetch_symbols(self):
        kraken_symbols = await KRAKEN.rest.public.symbols(self.client)
        binance_symbols = await BINANCE.rest.public.symbols(self.client)
        ftx_symbols = await FTX.rest.public.symbols(self.client)

        if kraken_symbols.is_ok():
            self.symbols["KRAKEN"] = kraken_symbols
        else:
            self.log_field.log("Error fetching kraken symbols")
            self.log("Error fetching kraken symbols")
            self.log(kraken_symbols.value)
        
        if binance_symbols.is_ok():
            self.symbols["BINANCE"] = binance_symbols
        else:
            self.log_field.log("Error fetching binance symbols")
            self.log("Error fetching binance symbols")
            self.log(binance_symbols.value)
            
        if ftx_symbols.is_ok():
            self.symbols["FTX"] = ftx_symbols
        else:
            self.log_field.log("Error fetching ftx symbols")
            self.log("Error fetching ftx symbols")
            self.log(ftx_symbols.value)



    def show_symbols(self, exchange: str):
        from noobit_markets.base.models.rest.response import NSymbol

        cap_exch = exchange.upper()
        
        if cap_exch in ntypes.EXCHANGE.__members__.keys():
            if cap_exch in self.symbols.keys():

                self.log_field.log(f"Exchange is accepted: {cap_exch}")
                _sym = NSymbol(self.symbols[cap_exch])
                if _sym.is_err():
                    self.log("Err")
                    self.log(_sym.result)
                else:
                    self.log("OK")
                    self.log(_sym.table)

            else:
                self.log("Please initialize symbols for this exchange")

        else:
            self.log("Unknown Exchange requested")


    @ensure_symbols
    async def fetch_ohlc(self, exchange: str, symbol: str, timeframe: str, since: typing.Optional[int]=None):
        from noobit_markets.base.models.rest.response import NOhlc

        self.log_field.log("CALLED fetch_ohlc")

        if not symbol:
            if not settings.SYMBOL or settings.SYMBOL.isspace(): 
                return Err("Please set or pass <symbol> argument")
            else:
                symbol = settings.SYMBOL
        else: symbol=symbol.upper()

        interface = globals()[exchange]
        _res = await interface.rest.public.ohlc(self.client, symbol.upper(), self.symbols[exchange].value, timeframe.upper(), since)
        _ohlc = NOhlc(_res)
        return _ohlc

        

    @ensure_symbols
    async def fetch_orderbook(self, exchange: str, symbol: str, depth: int):
        from noobit_markets.base.models.rest.response import NOrderBook

        self.log_field.log("CALLED fetch_orderbook")

        if not symbol:
            if not settings.SYMBOL or settings.SYMBOL.isspace(): 
                return Err("Please set or pass <symbol> argument")
            else:
                symbol = settings.SYMBOL
        else: symbol=symbol.upper()
        
        interface = globals()[exchange]
        _res = await interface.rest.public.orderbook(self.client, symbol.upper(), self.symbols[exchange].value, depth)
        _book = NOrderBook(_res)
        return _book


    @ensure_symbols
    async def fetch_trades(self, exchange: str, symbol: str, since: typing.Optional[int]=None):
        from noobit_markets.base.models.rest.response import NTrades

        self.log_field.log("CALLED fetch_trades")
        
        if not symbol: 
            if not settings.SYMBOL or settings.SYMBOL.isspace(): 
                return Err("Please set or pass <symbol> argument")
            else: symbol = settings.SYMBOL
        else: symbol=symbol.upper()
        
        interface = globals()[exchange]
        _res = await interface.rest.public.trades(self.client, symbol, self.symbols[exchange].value, since)
        _trd = NTrades(_res)
        return _trd
        


    # ========================================
    # PRIVATE ENDPOINTS COMMANDS


    @ensure_symbols
    async def fetch_balances(self, exchange: str):
        from noobit_markets.base.models.rest.response import NBalances
        
        self.log_field.log("CALLED fetch_balances")
        
        
        interface = globals()[exchange]
        _res = await interface.rest.private.balances(self.client, self.symbols[exchange].value)
        _bal = NBalances(_res)
        return _bal


    @ensure_symbols
    async def fetch_exposure(self, exchange: str):
        from noobit_markets.base.models.rest.response import NExposure

        self.log_field.log("CALLED fetch_exposure")
        self.log_field.log(f"Requested Exchange : {exchange.upper()}")

        interface = globals()[exchange]

        _res = await interface.rest.private.exposure(self.client, self.symbols[exchange].value)
        _exp = NExposure(_res)
        return _exp


    @ensure_symbols
    async def fetch_usertrades(self, exchange: str, symbol: str):
        from noobit_markets.base.models.rest.response import NTrades

        self.log_field.log("CALLED fetch_usertrades")

        if not symbol: symbol = settings.SYMBOL
        else: symbol=symbol.upper()

        self.log_field.log(f"Requested Symbol : {symbol}")
        self.log_field.log(f"Requested Exchange : {exchange}")
        
        interface = globals()[exchange]
        _res = await interface.rest.private.trades(self.client, symbol, self.symbols[exchange].value)
        _utr = NTrades(_res)
        return _utr


    @ensure_symbols
    async def fetch_openorders(self, exchange: str, symbol: str):
        from noobit_markets.base.models.rest.response import NOrders
    
        self.log_field.log("CALLED fetch_openorders") 
        
        if not symbol: 
            if not settings.SYMBOL or settings.SYMBOL.isspace(): 
                return Err("Please set or pass <symbol> argument")
            else: symbol = settings.SYMBOL
        else: symbol=symbol.upper()

        self.log_field.log(f"Requested Symbol : {symbol}")
        self.log_field.log(f"Requested Exchange : {exchange}")
        
        interface = globals()[exchange]
        _res = await interface.rest.private.open_orders(self.client, symbol, self.symbols[exchange].value)
        _opo = NOrders(_res)
        return _opo
        

    @ensure_symbols
    async def create_neworder(
        self,
        exchange: str,
        symbol: str,
        ordType: str,
        clOrdID,
        orderQty: float,
        price: float,
        timeInForce: str = "GOOD-TIL-CANCEL",
        quoteOrderQty: typing.Optional[float] = None,
        stopPrice: typing.Optional[float] = None,
        *,
        side: str,
        blind: bool,
        split: typing.Optional[int] = None,
        delay: typing.Optional[int] = None,
        step: typing.Optional[float] = None,
    ):
        from noobit_markets.base.models.rest.response import NSingleOrder

        self.log_field.log("CALLED create_neworder") 

        if not symbol: 
            if not settings.SYMBOL or settings.SYMBOL.isspace(): 
                return Err("Please set or pass <symbol> argument")
            else: symbol = settings.SYMBOL
        else: symbol=symbol.upper()

        if not ordType:
            if not settings.ORDTYPE or settings.ORDTYPE.isspace(): 
                return Err("Please set or pass <ordType> argument")
            else: ordType = settings.ORDTYPE
        # TODO be consistent: either all noobit types in capital or in lowercase
        else: ordType = ordType.upper()

        if not orderQty:
            if not settings.ORDQTY: 
                return Err("Please set or pass <orderQty> argument")
            else: orderQty = settings.ORDQTY

        if not timeInForce and ordType in ["LIMIT", "STOP-LOSS-LIMIT", "TAKE-PROFIT-LIMIT"]:
            self.log("WARNING : <timeInForce> not provided, defaulting to <GOOD-TIL-CANCEL>")
            timeInForce = "GOOD-TIL-CANCEL"

        
        interface = globals()[exchange]

        if split:
            # step is only for limit orders
            if step:
                if not ordType in ["LIMIT", "STOP_LIMIT", "TAKE_PROFIT"]:
                    return Err(f"Argument <step>: Ordertype can not be {ordType}")
            
            # only one of delay or step
            if not any([delay, step]) or all([delay, step]):
                return Err("Please set only one of <delay> or <step> argument to split orders")
            else: 
                _acc = []
                acc_price = price

                for i in range(split):
                    _res = await interface.rest.private.new_order(
                        client=self.client,
                        symbol=symbol, 
                        symbols_resp=self.symbols[exchange].value, 
                        side=side, 
                        ordType=ordType, 
                        clOrdID=clOrdID, 
                        # orderQty=round(orderQty/split, self.symbols[exchange].value.asset_pairs[symbol].volume_decimals), # TODO atm we limit to 2 decimal places, could check max decimals for pair, ALSO this can lead to keyerror if symbol is not on exchange
                        orderQty=round(orderQty/split, 2), # TODO atm we limit to 2 decimal places, could check max decimals for pair, ALSO this can lead to keyerror if symbol is not on exchange
                        price=acc_price, 
                        timeInForce=timeInForce, 
                        quoteOrderQty=quoteOrderQty, 
                        stopPrice=stopPrice
                        )
                    if _res.is_ok():
                        if blind:
                            _res.value.price = None
                        _acc.append(_res.value)
                        self.log_field.log(f"Successful order, count {len(_acc)}")
                    else:
                        self.log_field.log(f"Failed order")
                        return _res
                    
                    if step:
                        acc_price = round(float(acc_price + step), 2) # FIXME this will create decimal place precision errors sometimes, we need to round somehow (use context ??) 
                        await asyncio.sleep(1)
                    else:
                        await asyncio.sleep(delay)  # avoid rate limiting


                try:
                    
                    if blind:
                        self.log("Argument <blind>: Setting Order Price to <None>")
                    
                    _splitorders = NoobitResponseClosedOrders(
                        exchange="KRAKEN",
                        rawjson={},
                        orders=_acc
                    )
                    # request coros always return a result
                    # so we wrap the validated model in an OK container
                    
                    _nords = NOrders(Ok(_splitorders))
                    return _nords
                except ValidationError as e:
                    return Err(e)


        else:

            if any([delay, step]):
                self.log_field.log("Argument <delay> or <step> require <split>")    
                return Err("Argument <delay> or <step> require <split>")    

            _res = await interface.rest.private.new_order(
                client=self.client,
                symbol=symbol, 
                symbols_resp=self.symbols[exchange].value, 
                side=side, 
                ordType=ordType, 
                clOrdID=clOrdID, 
                orderQty=orderQty, 
                price=price, 
                timeInForce=timeInForce, 
                quoteOrderQty=quoteOrderQty, 
                stopPrice=stopPrice
                )

            if blind:
                self.log("Argument <blind>: Setting Order Price to <None>")
                if _res.is_ok():
                    _res.value.price = None

            _nord = NSingleOrder(_res)
        
            return _nord


    # side argument isnt registered for some reason (in following partials)
    # create_buyorder: typing.Coroutine = functools.partialmethod(create_neworder, "BUY")
    # create_sellorder: typing.Coroutine = functools.partialmethod(create_neworder, "SELL")

    
    async def create_buyorder(
        self,
        exchange: str,
        symbol: str,
        ordType: str,
        clOrdID,
        orderQty: float,
        price: float,
        timeInForce: str,
        stopPrice: float,
        blind: bool,
        split: int,
        delay: int,
        step: int
    ):

        await self.create_neworder(exchange, symbol, ordType, clOrdID, orderQty, price, timeInForce, stopPrice, blind=blind, split=split, delay=delay, step=step, side="BUY")


    async def create_sellorder(
        self,
        exchange: str,
        symbol: str,
        ordType: str,
        clOrdID,
        orderQty: float,
        price: float,
        timeInForce: str,
        stopPrice: float,
        blind: bool,
        split: int,
        delay: int,
        step: int
    ):

        await self.create_neworder(exchange, symbol, ordType, clOrdID, orderQty, price, timeInForce, stopPrice, blind=blind, split=split, delay=delay, step=step, side="SELL")


    # TODO add remove_order to binance interface
    @ensure_symbols
    async def cancel_order(
        self,
        exchange: str,
        symbol: str,
        slice: str,
        all: bool
    ):
        
        self.log_field.log("CALLED cancel_order") 

        # testlist = [x for x in range(0, 100)]
        # regex = re.compile(r"^\[[-]?[0-9]+:[-]?[0-9]+\]$")
        regex = "^\[[-]?[0-9]+:[-]?[0-9]+:[-]?[0-9]\]$"
        match = re.match(regex, slice)
        
        if not match:
            self.log(match)
            return Err(f"Argument position did not match regex - Given {slice}")
        else:
            # sliced = eval(f"testlist{position}")
            # self.log(sliced)
            # return Ok("SUCCESS")
        
            if not symbol: 
                if not settings.SYMBOL or settings.SYMBOL.isspace(): 
                    return Err("Please set or pass <symbol> argument")
                else: symbol = settings.SYMBOL
            else: symbol=symbol.upper()

            self.log_field.log(f"Requested Symbol : {symbol}")
            self.log_field.log(f"Requested Exchange : {exchange}")
            
            interface = globals()[exchange]
            _res = await interface.rest.private.open_orders(self.client, symbol, self.symbols[exchange].value)

            if _res.is_err():
                return _res.value
            else:
                _acc = []

                _all_orders = sorted([order for order in _res.value.orders], key=lambda x: getattr(x, "price"), reverse=False)
                _sliced_orders = eval(f"_all_orders{slice}") 

                for _order in _sliced_orders:

                    _ord = await interface.rest.private.remove_order(self.client, symbol, self.symbols[exchange].value, _order.orderID)

                    if _ord.is_ok():
                        _acc.append(_ord.value)
                    else:
                        return _ord

                    await asyncio.sleep(1)
                
                try:
                    _canceled_orders = NoobitResponseClosedOrders(
                        exchange="KRAKEN",
                        rawjson={},
                        orders=_acc
                    )
                    # request coros always return a result
                    # so we wrap the validated model in an OK container
                    _nords = NOrders(Ok(_canceled_orders))
                    return _nords
                except ValidationError as e:
                    return Err(e)




    # ========================================
    # START WEBSOCKET PUBLIC STREAMS

    async def connect(self):
        import websockets
        from noobit_markets.exchanges.kraken.websockets.public.routing import msg_handler
        from noobit_markets.exchanges.kraken.websockets.public.api import KrakenWsPublic

        
        feed_map = {
            "trade": "trade",
            "ticker": "instrument",
            "book": "orderbook",
            "spread": "spread"
        }

        #! only connect Kraken for now
        client = await websockets.connect("wss://ws.kraken.com")
        self.ws["KRAKEN"] = KrakenWsPublic(client, msg_handler, self.loop, feed_map)

        self.log(client)


    #! only stream Kraken for now, need to call connect before
    async def stream_orderbook(self, exchange: str, symbol: str, depth: str):


        if not exchange: exchange = settings.EXCHANGE
        if not symbol: symbol = settings.SYMBOL
        
        self.log(self.ws[exchange])
        self.log(self.ws[exchange].client)
        self.log(self.ws[exchange].orderbook)

        # while True:
            # self.log_field.log("Heartbeat")
            # await asyncio.sleep(2)

        async for msg in self.ws[exchange].orderbook(self.symbols[exchange].value, symbol, depth, True):
            self.log_field.log("Got new message from orderbook websocket")
Exemple #9
0
class ChatTUI(object):
    def __init__(self, client=None, messages=None):
        self.client = client
        self.send = self.client.send

        # Style.
        self.classic_style = Style([
            ("output-field", "bg:#000044 #ffffff"),
            ("input-field", "bg:#000000 #ffffff"),
            ("line", "#004400"),
        ])

        self.dark_mode = Style([
            ("output-field", "bg:#2b2b2b #ffffff"),
            ("input-field", "bg:#000000 #ffffff"),
            ("line", "#004400"),
        ])

        self.dakes_theme = Style([
            ("output-field", "bg:#004400 #ffffff"),
            ("input-field", "bg:#000000 #ffffff"),
            ("line", "#aa007f"),
        ])

        self.themes = ["classic style", "dark mode", "dakes theme"]

        self.themes_help_txt = "Available themes  are: \n" + str(self.themes) + \
                               '\nYou select a theme by typing: "!theme classic style"'

        self.themes_help_msg = Message(Message.server_user,
                                       self.themes_help_txt)

        self.style = self.classic_style

        self.welcome_txt = """
        Welcome to honkuru. The peer-to-peer text chat. 
        To send a message just type it and send with 'enter'. 
        To display the help message type '!help'\n
        """
        self.welcome_msg = Message(Message.server_user, self.welcome_txt)

        self.help_txt = """To display this help type: {}
        To display all available color themes type: {}
        To disconnect type: {}
        or Press Ctrl+C or Ctrl+Q""".format(Message.help, Message.theme,
                                            Message.disconnect)
        self.help_msg = Message(Message.server_user, self.help_txt)

        # reference to messages object of client
        self.manager = multiprocessing.Manager()

        self.messages = messages
        self.messages.append(self.welcome_msg)

        self.application = Application()

        # self.draw()

    def main(self):
        # The layout.
        self.output_field = TextArea(style="class:output-field",
                                     text=self.welcome_msg.message)
        self.input_field = TextArea(
            height=1,
            prompt=">>> ",
            style="class:input-field",
            multiline=False,
            wrap_lines=False,
        )

        container = HSplit([
            self.output_field,
            Window(height=1, char="-", style="class:line"),
            self.input_field,
        ])

        self.input_field.accept_handler = self.send_message

        # The key bindings.
        kb = KeyBindings()

        @kb.add("c-c")
        @kb.add("c-q")
        def _(event):
            """ Pressing Ctrl-Q or Ctrl-C will exit the user interface. """
            self.client.client_socket.send(Message.close_connection_client)
            sleep(2)
            event.app.exit()

        # Run application.
        self.application = Application(
            layout=Layout(container, focused_element=self.input_field),
            key_bindings=kb,
            style=self.style,
            mouse_support=True,
            full_screen=True,
        )

        # _thread.start_new_thread(self.render_messages, ("Thread-1", 2,))

        t = threading.Thread(
            target=self.render_messages,
            args=(),
        )
        t.daemon = True
        t.start()

        self.application.run()

    def send_message(self, buff):
        """
        Will send the message typed by calling the reference to the send function of the client
        Also handles some special command, like theme changing
        :param buff:
        :return:
        """
        msg = self.input_field.text

        if msg and msg[0] == Message.command_prefix:
            # change color themes
            if Message.theme in msg:
                theme = msg.lstrip(Message.theme).lstrip()
                if theme == "classic style" or theme == "classic_style":
                    self.style = self.classic_style
                elif theme == "dark mode" or theme == "dark_mode":
                    self.style = self.dark_mode
                elif "dakes" in theme:
                    self.style = self.dakes_theme

                else:
                    self.messages.append(self.themes_help_msg)
                self.application.style = self.style

            # disconnect is handled by client
            elif Message.disconnect in msg:
                # self.disconnect()
                self.client.client_socket.sendall(
                    Message.close_connection_client)
                # self.client.disconnect()
                # self.disconnect()

            # help Message
            elif Message.help in msg:
                self.messages.append(self.help_msg)

        else:
            self.client.send(msg)

    def render_messages(self):
        msg_len = 0
        while True:
            sleep(0.01)
            if msg_len < len(self.messages):

                # print(self.messages)
                new_text = ""
                msg = ""
                for msg in self.messages:
                    new_text = new_text + "\n" + msg.user + ": " + msg.message

                self.output_field.buffer.document = Document(
                    text=new_text, cursor_position=len(new_text))
                msg_len = len(self.messages)

    def disconnect(self):
        self.messages.append(Message(Message.client_user, "Disconnecting... "))
        sleep(1)
        try:
            self.application.exit()
        except Exception:
            pass
Exemple #10
0
class AppManager():
    def __init__(self):
        self.browser = Browser()
        self.wm = WindowManager()
        self.current_dir = None
        self.current_station = None

        @Condition
        def is_not_dialog_active():
            return not self.wm.is_dialog_active

        kb = KeyBindings()
        kb.add('tab', filter=is_not_dialog_active)(self.next_window)
        kb.add('s-tab', filter=is_not_dialog_active)(self.prev_window)
        kb.add('c-q', filter=is_not_dialog_active)(self.exit)
        kb.add('escape')(self.close_dialog)
        kb.add('c-p')(self.play)
        kb.add('c-s')(self.stop)
        kb.add('c-l', filter=is_not_dialog_active)(self.add_to_list)

        root = AppManager.format_dirs(self.browser.fetch()[0])
        self.wm.append_folder(root, self.on_click_folder, 'No lists')
        self.app = Application(full_screen=True,
                               layout=self.wm.layout,
                               key_bindings=kb)
        self.instance = vlc.Instance('--input-repeat=-1', '--fullscreen',
                                     '--quiet', '--file-logging',
                                     '--logfile={vlc_log}', '--logmode=text',
                                     '--log-verbose=3')
        self.player = self.instance.media_player_new()

    def next_window(self, _ev=None):
        self.app.layout.focus(self.wm.next)

    def prev_window(self, _ev=None):
        self.app.layout.focus(self.wm.prev)

    def keep_window(self, _ev=None):
        self.app.layout.focus(self.wm.current)

    def exit(self, _ev):
        self.app.exit()

    def on_click_folder(self, dir_name):
        self.fetch(dir_name)
        self.app.layout = self.wm.layout
        self.next_window()
        self.current_dir = dir_name

    def on_click_station(self, station_name):
        stations = self.browser.stations[self.current_dir]
        station = next(x for x in stations if x['name'] == station_name)
        self.current_station = station
        self.play()

    def fetch(self, dir_name):
        dirs, stations = self.browser.fetch(dir_name)

        if len(dirs) > 0:
            self.wm.insert_folder(AppManager.format_dirs(dirs),
                                  self.on_click_folder)
        if len(stations) > 0:
            self.wm.show_stations(dir_name,
                                  AppManager.format_stations(stations),
                                  self.on_click_station, 'No stations')

    def play(self, _ev=None):
        log.info('currently playing: %s', self.current_station)
        media = self.instance.media_new(self.current_station['url'])
        self.wm.playing = HTML('<u>Playing</u>: ' +
                               self.current_station['name'])
        self.app.layout = self.wm.layout
        self.keep_window()
        self.player.set_media(media)
        self.player.play()

    def stop(self, _ev=None):
        self.wm.playing = HTML('<u>Stopped</u>: ' +
                               self.current_station['name'])
        self.app.layout = self.wm.layout
        self.keep_window()
        self.player.stop()

    def add_to_list(self, _ev=None):
        if self.current_station:

            def select_list(st_list):
                stations = list(
                    map(lambda st: st[0], MyStations.get_stations(st_list)))

                if self.current_station['name'] in stations:
                    log.info('%s station already in %s\'s list',
                             self.current_station['name'], st_list)
                else:
                    log.info('save %s in %s\'s list',
                             self.current_station['name'], st_list)
                    MyStations.save_station(st_list,
                                            str(self.current_station['name']),
                                            str(self.current_station['url']))
                self.close_dialog()

            def new_list(buffer):
                log.info('new list: %s', buffer.text)
                select_list(buffer.text)

            radio_list = RadioList(values=list(
                map(lambda i: (i, i), MyStations.get_lists())),
                                   handler=select_list)

            text_input = TextArea(multiline=False, accept_handler=new_list)

            dialog = Dialog(title="Add to list",
                            body=HSplit([text_input, radio_list], padding=1),
                            width=40,
                            with_background=True)
            self.wm.show_dialog(dialog)
            self.app.layout = self.wm.layout
            self.keep_window()

    def run(self):
        self.app.run()

    def close_dialog(self, _ev=None):
        self.wm.hide_dialog()
        self.app.layout = self.wm.layout
        self.keep_window()

    @staticmethod
    def format_dirs(dirs):
        list = []
        for i, di in enumerate(dirs):
            list.append((i, di["title"]))
        return list

    @staticmethod
    def format_stations(stations):
        list = []
        for i, di in enumerate(stations):
            list.append((i, di["name"]))
        return list