2013-04-01 9 views
8

Próbuję wysłać wiadomość wieloczęściową/powiązaną za pomocą żądań w języku Python. Wydaje się, że skrypt jest dość prosty, z tym wyjątkiem, że wydaje się, że tylko żądania pozwalają na wysyłanie wiadomości wieloczęściowych/danych formularzy, chociaż ich dokumentacja nie wskazuje wyraźnie tego w ten czy inny sposób.Jak wysłać "multipart/related" z zapytaniami w pythonie?

Mój przypadek użycia wysyła mydło z załącznikami. Mogę dostarczyć słownik z dwoma plikami, których zawartością jest wiadomość testowa i dokument testowy, który próbuję wysłać. Pierwszy zawiera komunikat mydła z wszystkimi instrukcjami, drugi jest rzeczywistym dokumentem.

Jeśli jednak nie podam wartości nagłówków, to tylko przy użyciu opcji plików wydaje się, że używają one danych wieloczęściowych/formularzy. Ale jeśli określę nagłówki w celu określenia innego typu wieloczęściowego, żądania nie wydają się uzupełniać informacjami o granicach mime.

url = 'http://10.10.10.90:8020/foo' 
headers = {'content-type': 'multipart/related'} 
files = {'submission': open('submission_set.xml', 'rb'), 'document': open('document.txt', 'rb')} 
response = requests.post(url, data=data, headers=headers) 
print response.text 

Czy można to zrobić za pomocą żądań? Czy jest jeszcze inne narzędzie, na które powinienem patrzeć?

+0

Czy sprawdziłeś te 22 pytania, które pojawiły się w wyniku wyszukiwania '[python] [python-requests] + multipart'? –

+3

@PiotrDobrogost: To wszystko dotyczy 'multipart/form-data', które' requests' obsługuje. Jest to * 'multipart/related' *, które nie jest typowym kodowaniem dla' POST' i 'requests' nie obsługuje tego automatycznie. –

Odpowiedz

19

Musisz samodzielnie utworzyć kodowanie MIME. Można to zrobić za pomocą pakietu email.mime:

import requests 
from email.mime.multipart import MIMEMultipart 
from email.mime.text import MIMEText 

related = MIMEMultipart('related') 

submission = MIMEText('text', 'xml', 'utf8') 
submission.set_payload(open('submission_set.xml', 'rb').read()) 
related.attach(submission) 

document = MIMEText('text', 'plain') 
document.set_payload(open('document.txt', 'rb').read()) 
related.attach(document) 

body = related.as_string().split('\n\n', 1)[1] 
headers = dict(related.items()) 

r = requests.post(url, data=body, headers=headers) 

I domniemywać plik XML używa UTF-8, prawdopodobnie chcesz ustawić zestaw znaków dla wejścia document również.

requests tylko wie, jak utworzyć multipart/form-data korpusów postów; multipart/related nie jest powszechnie używany.

+0

Dzięki! To jest bardzo pomocne. Właściwie próbowałem wcześniej użyć pakietu 'email.mime', ale nie mogłem wymyślić, jak połączyć obie usługi razem. Pomogłeś mi połączyć dwa alternatywne skrypty w jeden! Wydaje się jednak, że jest mały problem i nie mogę stwierdzić, czy jest to połączenie usług, czy inna usterka.Otrzymuję "nieoczekiwany EOF w prologu w [wiersz, col {nieznane-źródło}]: [1,0]", co sprawia, że ​​wygląda na to, że pierwszy wysłany znak jest współdziałający jako EOF. Czy może to być spowodowane łańcuchem narzędzi/kodowaniem? –

+0

Nie mam absolutnie żadnego pojęcia; brzmi jak problem z parsowaniem XML, ale błąd nie jest znany. –

+0

To wygląda fantastycznie, ale moja prośba wydaje się powiesić na zawsze. Jakieś wskazówki? – zapatilla

0

Pracuję z requests i przesyłam "wieloczęściowy" interfejs Google Drive API.

Rozwiązanie email.mime nie działa z interfejsem API Google, więc zagłębiłem się w kod źródłowy requests, aby zobaczyć, w jaki sposób implementuje on obiekty multipart/form-data.

requests używa urllib3.filepost.encode_multipart_formdata() pomocnik, który może być owinięty w celu zapewnienia multipart/related:

from urllib3.filepost import encode_multipart_formdata, choose_boundary 

def encode_multipart_related(fields, boundary=None): 
    if boundary is None: 
     boundary = choose_boundary() 

    body, _ = encode_multipart_formdata(fields, boundary) 
    content_type = str('multipart/related; boundary=%s' % boundary) 

    return body, content_type 

Teraz możemy użyć encode_multipart_related() stworzyć (body, content_type) krotki, która odpowiada wymaganiom Google:

import json 
from urllib3.fields import RequestField 

def encode_media_related(metadata, media, media_content_type): 
    rf1 = RequestField(
     name='placeholder', 
     data=json.dumps(metadata), 
     headers={'Content-Type': 'application/json; charset=UTF-8'}, 
    ) 
    rf2 = RequestField(
     name='placeholder2', 
     data=media, 
     headers={'Content-Type': media_content_type}, 
    ) 
    return encode_multipart_related([rf1, rf2]) 

Oto pełny przykład użycia naszego encode_media_related() do przesłania pliku cześć World do Dysku Google przy użyciu biblioteki google_auth.

from google.oauth2 import service_account 
import google.auth.transport.requests 

credentials = service_account.Credentials.from_service_account_file(
    PATH_TO_SERVICE_FILE, 
    scopes=['https://www.googleapis.com/auth/drive.file'], 
) 
session = google.auth.transport.requests.AuthorizedSession(credentials) 

metadata = { 
    'mimeType': 'application/vnd.google-apps.document', 
    'name': 'Test Upload', 
} 
body, content_type = encode_media_related(
    metadata, 
    '<html><body><p>Hello World!</body></html>', 
    'text/html; charset=UTF-8', 
) 
resp = session.post(
    'https://www.googleapis.com/upload/drive/v3/files', 
    data=body, 
    params={'uploadType': 'multipart'}, 
    headers={'Content-Type': content_type}, 
) 

print 'Uploaded to file with id: %s' % resp.json()['id'] 
Powiązane problemy