Visão geral do projeto
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
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
"""</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">"Connect Milvus successfully!"</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 > 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 > delete collection -c car
"""</span>
click.echo(
<span class="hljs-string">"Warning!\nYou are trying to delete the collection with data. This action cannot be undone!\n"</span>)
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> click.confirm(<span class="hljs-string">'Do you want to continue?'</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 comoNone. E o valor predefinidoNonefará com queclick.promptapareç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
- Use
while Truepara 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()
- Usar apenas
inputfará com que as teclas de setaup,down,left,right, a teclatabe 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, adicionamosreadlineà funçãorunCliPrompt.
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
- Adicionar
quitCLI.
@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
- Pegar o erro
KeyboardInterruptquando usarctrl Cpara 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)
- 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>) -> <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">'complete_%s'</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"Completions for the <span class="hljs-subst">{cmd}</span> command."</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">'.'</span>)
<span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(args) <= <span class="hljs-number">1</span> <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> cmd == <span class="hljs-string">'import'</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">"List directory 'root' appending the path separator to subdirs."</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">"Perform completion of filesystem path."</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">'.'</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">'.'</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) > <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">' '</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">' '</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) > <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">' '</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">"Generic readline completion entry point."</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">' '</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">''</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">'complete_%s'</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">' '</span>][state]
results = [
c + <span class="hljs-string">' '</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:
- Usamos o conteúdo de
README.mdcomo a descrição longa do pacote. - Adicione todas as dependências a
install_requires. - Especifique o
entry_points. Neste caso, definimosmilvus_clicomo um filho deconsole_scripts, para que possamos digitarmilvus_clicomo um comando diretamente após instalarmos este pacote. E o ponto de entrada demilvus_clié a funçãorunCliPromptemmilvus_cli/scripts/milvus_cli.py.
Construir
Actualize o pacote
build:python3 -m pip install --upgrade buildExecute build:
python -m build --sdist --wheel --outdir dist/ .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
- Atualizar o pacote
twine:python3 -m pip install --upgrade twine - Upload para
PYPItest env:python3 -m twine upload --repository testpypi dist/* - 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: '</span>3.8<span class="hljs-string">'
architecture: '</span>x64<span class="hljs-string">'
- name: Install pypa/build
run: >-
python -m
pip install
build
--user
- name: Clean dist/
run: |
sudo rm -fr dist/*
- name: Build a binary wheel and a source tarball
run: >-
python -m
build
--sdist
--wheel
--outdir dist/
.
# Update target github release'</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">'refs/tags/'</span>)
with:
files: ./dist/*
- name: Publish distribution 📦 to Test PyPI
<span class="hljs-keyword">if</span>: contains(github.ref, <span class="hljs-string">'beta'</span>) && startsWith(github.ref, <span class="hljs-string">'refs/tags'</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:
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word



