I assume you're using Ken Reitz's excellent requests library to do the HTTP requests (if not, you really should be!)
Most likely, the problem is that requests doesn't add a 'Content-type' header to multi-part uploads, and packmgr doesn't read multi-part bodies with no 'Content-type' header. So, while a normal 'requests' POST of a multipart-encoded file would go something like this:
url = 'http://localhost:4502/crx/packmgr/service/.json?cmd=upload'
files = {'package': open('package.zip', 'rb')}
r = requests.post(url, files=files)
you instead have to use the longer form, setting an explicit content-type:
url = 'http://localhost:4502/crx/packmgr/service/.json?cmd=upload'
files = {'package': ('package.zip', open('package.zip', 'rb'), 'application/octet-stream')}
r = requests.post(url, files=files)
Note that one side-effect of doing it this way is that it cannot stream the file - the entire package ZIP file will be read into memory.