Requests: Gerar postagens multipartes sem um arquivo

Criado em 3 jan. 2013  ·  36Comentários  ·  Fonte: psf/requests

Atualmente, a única maneira de ter uma solicitação de formulário de várias partes é r = requests.post(url, data=payload, files=files)
que pode ter um componente

Content-Disposition: form-data; name="file"; filename="filename.txt"
Content-Type: text/plain

content
--3eeaadbfda0441b8be821bbed2962e4d--

No entanto, encontro casos em que as postagens precisam estar em um formato multiparte sem um arquivo associado, como:

Content-Disposition: form-data; name="key1"

value1
--3eeaadbfda0441b8be821bbed2962e4d

mas o último é impossível de gerar sem o primeiro.

Talvez possamos adicionar um sinalizador como r = requests.post(url, data=payload, multipart=True) que força uma postagem a ser multiparte, mesmo sem um arquivo.

Estou feliz em trabalhar na implementação disso, se parecer uma boa ideia.

Todos 36 comentários

Isto foi discutido antes. Isso representaria uma mudança significativa na API que não tenho certeza se @kennethreitz gostaria.

Pessoalmente, eu seria mais a favor de expor uma função para gerar dados multipartes de dicionários (listas de tuplas, etc) na API para que os usuários possam usar isso e apenas passar os dados gerados para as solicitações. Ostensivamente, se eles não estiverem usando arquivos, não deve haver um grande acerto de memória, mas mesmo assim, eles já têm uma string enorme na memória, a segunda não vai matá-los realmente e seria culpa deles, não nossa .

Talvez @kennethreitz fosse mais receptivo à segunda solução. Eu não acho que isso se encaixa na filosofia de design das solicitações e seria extraordinariamente bizarro dado o resto da API, mas _shrug_ quem sabe.

Na verdade, a API atual não suporta o envio de dados multipartes além de arquivos e isso é uma coisa ruim. Consulte o problema nº 935 e shazow / urllib3 / issues / 120

Talvez eu esteja faltando alguma coisa, mas as mudanças parecem menores para mim. Eu fiz um fork do repositório e tenho uma proposta de alteração em minha versão aqui: https://github.com/spacecase/requests/commit/45b0b3ce1e76b241b323570a5fc88ae2089c3c3d
(se houver uma maneira melhor de fazer isso, me avise? Sou bastante novo no github).

Pode ser necessário alguns testes de unidade e uma alteração em uma docstring em api.py, mas algo assim é razoável?

O problema com a mudança não é que seja complicado de implementar, é que é uma divergência na API. Estou meio em risco nesta discussão em andamento: acho que provavelmente seria útil ter uma boa maneira de fazer upload de dados de formulário com várias partes, mas também acho que a API files atual é muito boa . Eu _não_ acho que adicionar multipart à API Request seja o caminho a percorrer.

Honestamente, eu pessoalmente prefiro controlá-lo por meio do cabeçalho do tipo de conteúdo, mas isso provavelmente seria 100 vezes mais sujeito a erros e confuso para novos usuários do que o que fazemos atualmente. Eu também estou em cima do muro sobre isso, mas ainda assim erraria por não fazer isso.

Porque? Se aceitarmos essa solicitação de recurso, será mais provável que alguém reclame por não ter um parâmetro json. E tenho certeza de que outras pessoas poderiam criar ainda mais parâmetros que adorariam ver adicionados. Como está agora, a API faz exatamente o que deveria e tem pouco ou nenhum kruft. Uma maneira de ver isso é o KISS. Isso torna as solicitações e retorna um grande objeto que torna o uso da resposta fácil e natural. Ele faz o que é anunciado e você faz o resto. Ele anuncia codificação multipartes e o faz por meio de seu design documentado. Pode parecer estranho, mas está documentado e funciona.

_ (...) alguém vai reclamar por não ter um parâmetro json_

Não haveria base para tal reclamação porque json é um subtipo de application tipo de mídia ( rfc4627 ) e não multipart tipo de mídia ( httpbis rascunho 21 )

_Pode parecer estranho (...) _

É estranho, mas não deveria ser.

Depois de ler os comentários anteriores, gostaria de reiterar minha posição: multipart/form-data é o tipo MIME multiparte mais comum usado atualmente (carece de fontes) e não suportá-lo é uma omissão grosseira.

@ piotr-dobrogost: Apesar do que eu disse acima, não acho o argumento que você fez nem um pouco convincente.

As solicitações não oferecem suporte a tipos MIME, mas a casos de uso. Esse ponto de vista faz com que seus comentários acima pareçam estranhos. Por exemplo, a reclamação sobre não ter um parâmetro JSON será porque o upload de dados formatados em JSON é muito comum - provavelmente mais comum entre os usuários de Requests do que o upload de dados multiparte sem arquivo. Argumentar que não o forneceremos porque 'só temos casos especiais de subtipos de multipart ' parece uma coisa bizarra de se dizer.

Independentemente disso, o cerne do problema é este: a API é o ponto principal desta biblioteca. Se você não conseguir encontrar uma forma _bela_ de implementar essa funcionalidade, isso não acontecerá.

Não haveria motivo para tal reclamação [sic]

@ piotr-dobrogost você tem mais esperança para o futuro dos desenvolvedores do que eu. Além disso, o parâmetro solicitado é inexato. Ele precisaria ser denominado algo mais parecido com form_data que multipart . Como você observou, (embora indiretamente) multipart pode se referir a muitos tipos de mídia diferentes.

É estranho, mas não deveria ser.

Nem todas as coisas podem ser elegantes (mesmo em python).

e não apoiando isso

Mas é suportado. Mas tirando isso, temos data para application/x-www-form-urlencoded , files (ou files+data ) para multipart/form-data , por que precisamos de outro parâmetro por apenas multipart/form-data quando o tivermos?

Você não precisa usar uma combinação de data e files para obter o resultado pretendido. Você pode fazer algo semelhante a: requests.post('http://example.com/', files=[('key1', 'param1'), ('key2', 'param2')]) sem ter um arquivo real lá.

E se formos exatos, form_data pode não ser totalmente óbvio, então por que não usar o parâmetro multipart_form_data , mas agora isso também é desajeitado. É explícito, sim, e o PEP 8 pede explicitamente, mas o comportamento existente está bem documentado . Se @kennethreitz decidir aceitar esta solicitação de recurso, tudo o que o parâmetro precisará fazer é atuar como um apelido para o parâmetro files. Mas considerando que o comportamento já é suportado , não acho que seja necessário.

@ sigmavirus24 e @Lukasa resumiram isso perfeitamente.

@ piotr-dobrogost agradecemos suas contribuições, mas não seu tom. Por favor, pare de fazer afirmações firmes contra nosso projeto e seus objetivos.

Do meu ponto de vista, é bastante frustrante que as solicitações já tenham suporte para postagens com várias partes, mas não dê ao usuário acesso a isso sem usar um arquivo. A sugestão de @ sigmavirus24 de apenas

Suspeito que isso continuará a ser um problema para os usuários no futuro, e estou um pouco confuso por que não parece haver um esforço para resolver isso.

Veja minha resposta para # 935

Oh, obrigado. Estou ansioso por isso!

@Lukasa

_Requests não suporta tipos MIME, mas sim casos de uso._

O envio de multipart/form-data dados é um caso de uso comum.

_ (...) enviar dados formatados em JSON é muito comum (...) _

Não podemos comparar o envio de json com o envio de multipart/form-data . Enviar json é fácil; você define Content-type , codifica os dados em uma linha usando o módulo integrado e pronto. O envio de multipart/form-data é mais complexo porque o corpo da solicitação deve ter uma estrutura específica. Em outras palavras, enviar json é meio transparente no que diz respeito ao HTTP, mas enviar multipart/form-data não é. Como Requests é a biblioteca HTTP, ela deve se preocupar em criar tal estrutura.

_Se você não conseguir imaginar uma maneira bonita de implementar essa funcionalidade, isso não acontecerá._

Ter files param atuais para enviar multipart/form-data que não tem NADA a ver com arquivos não é bonito de forma alguma (é feio), mas de alguma forma conseguiu entrar no codebase :). Encontrar algo menos feio é realmente fácil :)

@ sigmavirus24

_ (...) mas o comportamento existente está bem documentado._

Nenhuma quantidade de documentação torna uma API ruim de uma API ruim. Quanto melhor a API, menos precisa de documentação.

_Você pode fazer algo parecido com (...) _

Certo, mas isso é muito enganoso e não intuitivo. Descrevi o que há de errado em usar files param para isso em meu comentário anterior .

Resumindo: para chegar a algo melhor, precisamos admitir que a API atual é ruim no que diz respeito ao envio de multipart/form-data dados.

@spacecase

_A partir da minha perspectiva, é bastante frustrante que as solicitações já tenham suporte para postagens com várias partes, mas não dê ao usuário acesso a isso sem usar um arquivo_

Eu concordo e é por isso que criei o problema # 935.

Este problema foi encerrado.

Perdoe-me @kennethreitz, mas @spacecase , não usei nenhum arquivo nesse exemplo. Usei o parâmetro files para fazer exatamente o que você deseja. Se eu tivesse usado um arquivo, você teria visto open('filename') .

@ sigmavirus24 , Infelizmente não é isso que eu quero. Não é uma questão de se o cliente está lendo um arquivo ou não. É o que está sendo informado ao servidor. Em seu exemplo, o corpo da postagem é

Content-Disposition: form-data; name="key1"; filename="key1"
Content-Type: application/octet-stream

param1
--2f8732ee35564115a6c6e0c1032773e8
Content-Disposition: form-data; name="key2"; filename="key2"
Content-Type: application/octet-stream

param2
--2f8732ee35564115a6c6e0c1032773e8--

Observe o uso de filename= . Diz ao servidor que um arquivo está sendo enviado. Nos aplicativos web que trabalho com esse comportamento incorreto resulta em erro.

Sinto muito, mas a presunção de me dizer que é exatamente isso que eu quero me ofende. Vim oferecer ideias e ajudar, e parece que você também não quer neste caso. Tudo bem, mas, por favor, não me faça sentir que rebateu.

Hm, parece que me lembrei do comportamento incorretamente. Desculpe por isso. Não era minha intenção ofendê-lo ou fazer você se sentir rebaixado. Isso é definitivamente o suficiente para me convencer a precisar de uma maneira melhor de lidar com multipart/form-data , mas, por enquanto, ainda discordo que as ideias para a API não sejam nada elegantes.

@spacecase , consulte minha resposta a # 935. As coisas vão melhorar. Seu feedback é importante e muito apreciado. :)

@spacecase como um gesto para mostrar que sua opinião é importante, verifique sigmavirus24 / requests-data-scheme como uma medida temporária. Você terá que definir seu próprio cabeçalho Content-Type , mas isso pode ser um pequeno aborrecimento.

Obrigado @ sigmavirus24 , vou experimentar. Parece que vai resolver o problema.
Eu aprecio que você não quis me ofender. Eu me sinto melhor com isso e não tenho nada contra você ou o projeto.

Sim, parece que pareço impetuoso com algumas pessoas, então acho que preciso refinar o que escrevo na internet. Não entendo e outros concordaram comigo, mas estou trabalhando nisso. Acho que só preciso de um tamanho de amostra grande o suficiente para perceber o que é ofensivo para as pessoas sem a minha intenção (além de me fazer passar por ridículo por lembrar de algo funcionando de uma maneira que não o faz).

Eu tenho um arquivo e alguns valores-chave para postar. Então, qual é a maneira correta de enviar essa solicitação de formulário de várias partes? Espero que você possa me ajudar.

Content-Disposition: form-data; name="up"; filename="aa.PNG"
Content-Type: image/png

file data
---------------------------7dee5302248e
Content-Disposition: form-data; name="exp"


-----------------------------7dee5302248e
Content-Disposition: form-data; name="ptext"

text
-----------------------------7dee5302248e
Content-Disposition: form-data; name="board"

DV_Studio
-----------------------------7dee5302248e--

I tried this way but only got a 504 error.
myfile=[('file',open('bb.jpg')),('exp','python'),('ptext',''),('board','DV_Studio')]
r = requests.post(url,files=myfile)

@deerstalker para perguntas, use StackOverflow . Para responder à sua pergunta, porém, há um projeto em andamento para resolver este problema sigmavirus24 / requests-toolbelt

Observe também que já respondi a essa pergunta antes no StackOverflow, como você pode ver aqui .

Tenho o mesmo problema de gerar postagens com várias partes sem um arquivo. No momento, não faz diferença se você faz requests.post(url, data=data_dict) ou requests.post(url, data=data_dict, files={}) . Mas, como a palavra-chave files padrão None , devemos ser capazes de ter dois comportamentos distintos. A multipart/form-data quando files={} é especificado, e application/x-www-form-urlencoded quando não.

Perdi algo?

@jwoillez Você seguiu o link de estouro de pilha que postei?

Acho que sim, mas sua resposta aí trata do Multipart POST com um arquivo. Voltei ao problema original: POST multiparte sem arquivo.

O objeto de arquivo pode ser uma string. =) Isso deve fornecer as informações que você deseja.

Mas se eu seguir sua sugestão, não vou acabar com algo assim:

Content-Disposition: form-data; name="file"; filename="filename.txt"
Content-Type: text/plain

content
--3eeaadbfda0441b8be821bbed2962e4d--

onde content é a string que você me convida a usar em vez do arquivo?

Estou realmente atrás apenas disso:

Content-Disposition: form-data; name="key1"

value1
--3eeaadbfda0441b8be821bbed2962e4d

Os campos da tupla que você não deseja podem ser deixados como padrão:

files = {'name': ('', 'content')}

No futuro, encaminhe suas perguntas para StackOverflow. Todos os mantenedores o monitoram regularmente, e é o lugar mais apropriado para fazer essas perguntas.

Essa é a resposta que eu estava procurando, obrigado. Desculpe pelo barulho.

Talvez uma última pergunta, o seguinte é possível (nome de arquivo vazio especificado, conteúdo vazio)?

Content-Disposition: form-data; name="file"; filename=""
Content-Type: text/plain


--3eeaadbfda0441b8be821bbed2962e4d--

Você pode fornecer um conteúdo vazio usando uma string vazia na seção de conteúdo da tupla. Você não pode fornecer um nome de arquivo vazio literal, mas um nome de arquivo ausente deve ser tratado exatamente da mesma maneira.

Ocasionalmente, precisei fazer isso por vários motivos estranhos.
Eu sugeriria essa abordagem para qualquer pessoa que queira armar a API neste caso de uso:

class ForceMultipartDict(dict):
    def __bool__(self):
        return True


FORCE_MULTIPART = ForceMultipartDict()  # An empty dict that boolean-evaluates as `True`.


client.post("/", data={"some": "data"}, files=FORCE_MULTIPART)

Ou você pode usar o cinto de ferramentas e não recorrer a hacks.

Esta página foi útil?
0 / 5 - 0 avaliações