Since there is no library that supports Python 3, I've been hacking this together on my own using the ad hoc reporting service and some of the code samples given on various parts of the API developers section of the site.
My script (below) successfully retrieves the SID, LSID and Auth values. It then attempts to POST the report definition XML, including the appropriate headers.
However, with no discernible logic or pattern, it only returns either 400 or 411 ("Length required") responses. As you can see, I have included the 'Content-Length' header and have even reverted back to the 100% copy/paste version of the report definition XML. My XML validates against the XSD, but I still can't get anything other than 400 and 411.
I've tried a number of different configurations and I'm convinced I'm very close to a working script... but at this point, I can't take this to stackoverflow or anywhere else and ask them to troubleshoot Google's server responses.
import urllib.parse
import urllib.request
import pprint
import socket
    ('accountType', 'GOOGLE'), ('service', 'adwords')]
auth = ''
testurl = url + "?" + urllib.parse.urlencode(data)
fh = urllib.request.urlopen(testurl)
equals = '='
eqenc = equals.encode('utf-8')
for msg in fh:
  number = 0
  block = msg.split(eqenc)
  for val in block:
## Â Â Â print (number)
    msgitem = block[number]
    valdecoded = (msgitem.decode('utf-8'))
    if valdecoded == 'SID':
      number +=1
      SID = (block[number].decode('utf-8'))
      number -=1
    elif valdecoded == 'LSID':
      number +=1
      LSID = (block[number].decode('utf-8'))
      number -=1
    elif valdecoded == 'Auth':
      number +=1
      Auth = (block[number].decode('utf-8'))
      number -=1
    number += 1
    Â
number = 0
fh.close()
#report_definition_xml = '<reportDefinition xmlns="
https://adwords.google.com/api/adwords/cm/v201302"><selector><fields>CampaignId</fields><fields>Impressions</fields><fields>Clicks</fields><fields>Cost</fields><predicates><field>Status</field><operator>IN</operator><values>ENABLED</values><values>PAUSED</values></predicates></selector><reportName>Search Query Performance eport</reportName><reportType>SEARCH_QUERY_PERFORMANCE_REPORT</reportType><dateRangeType>LAST_30_DAYS</dateRangeType><downloadFormat>CSV</downloadFormat><includeZeroImpressions>true</includeZeroImpressions></reportDefinition>'
report_definition_xml = '<reportDefinition xmlns="
https://adwords.google.com/api/adwords/cm/v201302"><selector><fields>CampaignId</fields><fields>Id</fields><fields>Impressions</fields><fields>Clicks</fields><fields>Cost</fields><predicates><field>Status</field><operator>IN</operator><values>ENABLED</values><values>PAUSED</values></predicates></selector><reportName>Custom Adgroup Performance Report</reportName><reportType>ADGROUP_PERFORMANCE_REPORT</reportType><dateRangeType>LAST_7_DAYS</dateRangeType><downloadFormat>CSV</downloadFormat></reportDefinition>'
payload = urllib.parse.urlencode({'__rdxml': report_definition_xml})
payloadencode = payload.encode('utf-8')
loadlength = len(payloadencode)
print (loadlength)
print (url)
request = urllib.request.Request(url)
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
request.add_header('clientCustomerId', '1234567890')
request.add_header('developerToken', 'abc123easyasdoeraymi')
request.add_header('Authorization', 'GoogleLogin auth=%s' % Auth)
request.add_header('Content-Length', loadlength)
f = urllib.request.urlopen(request, payloadencode, 60)
print(f.read().decode('utf-8'))
Traceback (most recent call last):
 File "C:/Python33/apicaller3.py", line 71, in <module>
  f = urllib.request.urlopen(request, payloadencode, 60)
 File "C:\Python33\lib\urllib\request.py", line 156, in urlopen
  return opener.open(url, data, timeout)
 File "C:\Python33\lib\urllib\request.py", line 475, in open
  response = meth(req, response)
 File "C:\Python33\lib\urllib\request.py", line 587, in http_response
  'http', request, response, code, msg, hdrs)
 File "C:\Python33\lib\urllib\request.py", line 513, in error
  return self._call_chain(*args)
 File "C:\Python33\lib\urllib\request.py", line 447, in _call_chain
 File "C:\Python33\lib\urllib\request.py", line 595, in http_error_default
  raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 400: Bad Request
Traceback (most recent call last):
 File "C:/Python33/apicaller3.py", line 71, in <module>
  f = urllib.request.urlopen(request, payloadencode, 60)
 File "C:\Python33\lib\urllib\request.py", line 156, in urlopen
  return opener.open(url, data, timeout)
 File "C:\Python33\lib\urllib\request.py", line 475, in open
  response = meth(req, response)
 File "C:\Python33\lib\urllib\request.py", line 587, in http_response
  'http', request, response, code, msg, hdrs)
 File "C:\Python33\lib\urllib\request.py", line 513, in error
  return self._call_chain(*args)
 File "C:\Python33\lib\urllib\request.py", line 447, in _call_chain
 File "C:\Python33\lib\urllib\request.py", line 595, in http_error_default
  raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 411: Length Required