A fork from Chris AtLee's poster package, available on Bitbucket.
poster provides a set of classes and functions to faciliate making HTTP POST (or PUT) requests using the standard multipart/form-data encoding.
The poster3
package publishes the same APIs as used in the poster
package, however the internal workings have changed completely. Until version 0.9, we will provide compatibility APIs that facade the new backend API.
In the 0.9 release, many of the scattered functions in poster
are now managed by objects, many of which are handled for you.
from poster import Form
import requests # For demonstration
# Create a new multipart form
form = Form()
# Add an image object
form.add_file('image', open('upload.jpg', 'rb'))
# Add a simple object
form.add_data('foo', 'bar')
content, headers = form.encode()
res = requests.post('http://site.com/form', data=content, headers=headers)
The headers
result is a dictionary containing the key and value pairs of headers that should be passed and applied to your request.
Here's what the resulting headers look like:
{
"Content-Type": "multipart/form-data; boundary=--efaa3fef19fd4826b3e7c6fc37967291",
"Content-Length": "318"
}
And for a look at what the content
(the encoded output) looks like,
--efaa3fef19fd4826b3e7c6fc37967291
Content-Disposition: form-data; name="foo"
Content-Type: text/plain
bar
--efaa3fef19fd4826b3e7c6fc37967291
Content-Disposition: form-data; name="image"; filename="upload.jpg"
Content-Type: image/jpg
[... image data here ...]
--efaa3fef19fd4826b3e7c6fc37967291--
This section is for reference to the backwords-compatible API we provide. This API will be removed in version 1.0, so you should convert to the new syntax immediately.
Here's an example client using the old poster API:
test_client.py:
# THESE FUNCTIONS ARE DEPRECATED AND
# _WILL_ BE REMOVED
from poster.encode import multipart_encode
from poster.streaminghttp import register_openers
import urllib2
# The register_openers() function does absolutely nothing.
# So we won't even call it.
# Start encoding the file "DSC0001.jpg", naming it "image1" in
# the request.
content, headers = multipart_encode({
"image1": open("DSC0001.jpg", "rb")
})
# Create the Request object
request = urllib2.Request("http://localhost:5000/upload", content, headers)
# Actually do the request, and get the response
print urllib2.urlopen(request).read()
test_server.py:
from paste import httpserver
import webob
def app(environ, start_response):
request = webob.Request(environ)
start_response("200 OK", [("Content-Type", "text/plain")])
for name,value in request.POST.items():
yield "%s: %s\n" % (name, value)
httpserver.serve(app, port=5000)
After starting up the server, you should be able to connect to it with the client and get the following output:
image1: FieldStorage('image1', 'DSC0001.jpg')
One of the core classes in the poster
API is the concept of a FormData object, a key value pair that has a value, whether it be a File
-like object or is a str
.
For every element you want to send in your multipart HTTP form, you will need to add a new FormData object. However, in most cases when you use poster
, you won't need to create these objects on your own.
Here's a quick guide:
from poster import Form, FormData
# The boundary is a random string used to separate data
boundary = 'mycustomboundary'
# If you want to track progress, you can pass a
# callback function.
def cb(parameter, current, total):
print('CB: {}/{} bytes!'.format(current, total))
form = Form()
# Create it automatically (provides the same parameters)
form.add_file('photo', open('profile.jpg', 'rb'),
filename='new_profile.jpg',
mime_type='image/jpg', # If you want to override it
cb=cb) # Add a progress callback
# Create it the hard way
data = FormData('foo', 'bar')
form.add_form_data(data)
The Multipart
class collects attributes and encodes the data into a HTTP multipart format.
from poster import Form, FormData
form = Form()
form.add_file('profile', open('profile.jpg', 'rb'))
form.add_data('foo', 'bar')
form.add_form_data(FormData('hello', 'world'))
# Encode it into a multipart string
content, headers = form.encode()
# Use the requests lib for example purposes, works with urllib and
# http/httplib too.
import requests
resp = requests.post('http://localhost/upload', content=content,
headers=headers)
- Added support for both Python 2.7+ and 3.2+.
- Cleaned out a lot of code, removed old imports and weird conditions.
- Refactored a lot of
MultipartParam
into theposter.FormData
class (the APIs have changed) - The following functions and attributes will be deprecated and removed in version 1.0.0:
- Using the
poster.encode.MultipartParam
class poster.streaminghttp.register_openers()
poster.encode.multipart_encode()
- Accessing the
poster.version
tuple
- Using the
- Encoding is now handled by the
Form
object, allowing you to add data easily - Added more thorough tests for both Python 2/3
- Travis CI now builds on all Python versions
- Removed streaming HTTP helpers, if needed, can be reimplemented.
For more information about migrating to the new API in 0.9.0, read the migration guide
- Factor out handler creation into get_handlers() method. Thanks to Flavio Percoco Premoli
- Fixed parameter name encoding so that it follows RFC 2388,2047. Thanks to Emilien Klein for pointing this out.
- Don’t include Content-Length header for each part of the multipart message. Fixes issues with some ruby web servers. Thanks to Anders Pearson.
- Added callback parameters to
MultipartParam
andmultipart_encode
so you can add progress indicators to your applications. Thanks to Ludvig Ericson for the suggestion. - Fixed a bug where posting to a url that returned a 401 code would hang. Thanks to Patrick Guido and Andreas Loupasakis for the bug reports.
MultipartParam.from_params
will now acceptMultipartParam
instances as the values of a dict object passed in. The parameter name must match the key corresponding to the parameter in the dict. Thanks to Matthew King for the suggestion.- poster now works on Python 2.7
- Update docs to clarify how to use multiple parameters with the same key
- Fix for unicode filenames. Thanks to Zed Shaw.
- Added poster.version attribute. Thanks to Chritophe Combelles.
- Fix
MultipartParam
to open files in binary mode - Update docs to open files in binary mode
- Updated
register_openers()
to return theOpenerDirector
object
- Added
__all__
attributes to modules - Bug fixes from 0.3:
- Fix connections to HTTPS. Thanks to Kenji Noguchi and Marat Khayrullin
- Bug fixes from 0.2
- Use quoted-string encoding for filename parameter
- Terminate encoded document with MIME boundary
- Bug fixes from version 0.1
- First release, used for internal projects
Copyright (c) 2011 Chris AtLee, 2016 Evan Darwin
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.