Пример #1
0
def pprint_routes(resp: 'Response', stack_limit: int = 3):
    """Pretty print routes with :mod:`prettytable`, fallback to :func:`print`.

    :param resp: the :class:`Response` object
    :param stack_limit: traceback limit
    """
    routes = resp.routes

    from rich import box
    from rich.table import Table

    table = Table(box=box.SIMPLE)
    for v in ('Executor', 'Time', 'Exception'):
        table.add_column(v)

    for route in routes:
        status_icon = '🟢'
        if route.status.code == jina_pb2.StatusProto.ERROR:
            status_icon = '🔴'
        elif route.status.code == jina_pb2.StatusProto.ERROR_CHAINED:
            status_icon = '⚪'

        table.add_row(
            f'{status_icon} {route.executor}',
            f'{route.start_time.ToMilliseconds() - routes[0].start_time.ToMilliseconds()}ms',
            ''.join(route.status.exception.stacks[-stack_limit:]),
        )

    console = get_rich_console()
    console.print(table)
Пример #2
0
    def __init__(
        self,
        description: str = 'Working...',
        total_length: Optional[float] = None,
        message_on_done: Optional[Union[str, Callable[..., str]]] = None,
        columns: Optional[Union[str, ProgressColumn]] = None,
        disable: bool = False,
        console: Optional[Console] = None,
        **kwargs,
    ):
        """Init a custom progress bar based on rich. This is the default progress bar of jina if you want to  customize
        it you should probably just use a rich `Progress` and add your custom column and task

        :param description: description of your task ex : 'Working...'
        :param total_length: the number of steps
        :param message_on_done: The message that you want to print at the end of your task. It can either be a string to
        be formatted with task (ex '{task.completed}') task or a function which take task as input (ex : lambda task : f'{task.completed}'
        :param columns: If you want to customize the column of the progress bar. Note that you should probably directly use
        rich Progress object than overwriting these columns parameters.
        :param total_length: disable the progress bar



        .. # noqa: DAR202
        .. # noqa: DAR101
        .. # noqa: DAR003

        """
        def _default_message_on_done(task):
            return f'{task.completed} steps done in {get_readable_time(seconds=task.finished_time)}'

        columns = columns or [
            SpinnerColumn(),
            _OnDoneColumn(f'DONE', description, 'progress.description'),
            BarColumn(complete_style='green', finished_style='yellow'),
            TimeElapsedColumn(),
            '[progress.percentage]{task.percentage:>3.0f}%',
            TextColumn('ETA:', style='progress.remaining'),
            TimeRemainingColumn(),
            _OnDoneColumn(message_on_done
                          if message_on_done else _default_message_on_done),
        ]

        if not console:
            console = get_rich_console()

        super().__init__(*columns, console=console, disable=disable, **kwargs)

        self.task_id = self.add_task(
            'Working...', total=total_length if total_length else 100.0)
Пример #3
0
def _try_plugin_command():
    """Tries to call the CLI of an external Jina project.

    :return: if the plugin has been found (locally or among the known plugins)
    """
    argv = sys.argv
    if len(argv) < 2:  # no command given
        return False

    from jina_cli.autocomplete import ac_table

    if argv[1] in ac_table[
            'commands']:  # native command can't be plugin command
        return False

    def _cmd_exists(cmd):
        return shutil.which(cmd) is not None

    subcommand = argv[1]
    cmd = 'jina-' + subcommand
    if _cmd_exists(cmd):
        subprocess.run([cmd] + argv[2:])
        return True

    from jina_cli.known_plugins import plugin_info

    if subcommand in plugin_info:
        from jina.helper import get_rich_console

        cmd_info = plugin_info[subcommand]
        project, package = cmd_info['display-name'], cmd_info['pip-package']
        console = get_rich_console()
        console.print(
            f"It seems like [yellow]{project}[/yellow] is not installed in your environment."
            f"To use it via the [green]'jina {subcommand}'[/green] command, "
            f"install it first: [green]'pip install {package}'[/green].")
        return True
    return False
Пример #4
0
def _get_run_args(print_args: bool = True):
    from jina.helper import get_rich_console
    from jina.parsers import get_main_parser

    console = get_rich_console()

    silent_print = {'help', 'hub', 'export'}

    parser = get_main_parser()
    if len(sys.argv) > 1:
        from argparse import _StoreAction, _StoreTrueAction

        from rich import box
        from rich.table import Table

        args, unknown = parser.parse_known_args()

        if unknown:
            from jina.helper import warn_unknown_args

            unknown = list(filter(lambda x: x.startswith('--'), unknown))
            warn_unknown_args(unknown)

        if args.cli not in silent_print and print_args:
            from jina import __resources_path__

            p = parser._actions[-1].choices[sys.argv[1]]
            default_args = {
                a.dest: a.default
                for a in p._actions
                if isinstance(a, (_StoreAction, _StoreTrueAction))
            }

            with open(os.path.join(__resources_path__, 'jina.logo')) as fp:
                logo_str = fp.read()

            param_str = Table(
                title=' '.join(sys.argv),
                box=box.ROUNDED,
                highlight=True,
                title_justify='left',
            )
            param_str.add_column('Argument', justify='right')
            param_str.add_column('Value', justify='left')

            for k, v in sorted(vars(args).items()):
                param = k.replace('_', '-')
                value = str(v)

                if not default_args.get(k, None) == v:
                    value = f'[b]{value}[/]'

                param_str.add_row(param, value)

            if 'JINA_LOG_NO_COLOR' not in os.environ:
                print(f'\n{logo_str}\n')
            console.print(param_str)
        return args
    else:
        parser.print_help()
        exit()
Пример #5
0
    def new(self) -> None:
        """Create a new executor folder interactively."""

        from rich import box, print
        from rich.panel import Panel
        from rich.progress import track
        from rich.prompt import Confirm, Prompt
        from rich.syntax import Syntax
        from rich.table import Table

        console = get_rich_console()

        print(
            Panel.fit(
                '''
[bold green]Executor[/bold green] is how Jina processes [bold]Document[/bold].

This guide helps you to create your own Executor in 30 seconds.''',
                title='Create New Executor',
            ))

        exec_name = (self.args.name if self.args.name else Prompt.ask(
            ':grey_question: What is the [bold]name[/bold] of your executor?\n'
            '[dim]CamelCase is required[/dim]',
            default=f'MyExecutor{random.randint(0, 100)}',
        ))

        exec_path = (self.args.path if self.args.path else Prompt.ask(
            ':grey_question: [bold]Which folder[/bold] to store your executor?',
            default=os.path.join(os.getcwd(), exec_name),
        ))
        exec_description = '{{}}'
        exec_keywords = '{{}}'
        exec_url = '{{}}'

        is_dockerfile = False

        if self.args.advance_configuration or Confirm.ask(
                '[green]That\'s all we need to create an Executor![/green]\n'
                ':grey_question: Or do you want to proceed to advanced configuration',
                default=False,
        ):
            exec_description = (
                self.args.description if self.args.description else
                (Prompt.ask(
                    ':grey_question: Please give a [bold]short description[/bold] of your executor?\n'
                    f'[dim]Example: {exec_name} embeds images into 128-dim vectors using ResNet.[/dim]'
                )))

            exec_keywords = (self.args.keywords if self.args.keywords else (
                Prompt.ask(
                    ':grey_question: Please give some [bold]keywords[/bold] to help people search your executor [dim](separated by comma)[/dim]\n'
                    f'[dim]Example: image cv embedding encoding resnet[/dim]'))
                             )

            exec_url = (self.args.url if self.args.url else (Prompt.ask(
                ':grey_question: What is the [bold]URL[/bold] for GitHub repo?\n'
                f'[dim]Example: https://github.com/yourname/my-executor[/dim]')
                                                             ))

            print(
                Panel.fit(
                    '''
[bold]Dockerfile[/bold] describes how this executor will be built. It is useful when
your executor has non-trivial dependencies or must be run under certain environment.

- If the [bold]Dockerfile[/bold] is missing, Jina automatically generates one for you.
- If you provide one, then Jina will respect the given [bold]Dockerfile[/bold].''',
                    title='[Optional] [bold]Dockerfile[/bold]',
                    width=80,
                ))

            is_dockerfile = self.args.add_dockerfile or Confirm.ask(
                ':grey_question: Do you need to write your own [bold]Dockerfile[/bold] instead of the auto-generated one?',
                default=False,
            )
            print('[green]That\'s all we need to create an Executor![/green]')

        def mustache_repl(srcs):
            for src in track(srcs,
                             description=f'Creating {exec_name}...',
                             total=len(srcs)):
                with open(
                        os.path.join(__resources_path__, 'executor-template',
                                     src)) as fp, open(
                                         os.path.join(exec_path, src),
                                         'w') as fpw:
                    f = (fp.read().replace('{{exec_name}}', exec_name).replace(
                        '{{exec_description}}', exec_description).replace(
                            '{{exec_keywords}}',
                            str(exec_keywords.split(','))).replace(
                                '{{exec_url}}', exec_url))

                    f = [
                        v + '\n' for v in f.split('\n')
                        if not ('{{' in v or '}}' in v)
                    ]
                    fpw.writelines(f)

        Path(exec_path).mkdir(parents=True, exist_ok=True)
        pkg_files = [
            'executor.py',
            'manifest.yml',
            'README.md',
            'requirements.txt',
            'config.yml',
        ]

        if is_dockerfile:
            pkg_files.append('Dockerfile')

        mustache_repl(pkg_files)

        table = Table(box=box.SIMPLE)
        table.add_column('Filename', style='cyan', no_wrap=True)
        table.add_column('Description', no_wrap=True)

        # adding the columns in order of `ls` output
        table.add_row(
            'config.yml',
            'The YAML config file of the Executor. You can define [bold]__init__[/bold] arguments using [bold]with[/bold] keyword.',
        )

        table.add_row(
            '',
            Panel(
                Syntax(
                    f'''
jtype: {exec_name}
with:
    foo: 1
    bar: hello
metas:
    py_modules:
        - executor.py
                ''',
                    'yaml',
                    theme='monokai',
                    line_numbers=True,
                    word_wrap=True,
                ),
                title='config.yml',
                width=50,
                expand=False,
            ),
        )

        if is_dockerfile:
            table.add_row(
                'Dockerfile',
                'The Dockerfile describes how this executor will be built.',
            )

        table.add_row('executor.py', 'The main logic file of the Executor.')
        table.add_row(
            'manifest.yml',
            'Metadata for the Executor, for better appeal on Jina Hub.',
        )

        manifest_fields_table = Table(box=box.SIMPLE)
        manifest_fields_table.add_column('Field', style='cyan', no_wrap=True)
        manifest_fields_table.add_column('Description', no_wrap=True)
        manifest_fields_table.add_row('name',
                                      'Human-readable title of the Executor')
        manifest_fields_table.add_row(
            'description', 'Human-readable description of the Executor')
        manifest_fields_table.add_row(
            'url',
            'URL to find more information on the Executor (e.g. GitHub repo URL)',
        )
        manifest_fields_table.add_row(
            'keywords', 'Keywords that help user find the Executor')

        table.add_row('', manifest_fields_table)
        table.add_row('README.md', 'A usage guide of the Executor.')
        table.add_row('requirements.txt',
                      'The Python dependencies of the Executor.')

        final_table = Table(box=None)

        final_table.add_row(
            'Congrats! You have successfully created an Executor! Here are the next steps:'
        )

        p0 = Panel(
            Syntax(
                f'cd {exec_path}\nls',
                'console',
                theme='monokai',
                line_numbers=True,
                word_wrap=True,
            ),
            title='1. Check out the generated Executor',
            width=120,
            expand=False,
        )

        p1 = Panel(
            table,
            title='2. Understand folder structure',
            width=120,
            expand=False,
        )

        p2 = Panel(
            Syntax(
                f'jina hub push {exec_path}',
                'console',
                theme='monokai',
                line_numbers=True,
                word_wrap=True,
            ),
            title='3. Share it to Jina Hub',
            width=120,
            expand=False,
        )

        final_table.add_row(p0)
        final_table.add_row(p1)
        final_table.add_row(p2)

        p = Panel(
            final_table,
            title=':tada: Next steps',
            width=130,
            expand=False,
        )
        console.print(p)
Пример #6
0
    def pull(self) -> str:
        """Pull the executor package from Jina Hub.

        :return: the `uses` string
        """

        console = get_rich_console()
        cached_zip_file = None
        executor_name = None
        usage_kind = None

        try:
            need_pull = self.args.force_update
            with console.status(f'Pulling {self.args.uri}...') as st:
                scheme, name, tag, secret = parse_hub_uri(self.args.uri)
                image_required = scheme == 'jinahub+docker'

                st.update(f'Fetching [bold]{name}[/bold] from Jina Hub ...')
                executor, from_cache = HubIO.fetch_meta(
                    name,
                    tag,
                    secret=secret,
                    image_required=image_required,
                    force=need_pull,
                )

                presented_id = getattr(executor, 'name', executor.uuid)
                executor_name = (
                    f'{presented_id}' if executor.visibility == 'public' else
                    f'{presented_id}:{secret}') + (f'/{tag}' if tag else '')

                if scheme == 'jinahub+docker':
                    self._load_docker_client()
                    import docker

                    try:
                        self._client.images.get(executor.image_name)
                    except docker.errors.ImageNotFound:
                        need_pull = True

                    if need_pull:
                        st.update(f'Pulling image ...')
                        log_stream = self._raw_client.pull(executor.image_name,
                                                           stream=True,
                                                           decode=True)
                        st.stop()
                        self._pull_with_progress(
                            log_stream,
                            console,
                        )
                    usage_kind = 'docker'
                    return f'docker://{executor.image_name}'
                elif scheme == 'jinahub':
                    import filelock

                    with filelock.FileLock(get_lockfile(), timeout=-1):
                        try:
                            pkg_path, pkg_dist_path = get_dist_path_of_executor(
                                executor)
                            # check commit id to upgrade
                            commit_file_path = (
                                pkg_dist_path /
                                f'PKG-COMMIT-{executor.commit_id or 0}')
                            if (not commit_file_path.exists()) and any(
                                    pkg_dist_path.glob('PKG-COMMIT-*')):
                                raise FileNotFoundError(
                                    f'{pkg_path} need to be upgraded')

                            st.update(
                                'Installing [bold]requirements.txt[/bold]...')
                            install_package_dependencies(
                                install_deps=self.args.install_requirements,
                                pkg_dist_path=pkg_dist_path,
                                pkg_path=pkg_dist_path,
                            )

                        except FileNotFoundError:
                            need_pull = True

                        if need_pull:
                            # pull the latest executor meta, as the cached data would expire
                            if from_cache:
                                executor, _ = HubIO.fetch_meta(
                                    name,
                                    tag,
                                    secret=secret,
                                    image_required=False,
                                    force=True,
                                )

                            st.update(f'Downloading {name} ...')
                            cached_zip_file = download_with_resume(
                                executor.archive_url,
                                get_download_cache_dir(),
                                f'{executor.uuid}-{executor.md5sum}.zip',
                                md5sum=executor.md5sum,
                            )

                            st.update(f'Unpacking {name} ...')
                            install_local(
                                cached_zip_file,
                                executor,
                                install_deps=self.args.install_requirements,
                            )

                            pkg_path, _ = get_dist_path_of_executor(executor)

                        usage_kind = 'source'
                        return f'{pkg_path / "config.yml"}'
                else:
                    raise ValueError(f'{self.args.uri} is not a valid scheme')
        except KeyboardInterrupt:
            executor_name = None
        except Exception:
            executor_name = None
            raise
        finally:
            # delete downloaded zip package if existed
            if cached_zip_file is not None:
                cached_zip_file.unlink()

            if not self.args.no_usage and executor_name:
                self._get_prettyprint_usage(console, executor_name, usage_kind)
Пример #7
0
    def deploy_public_sandbox(args: Union[argparse.Namespace, Dict]) -> str:
        """
        Deploy a public sandbox to Jina Hub.
        :param args: arguments parsed from the CLI

        :return: the host and port of the sandbox
        """
        args_copy = copy.deepcopy(args)
        if not isinstance(args_copy, Dict):
            args_copy = vars(args_copy)

        scheme, name, tag, secret = parse_hub_uri(args_copy.pop('uses', ''))
        payload = {
            'name': name,
            'tag': tag if tag else 'latest',
            'jina': __version__,
            'args': args_copy,
        }

        import requests

        console = get_rich_console()

        host = None
        port = None

        json_response = requests.post(
            url=get_hubble_url_v2() + '/rpc/sandbox.get',
            json=payload,
            headers=get_request_header(),
        ).json()
        if json_response.get('code') == 200:
            host = json_response.get('data', {}).get('host', None)
            port = json_response.get('data', {}).get('port', None)

        if host and port:
            console.log(f"🎉 A sandbox already exists, reusing it.")
            return host, port

        with console.status(
                f"[bold green]🚧 Deploying sandbox for [bold white]{name}[/bold white] since none exists..."
        ):
            try:
                json_response = requests.post(
                    url=get_hubble_url_v2() + '/rpc/sandbox.create',
                    json=payload,
                    headers=get_request_header(),
                ).json()

                data = json_response.get('data') or {}
                host = data.get('host', None)
                port = data.get('port', None)
                if not host or not port:
                    raise Exception(
                        f'Failed to deploy sandbox: {json_response}')

                console.log(f"🎉 Deployment completed, using it.")
            except:
                console.log(
                    "🚨 Deployment failed, feel free to raise an issue. https://github.com/jina-ai/jina/issues/new"
                )
                raise

        return host, port
Пример #8
0
    def push(self) -> None:
        """Push the executor package to Jina Hub."""

        work_path = Path(self.args.path)

        exec_tags = None
        exec_immutable_tags = None
        if self.args.tag:
            exec_tags = ','.join(self.args.tag)
        if self.args.protected_tag:
            exec_immutable_tags = ','.join(self.args.protected_tag)

        dockerfile = None
        if self.args.dockerfile:
            dockerfile = Path(self.args.dockerfile)
            if not dockerfile.exists():
                raise Exception(
                    f'The given Dockerfile `{dockerfile}` does not exist!')
            if dockerfile.parent != work_path:
                raise Exception(
                    f'The Dockerfile must be placed at the given folder `{work_path}`'
                )

            dockerfile = dockerfile.relative_to(work_path)

        console = get_rich_console()
        with console.status(f'Pushing `{self.args.path}` ...') as st:
            req_header = get_request_header()
            try:
                st.update(f'Packaging {self.args.path} ...')
                md5_hash = hashlib.md5()
                bytesio = archive_package(work_path)
                content = bytesio.getvalue()
                md5_hash.update(content)
                md5_digest = md5_hash.hexdigest()

                # upload the archived package
                form_data = {
                    'public':
                    'True' if getattr(self.args, 'public', None) else 'False',
                    'private':
                    'True' if getattr(self.args, 'private', None) else 'False',
                    'md5sum': md5_digest,
                }

                if self.args.verbose:
                    form_data['verbose'] = 'True'

                if self.args.no_cache:
                    form_data['buildWithNoCache'] = 'True'

                if exec_tags:
                    form_data['tags'] = exec_tags

                if exec_immutable_tags:
                    form_data['immutableTags'] = exec_immutable_tags

                if dockerfile:
                    form_data['dockerfile'] = str(dockerfile)

                uuid8, secret = load_secret(work_path)
                if self.args.force_update or uuid8:
                    form_data['id'] = self.args.force_update or uuid8
                if self.args.secret or secret:
                    form_data['secret'] = self.args.secret or secret

                st.update(f'Connecting to Jina Hub ...')
                if form_data.get('id') and form_data.get('secret'):
                    hubble_url = get_hubble_url_v2() + '/rpc/executor.update'
                else:
                    hubble_url = get_hubble_url_v2() + '/rpc/executor.create'

                # upload the archived executor to Jina Hub
                st.update(f'Uploading...')
                resp = upload_file(
                    hubble_url,
                    'filename',
                    content,
                    dict_data=form_data,
                    headers=req_header,
                    stream=True,
                    method='post',
                )

                image = None
                session_id = req_header.get('jinameta-session-id')
                for stream_line in resp.iter_lines():
                    stream_msg = json.loads(stream_line)

                    t = stream_msg.get('type')
                    subject = stream_msg.get('subject')
                    payload = stream_msg.get('payload', '')
                    if t == 'error':
                        msg = stream_msg.get('message')
                        hubble_err = payload
                        overridden_msg = ''
                        detail_msg = ''
                        if isinstance(hubble_err, dict):
                            (overridden_msg,
                             detail_msg) = get_hubble_error_message(hubble_err)
                            if not msg:
                                msg = detail_msg

                        if overridden_msg and overridden_msg != detail_msg:
                            self.logger.warning(overridden_msg)

                        raise Exception(
                            f'{overridden_msg or msg or "Unknown Error"} session_id: {session_id}'
                        )
                    if t == 'progress' and subject == 'buildWorkspace':
                        legacy_message = stream_msg.get('legacyMessage', {})
                        status = legacy_message.get('status', '')
                        st.update(
                            f'Cloud building ... [dim]{subject}: {t} ({status})[/dim]'
                        )
                    elif t == 'complete':
                        image = stream_msg['payload']
                        st.update(
                            f'Cloud building ... [dim]{subject}: {t} ({stream_msg["message"]})[/dim]'
                        )
                        break
                    elif t and subject:
                        if self.args.verbose and t == 'console':
                            console.log(
                                f'Cloud building ... [dim]{subject}: {payload}[/dim]'
                            )
                        else:
                            st.update(
                                f'Cloud building ... [dim]{subject}: {t} {payload}[/dim]'
                            )

                if image:
                    new_uuid8, new_secret = self._prettyprint_result(
                        console, image)
                    if new_uuid8 != uuid8 or new_secret != secret:
                        dump_secret(work_path, new_uuid8, new_secret)
                else:
                    raise Exception(f'Unknown Error, session_id: {session_id}')

            except KeyboardInterrupt:
                pass

            except Exception as e:  # IO related errors
                self.logger.error(
                    f'''Please report this session_id: [yellow bold]{req_header["jinameta-session-id"]}[/] to https://github.com/jina-ai/jina/issues'''
                )
                raise e