Visão geral do projeto

  • Engineering
September 15, 2021
Zhen Chen

Visão geral do projeto

URL do projeto: https://github.com/milvus-io/milvus_cli

Preparação: Python3.8, Click 8.0.x

Agrupar comandos

Criar um comando

import click
from utils import PyOrm

@click.group(no_args_is_help=False, add_help_option=False, invoke_without_command=True) @click.pass_context def cli(ctx): “""Milvus CLI""” ctx.obj = PyOrm() # PyOrm is a util class which wraps the milvus python SDK. You can pass any class instance here. Any command function passed by @click.obj can call it.

if name == 'main': cli()

Conforme o código acima, usamos @click.group() para criar um grupo de comandos cli como ponto de entrada. Para implementar um prompt CLI, precisamos de desativar as mensagens de ajuda para a entrada, por isso adicionamos no_args_is_help=False, add_help_option=False e invoke_without_command=True. E nada será impresso se introduzirmos cli apenas no terminal.

Além disso, utilizamos @click.pass_context para passar um contexto a este grupo para utilização posterior.

Criar um subcomando do grupo de comandos

De seguida, adicionamos o primeiro subcomando help em cli:

# Print the help message of specified command.
def print_help_msg(command):
    with click.Context(command) as ctx:
        click.echo(command.get_help(ctx))

# Use @cli.command() to create a sub command of cli. @cli.command() def help(): “""Show help messages.""” # Print help message of cli. click.echo(print_help_msg(cli))

Agora podemos utilizar cli help no terminal:

$ python milvus_cli/scripts/milvus_cli.py help

Criar um subgrupo de um grupo de comandos

Não só queremos ter um subcomando como cli help, mas também precisamos de um subgrupo de comandos como cli list collection, cli list partition e cli list indexes.

Em primeiro lugar, criamos um subgrupo de comandos list. Neste caso, podemos passar o primeiro parâmetro para @cli.group como nome do comando em vez de utilizar o nome da função predefinida, de modo a reduzir a duplicação de nomes de funções.

Atenção, aqui utilizamos @cli.group() em vez de @click.group para criar um subgrupo do grupo de origem.

Utilizamos @click.pass_obj para passar context.obj para os subcomandos deste subgrupo.

@cli.group('list', no_args_is_help=False)
@click.pass_obj
def listDetails(obj):
    """List collections, partitions and indexes."""
    pass

Depois, adicionamos alguns subcomandos a este subgrupo através de @listDetails.command() (e não @cli.command()). Este é apenas um exemplo, pode ignorar o implemento e discuti-lo-emos mais tarde.

@listDetails.command()
@click.option('--timeout', 'timeout', help="[Optional] - An optional duration of time in seconds to allow for the RPC. When timeout is set to None, client waits until server response or error occur.", default=None)
@click.option('--show-loaded', 'showLoaded', help="[Optional] - Only show loaded collections.", default=False)
@click.pass_obj
def collections(obj, timeout, showLoaded):
    """List all collections."""
    try:
        obj.checkConnection()
        click.echo(obj.listCollections(timeout, showLoaded))
    except Exception as e:
        click.echo(message=e, err=True)

@listDetails.command() @click.option('-c’, ‘–collection’, ‘collection’, help=‘The name of collection.’, default=‘’) @click.pass_obj def partitions(obj, collection): “""List all partitions of the specified collection.""” try: obj.checkConnection() validateParamsByCustomFunc( obj.getTargetCollection, ‘Collection Name Error!’, collection) click.echo(obj.listPartitions(collection)) except Exception as e: click.echo(message=e, err=True)

Depois de tudo isto concluído, temos um miltigroup de comandos com o seguinte aspeto

image imagem

Personalizar um comando

Adicionar opções

É possível adicionar algumas opções a um comando que será utilizado como cli --test-option value.

Eis um exemplo: adicionamos três opções alias, host e port para especificar um endereço para ligação ao Milvus.

Os dois primeiros parâmetros definem o nome curto e completo da opção, o terceiro parâmetro define o nome da variável, o parâmetro help especifica a mensagem curta de ajuda, o parâmetro default especifica o valor predefinido e o parâmetro type especifica o tipo de valor.

E todos os valores das opções serão passados para a função por ordem de definição.

@cli.command(no_args_is_help=False)
@click.option('-a', '--alias', 'alias', help="Milvus link alias name, default is `default`.", default='default', type=str)
@click.option('-h', '--host', 'host', help="Host name, default is `127.0.0.1`.", default='127.0.0.1', type=str)
@click.option('-p', '--port', 'port', help="Port, default is `19530`.", default=19530, type=int)
@click.pass_obj
def connect(obj, alias, host, port):
    pass

Adicionar opções de sinalização

Utilizámos as opções acima para passar um valor, mas, por vezes, só precisamos de um sinalizador como um valor booleano.

Como no exemplo abaixo, a opção autoId é uma opção de sinalização e não passa quaisquer dados para a função, pelo que podemos utilizá-la como cli create collection -c c_name -p p_name -a.

@createDetails.command('collection')
@click.option('-c', '--collection-name', 'collectionName', help='Collection name to be created.', default='')
@click.option('-p', '--schema-primary-field', 'primaryField', help='Primary field name.', default='')
@click.option('-a', '--schema-auto-id', 'autoId', help='Enable auto id.', default=False, is_flag=True)
@click.pass_obj
def createCollection(obj, collectionName, primaryField, autoId, description, fields):
    pass

Adicionar argumentos

Neste projeto, substituímos a utilização de todos os argumentos pela utilização de opções. Mas ainda introduzimos o uso de argumentos aqui. Diferente das opções, os argumentos são usados como cli COMMAND [OPTIONS] ARGUEMENTS. Se convertermos o exemplo acima na utilização de argumentos, será assim:

@createDetails.command('collection')
@click.argument('collectionName')
@click.option('-p', '--schema-primary-field', 'primaryField', help='Primary field name.', default='')
@click.option('-a', '--schema-auto-id', 'autoId', help='Enable auto id.', default=False, is_flag=True)
@click.pass_obj
def createCollection(obj, collectionName, primaryField, autoId, description, fields):
    pass

Então a utilização deve ser cli create collection c_name -p p_name -a.

Adicionar mensagem de ajuda completa

Tal como definimos a mensagem de ajuda curta acima, podemos definir a mensagem de ajuda completa na função:

@cli.command(no_args_is_help=False)
@click.option('-a', '--alias', 'alias', help="Milvus link alias name, default is `default`.", default='default', type=str)
@click.option('-h', '--host', 'host', help="Host name, default is `127.0.0.1`.", default='127.0.0.1', type=str)
@click.option('-p', '--port', 'port', help="Port, default is `19530`.", default=19530, type=int)
@click.pass_obj
def connect(obj, alias, host, port):
    """
    Connect to Milvus.
Example:

    milvus_cli > connect -h 127.0.0.1 -p 19530 -a default
&quot;&quot;&quot;</span>
<span class="hljs-keyword">try</span>:
    obj.connect(alias, host, port)
<span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
    click.echo(message=e, err=<span class="hljs-literal">True</span>)
<span class="hljs-keyword">else</span>:
    click.echo(<span class="hljs-string">&quot;Connect Milvus successfully!&quot;</span>)
    click.echo(obj.showConnection(alias))

O primeiro bloco dentro da função é a mensagem de ajuda que será impressa depois de introduzirmos cli connect --help.

milvus_cli > connect --help
Usage: milvus_cli.py connect [OPTIONS]

Connect to Milvus.

Example:

  milvus_cli &gt; connect -h <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span> -p <span class="hljs-number">19530</span> -a <span class="hljs-literal">default</span>

Options: -a, –alias TEXT Milvus link alias name, default is <span class="hljs-literal">default</span>. -h, --host TEXT Host name, default is <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>. -p, --port INTEGER Port, default is <span class="hljs-number">19530</span>. –help Show this message and exit.

Adicionar confirmação

Por vezes, precisamos que o utilizador confirme uma ação, especialmente a eliminação de algo. Podemos adicionar click.confirm para fazer uma pausa e pedir ao utilizador que confirme:

@deleteSth.command('collection')
@click.option('-c', '--collection', 'collectionName', help='The name of collection to be deleted.', default='')
@click.option('-t', '--timeout', 'timeout', help='An optional duration of time in seconds to allow for the RPC. If timeout is set to None, the client keeps waiting until the server responds or an error occurs.', default=None, type=int)
@click.pass_obj
def deleteCollection(obj, collectionName, timeout):
    """
    Drops the collection together with its index files.
Example:

    milvus_cli &gt; delete collection -c car
&quot;&quot;&quot;</span>
click.echo(
    <span class="hljs-string">&quot;Warning!\nYou are trying to delete the collection with data. This action cannot be undone!\n&quot;</span>)
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> click.confirm(<span class="hljs-string">&#x27;Do you want to continue?&#x27;</span>):
    <span class="hljs-keyword">return</span>
<span class="hljs-keyword">pass</span>

Como no exemplo acima, uma conversa de confirmação será apresentada como Aborted!ant to continue? [y/N]:.

Adicionar prompts

Para implementar avisos, só precisamos de adicionar click.prompt.

@cli.command()
@click.pass_obj
def query(obj):
    """
    Query with a set of criteria, and results in a list of records that match the query exactly.
    """
    collectionName = click.prompt(
        'Collection name', type=click.Choice(obj._list_collection_names()))
    expr = click.prompt('The query expression(field_name in [x,y])')
    partitionNames = click.prompt(
        f'The names of partitions to search(split by "," if multiple) {obj._list_partition_names(collectionName)}', default='')
    outputFields = click.prompt(
        f'Fields to return(split by "," if multiple) {obj._list_field_names(collectionName)}', default='')
    timeout = click.prompt('timeout', default='')
    pass

O aviso será apresentado quando cada click.prompt. Utilizamos alguns prompts em série para que pareça uma conversa contínua. Isto garante que o utilizador introduzirá os dados pela ordem pretendida. Neste caso, é necessário que o utilizador escolha primeiro uma coleção e que obtenha todas as partições desta coleção e, em seguida, mostre-as ao utilizador para que este as escolha.

Adicionar escolhas

Por vezes, se o utilizador quiser introduzir apenas um intervalo/tipo de valor limitado, pode adicionar type=click.Choice([<any>]) a click.prompt, click.options e etc...

Por exemplo,

collectionName = click.prompt(
        'Collection name', type=click.Choice(['collection_1', 'collection_2']))

Assim, o utilizador só pode introduzir collection_1 ou collection_2. Se introduzir outros valores, surgirá um erro.

Adicionar ecrã limpo

Pode utilizar click.clear() para o implementar.

@cli.command()
def clear():
    """Clear screen."""
    click.clear()

Sugestões adicionais

  • O valor predefinido é None, pelo que não faz sentido se especificar o valor predefinido como None. E o valor predefinido None fará com que click.prompt apareça continuamente se quiser deixar um valor vazio para o ultrapassar.

Implementar um prompt CLI para o utilizador introduzir dados

Porquê um prompt CLI

Para o funcionamento da base de dados, precisamos de uma ligação contínua a uma instância. Se utilizarmos o modo de linha de comandos de origem, a ligação será interrompida após cada comando executado. Também queremos armazenar alguns dados quando usamos o CLI, e limpá-los depois de sair.

Implementar

  1. Use while True para ouvir continuamente a entrada do utilizador.
def runCliPrompt():
    while True:
        astr = input('milvus_cli > ')
        try:
            cli(astr.split())
        except SystemExit:
            # trap argparse error message
            # print('error', SystemExit)
            continue

if name == 'main': runCliPrompt()

  1. Usar apenas input fará com que as teclas de seta up, down, left, right, a tecla tab e algumas outras teclas sejam convertidas em string Acsii automaticamente. Além disso, os comandos do histórico não podem ser lidos da sessão. Portanto, adicionamos readline à função runCliPrompt.
def runCliPrompt():
    while True:
            import readline
        readline.set_completer_delims(' \t\n;')
        astr = input('milvus_cli > ')
        try:
            cli(astr.split())
        except SystemExit:
            # trap argparse error message
            # print('error', SystemExit)
            continue
  1. Adicionar quit CLI.
@cli.command('exit')
def quitapp():
    """Exit the CLI."""
    global quitapp
    quitapp = True

quitapp = False # global flag

def runCliPrompt(): while not quitapp: import readline readline.set_completer_delims(' \t\n;’) astr = input('milvus_cli > ') try: cli(astr.split()) except SystemExit: # trap argparse error message # print('error’, SystemExit) continue

  1. Pegar o erro KeyboardInterrupt quando usar ctrl C para sair.
def runCliPrompt():
    try:
        while not quitapp:
            import readline
            readline.set_completer_delims(' \t\n;')
            astr = input('milvus_cli > ')
            try:
                cli(astr.split())
            except SystemExit:
                # trap argparse error message
                # print('error', SystemExit)
                continue
    except KeyboardInterrupt:
        sys.exit(0)
  1. Depois de tudo resolvido, a CLI agora se parece com:
milvus_cli >
milvus_cli > connect
+-------+-----------+
| Host  | 127.0.0.1 |
| Port  |   19530   |
| Alias |  default  |
+-------+-----------+

milvus_cli > help Usage: [OPTIONS] COMMAND [ARGS]…

Milvus CLI

Commands: clear Clear screen. connect Connect to Milvus. create Create collection, partition and index. delete Delete specified collection, partition and index. describe Describe collection or partition. exit Exit the CLI. help Show help messages. import Import data from csv file with headers and insert into target… list List collections, partitions and indexes. load Load specified collection. query Query with a set of criteria, and results in a list of… release Release specified collection. search Conducts a vector similarity search with an optional boolean… show Show connection, loading_progress and index_progress. version Get Milvus CLI version.

milvus_cli > exit

Implementar manualmente o autocomplete

Diferente do autocomplete do shell do click, nosso projeto envolve a linha de comando e usa um loop para obter a entrada do usuário para implementar uma linha de comando rápida. Portanto, precisamos vincular um complemento a readline.

class Completer(object):
    RE_SPACE = re.compile('.*\s+$', re.M)
    CMDS_DICT = {
        'clear': [],
        'connect': [],
        'create': ['collection', 'partition', 'index'],
        'delete': ['collection', 'partition', 'index'],
        'describe': ['collection', 'partition'],
        'exit': [],
        'help': [],
        'import': [],
        'list': ['collections', 'partitions', 'indexes'],
        'load': [],
        'query': [],
        'release': [],
        'search': [],
        'show': ['connection', 'index_progress', 'loading_progress'],
        'version': [],
    }
<span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-literal">None</span>:
    <span class="hljs-built_in">super</span>().__init__()
    <span class="hljs-variable language_">self</span>.COMMANDS = <span class="hljs-built_in">list</span>(<span class="hljs-variable language_">self</span>.CMDS_DICT.keys())
    <span class="hljs-variable language_">self</span>.createCompleteFuncs(<span class="hljs-variable language_">self</span>.CMDS_DICT)

<span class="hljs-keyword">def</span> <span class="hljs-title function_">createCompleteFuncs</span>(<span class="hljs-params">self, cmdDict</span>):
    <span class="hljs-keyword">for</span> cmd <span class="hljs-keyword">in</span> cmdDict:
        sub_cmds = cmdDict[cmd]
        complete_example = <span class="hljs-variable language_">self</span>.makeComplete(cmd, sub_cmds)
        <span class="hljs-built_in">setattr</span>(<span class="hljs-variable language_">self</span>, <span class="hljs-string">&#x27;complete_%s&#x27;</span> % cmd, complete_example)

<span class="hljs-keyword">def</span> <span class="hljs-title function_">makeComplete</span>(<span class="hljs-params">self, cmd, sub_cmds</span>):
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">f_complete</span>(<span class="hljs-params">args</span>):
        <span class="hljs-string">f&quot;Completions for the <span class="hljs-subst">{cmd}</span> command.&quot;</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> args:
            <span class="hljs-keyword">return</span> <span class="hljs-variable language_">self</span>._complete_path(<span class="hljs-string">&#x27;.&#x27;</span>)
        <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(args) &lt;= <span class="hljs-number">1</span> <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> cmd == <span class="hljs-string">&#x27;import&#x27;</span>:
            <span class="hljs-keyword">return</span> <span class="hljs-variable language_">self</span>._complete_2nd_level(sub_cmds, args[-<span class="hljs-number">1</span>])
        <span class="hljs-keyword">return</span> <span class="hljs-variable language_">self</span>._complete_path(args[-<span class="hljs-number">1</span>])
    <span class="hljs-keyword">return</span> f_complete

<span class="hljs-keyword">def</span> <span class="hljs-title function_">_listdir</span>(<span class="hljs-params">self, root</span>):
    <span class="hljs-string">&quot;List directory &#x27;root&#x27; appending the path separator to subdirs.&quot;</span>
    res = []
    <span class="hljs-keyword">for</span> name <span class="hljs-keyword">in</span> os.listdir(root):
        path = os.path.join(root, name)
        <span class="hljs-keyword">if</span> os.path.isdir(path):
            name += os.sep
        res.append(name)
    <span class="hljs-keyword">return</span> res

<span class="hljs-keyword">def</span> <span class="hljs-title function_">_complete_path</span>(<span class="hljs-params">self, path=<span class="hljs-literal">None</span></span>):
    <span class="hljs-string">&quot;Perform completion of filesystem path.&quot;</span>
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> path:
        <span class="hljs-keyword">return</span> <span class="hljs-variable language_">self</span>._listdir(<span class="hljs-string">&#x27;.&#x27;</span>)
    dirname, rest = os.path.split(path)
    tmp = dirname <span class="hljs-keyword">if</span> dirname <span class="hljs-keyword">else</span> <span class="hljs-string">&#x27;.&#x27;</span>
    res = [os.path.join(dirname, p)
           <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> <span class="hljs-variable language_">self</span>._listdir(tmp) <span class="hljs-keyword">if</span> p.startswith(rest)]
    <span class="hljs-comment"># more than one match, or single match which does not exist (typo)</span>
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(res) &gt; <span class="hljs-number">1</span> <span class="hljs-keyword">or</span> <span class="hljs-keyword">not</span> os.path.exists(path):
        <span class="hljs-keyword">return</span> res
    <span class="hljs-comment"># resolved to a single directory, so return list of files below it</span>
    <span class="hljs-keyword">if</span> os.path.isdir(path):
        <span class="hljs-keyword">return</span> [os.path.join(path, p) <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> <span class="hljs-variable language_">self</span>._listdir(path)]
    <span class="hljs-comment"># exact file match terminates this completion</span>
    <span class="hljs-keyword">return</span> [path + <span class="hljs-string">&#x27; &#x27;</span>]

<span class="hljs-keyword">def</span> <span class="hljs-title function_">_complete_2nd_level</span>(<span class="hljs-params">self, SUB_COMMANDS=[], cmd=<span class="hljs-literal">None</span></span>):
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> cmd:
        <span class="hljs-keyword">return</span> [c + <span class="hljs-string">&#x27; &#x27;</span> <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> SUB_COMMANDS]
    res = [c <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> SUB_COMMANDS <span class="hljs-keyword">if</span> c.startswith(cmd)]
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(res) &gt; <span class="hljs-number">1</span> <span class="hljs-keyword">or</span> <span class="hljs-keyword">not</span> (cmd <span class="hljs-keyword">in</span> SUB_COMMANDS):
        <span class="hljs-keyword">return</span> res
    <span class="hljs-keyword">return</span> [cmd + <span class="hljs-string">&#x27; &#x27;</span>]

<span class="hljs-keyword">def</span> <span class="hljs-title function_">complete</span>(<span class="hljs-params">self, text, state</span>):
    <span class="hljs-string">&quot;Generic readline completion entry point.&quot;</span>
    buffer = readline.get_line_buffer()
    line = readline.get_line_buffer().split()
    <span class="hljs-comment"># show all commands</span>
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> line:
        <span class="hljs-keyword">return</span> [c + <span class="hljs-string">&#x27; &#x27;</span> <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> <span class="hljs-variable language_">self</span>.COMMANDS][state]
    <span class="hljs-comment"># account for last argument ending in a space</span>
    <span class="hljs-keyword">if</span> <span class="hljs-variable language_">self</span>.RE_SPACE.<span class="hljs-keyword">match</span>(buffer):
        line.append(<span class="hljs-string">&#x27;&#x27;</span>)
    <span class="hljs-comment"># resolve command to the implementation function</span>
    cmd = line[<span class="hljs-number">0</span>].strip()
    <span class="hljs-keyword">if</span> cmd <span class="hljs-keyword">in</span> <span class="hljs-variable language_">self</span>.COMMANDS:
        impl = <span class="hljs-built_in">getattr</span>(<span class="hljs-variable language_">self</span>, <span class="hljs-string">&#x27;complete_%s&#x27;</span> % cmd)
        args = line[<span class="hljs-number">1</span>:]
        <span class="hljs-keyword">if</span> args:
            <span class="hljs-keyword">return</span> (impl(args) + [<span class="hljs-literal">None</span>])[state]
        <span class="hljs-keyword">return</span> [cmd + <span class="hljs-string">&#x27; &#x27;</span>][state]
    results = [
        c + <span class="hljs-string">&#x27; &#x27;</span> <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> <span class="hljs-variable language_">self</span>.COMMANDS <span class="hljs-keyword">if</span> c.startswith(cmd)] + [<span class="hljs-literal">None</span>]
    <span class="hljs-keyword">return</span> results[state]

Depois de definir Completer, podemos associá-lo ao readline:

comp = Completer()

def runCliPrompt(): try: while not quitapp: import readline readline.set_completer_delims(' \t\n;’) readline.parse_and_bind(“tab: complete”) readline.set_completer(comp.complete) astr = input('milvus_cli > ') try: cli(astr.split()) except SystemExit: # trap argparse error message # print('error’, SystemExit) continue except KeyboardInterrupt: sys.exit(0)

Adicionar opção única

Para a linha de comando de prompt, às vezes não queremos executar completamente os scripts para obter algumas informações, como a versão. Um bom exemplo é Python, quando digitamos python no terminal, a linha de comando promtp será exibida, mas só retorna uma mensagem de versão e não entrará nos scripts do prompt se digitarmos python -V. Assim, podemos utilizar sys.args no nosso código para implementar.

def runCliPrompt():
    args = sys.argv
    if args and (args[-1] == '--version'):
        print(f"Milvus Cli v{getPackageVersion()}")
        return
    try:
        while not quitapp:
            import readline
            readline.set_completer_delims(' \t\n;')
            readline.parse_and_bind("tab: complete")
            readline.set_completer(comp.complete)
            astr = input('milvus_cli > ')
            try:
                cli(astr.split())
            except SystemExit:
                # trap argparse error message
                # print('error', SystemExit)
                continue
    except KeyboardInterrupt:
        sys.exit(0)

if name == 'main': runCliPrompt()

Obtemos sys.args antes do loop quando corremos pela primeira vez para os scripts CLI. Se os últimos argumentos forem --version, o código devolverá a versão do pacote sem entrar no ciclo.

Será útil depois de construirmos os códigos como um pacote. O utilizador pode escrever milvus_cli para saltar para um prompt CLI, ou escrever milvus_cli --version para obter apenas a versão.

Construir e lançar

Finalmente queremos construir um pacote e liberar pelo PYPI. Assim, o usuário pode simplesmente usar pip install <package name> para instalar.

Instalar localmente para teste

Antes de publicar o pacote no PYPI, pode-se querer instalá-lo localmente para alguns testes.

Neste caso, pode simplesmente cd no diretório do pacote e correr pip install -e . (Não esquecer .).

Criar ficheiros de pacote

Consulte: https://packaging.python.org/tutorials/packaging-projects/

A estrutura de um pacote deve ser semelhante:

package_example/
├── LICENSE
├── README.md
├── setup.py
├── src/
│   ├── __init__.py
│   ├── main.py
│   └── scripts/
│       ├── __init__.py
│       └── example.py
└── tests/

Criar o diretório do pacote

Crie o diretório Milvus_cli com a estrutura abaixo:

Milvus_cli/
├── LICENSE
├── README.md
├── setup.py
├── milvus_cli/
│   ├── __init__.py
│   ├── main.py
│   ├── utils.py
│   └── scripts/
│       ├── __init__.py
│       └── milvus_cli.py
└── dist/

Escrever o código de entrada

A entrada do script deve estar em Milvus_cli/milvus_cli/scripts, e o Milvus_cli/milvus_cli/scripts/milvus_cli.py deve ser como:

import sys
import os
import click
from utils import PyOrm, Completer

pass_context = click.make_pass_decorator(PyOrm, ensure=True)

@click.group(no_args_is_help=False, add_help_option=False, invoke_without_command=True) @click.pass_context def cli(ctx): “""Milvus CLI""” ctx.obj = PyOrm()

“"” … Here your code. … “"”

@cli.command(‘exit’) def quitapp(): “""Exit the CLI.""” global quitapp quitapp = True

quitapp = False # global flag comp = Completer()

def runCliPrompt(): args = sys.argv if args and (args[-1] == ‘–version’): print(f"Milvus Cli v{getPackageVersion()}") return try: while not quitapp: import readline readline.set_completer_delims(' \t\n;’) readline.parse_and_bind(“tab: complete”) readline.set_completer(comp.complete) astr = input('milvus_cli > ') try: cli(astr.split()) except SystemExit: # trap argparse error message # print('error’, SystemExit) continue except Exception as e: click.echo( message=f"Error occurred!\n{str(e)}", err=True) except KeyboardInterrupt: sys.exit(0)

if name == 'main': runCliPrompt()

Editar o diretório setup.py

from setuptools import setup, find_packages

with open(“README.md”, “r”, encoding=“utf-8”) as fh: long_description = fh.read()

setup( name=‘milvus_cli’, version=‘0.1.6’, author=‘Milvus Team’, author_email=‘milvus-team@zilliz.com’, url=‘https://github.com/milvus-io/milvus_cli’, description=‘CLI for Milvus’, long_description=long_description, long_description_content_type=‘text/markdown’, license=‘Apache-2.0’, packages=find_packages(), include_package_data=True, install_requires=[ ‘Click==8.0.1’, ‘pymilvus==2.0.0rc5’, ‘tabulate==0.8.9’ ], entry_points={ ‘console_scripts’: [ ‘milvus_cli = milvus_cli.scripts.milvus_cli:runCliPrompt’, ], }, python_requires=‘>=3.8’ )

Algumas dicas aqui:

  1. Usamos o conteúdo de README.md como a descrição longa do pacote.
  2. Adicione todas as dependências a install_requires.
  3. Especifique o entry_points. Neste caso, definimos milvus_cli como um filho de console_scripts, para que possamos digitar milvus_cli como um comando diretamente após instalarmos este pacote. E o ponto de entrada de milvus_cli é a função runCliPrompt em milvus_cli/scripts/milvus_cli.py.

Construir

  1. Actualize o pacote build: python3 -m pip install --upgrade build

  2. Execute build: python -m build --sdist --wheel --outdir dist/ .

  3. Serão gerados dois ficheiros no diretório dist/:

dist/
  example_package_YOUR_USERNAME_HERE-0.0.1-py3-none-any.whl
  example_package_YOUR_USERNAME_HERE-0.0.1.tar.gz

Publicar versão

Consulte: https://packaging.python.org/tutorials/packaging-projects/#uploading-the-distribution-archives

  1. Atualizar o pacote twine: python3 -m pip install --upgrade twine
  2. Upload para PYPI test env: python3 -m twine upload --repository testpypi dist/*
  3. Fazer upload para PYPI: python3 -m twine upload dist/*

CI/CD por fluxos de trabalho do Github

Consultar: https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/

Queremos uma maneira de fazer upload de ativos automaticamente, ele pode construir os pacotes e enviá-los para lançamentos do github e PYPI.

(Por alguma razão, queremos apenas que o fluxo de trabalho publique o lançamento para testar o PYPI).

# This is a basic workflow to help you get started with Actions

name: Update the release's assets after it published

on: release: # The workflow will run after release published types: [published]

jobs:

build: # The type of runner that the job will run on runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
  # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
  - uses: actions/checkout@v2
  - uses: actions/setup-python@v2
    with:
      python-version: &#x27;</span>3.8<span class="hljs-string">&#x27;
      architecture: &#x27;</span>x64<span class="hljs-string">&#x27;
  - name: Install pypa/build
    run: &gt;-
      python -m
      pip install
      build
      --user
  - name: Clean dist/
    run: |
      sudo rm -fr dist/*
  - name: Build a binary wheel and a source tarball
    run: &gt;-
      python -m
      build
      --sdist
      --wheel
      --outdir dist/
      .
  # Update target github release&#x27;</span>s assets
  - name: Update assets
    uses: softprops/action-gh-release@v1
    <span class="hljs-keyword">if</span>: startsWith(github.ref, <span class="hljs-string">&#x27;refs/tags/&#x27;</span>)
    with:
      files: ./dist/*
  - name: Publish distribution 📦 to Test PyPI
    <span class="hljs-keyword">if</span>: contains(github.ref, <span class="hljs-string">&#x27;beta&#x27;</span>) &amp;&amp; startsWith(github.ref, <span class="hljs-string">&#x27;refs/tags&#x27;</span>)
    uses: pypa/gh-action-pypi-publish@release/v1
    with:
      user: __token__
      password: <span class="hljs-variable">${{ secrets.TEST_PYPI_API_TOKEN }</span>}
      repository_url: https://test.pypi.org/legacy/
      packages_dir: dist/
      verify_metadata: <span class="hljs-literal">false</span>

Saiba mais sobre o Milvus

Milvus é uma ferramenta poderosa capaz de alimentar uma vasta gama de aplicações de inteligência artificial e pesquisa de similaridade vetorial. Para saber mais sobre o projeto, consulte os seguintes recursos:

  • Leia o nosso blogue.
  • Interagir com a nossa comunidade de código aberto no Slack.
  • Utilize ou contribua para a base de dados de vectores mais popular do mundo no GitHub.
  • Teste e implemente rapidamente aplicações de IA com o nosso novo bootcamp.

    Try Managed Milvus for Free

    Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

    Get Started

    Like the article? Spread the word

    Continue Lendo