This is my code for testing Trello API:
import string
import random
import requests
from settings import *
nonce = lambda x: ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10 if not x else x))
def get_board_detals(board_id):
url = "https://trello.com/1/boards/" + board_id
params = {"key":key,
"token":token}
return requests.get(url=url, params=params)
def post_board_name(board_name):
url = "https://api.trello.com/1/boards"
params = {"key": key,
"token": token,
"name": board_name}
return requests.post(url=url, params=params)
def put_new_board_name(board_id):
url = "https://api.trello.com/1/boards" + board_id
params = {"key": key,
"token": token}
return requests.put(url=url, params=params)
and code for unittest:
import unittest
from board_details import *
class BoardDetails(unittest.TestCase):
def setUp(self):
self.name = nonce(10)
def test_put_new_name(self):
result_post = post_board_name(self.name)
board_id = result_post.json()['id']
result_put = put_new_board_name(board_id)
self.assertEqual(result_put.status_code, 200)
self.assertIn(result_put.json()['name'], self.name)
if __name__ == '__main__':
unittest.main()
When I run the code, I receive an error: AssertionError: 404 != 200.
I don't know why it is like that, can someone point out what's the reason for my error?
I did it like this and it seems to work fine:
function:
def put_new_board_name(board_id, board_name):
url = "https://api.trello.com/1/boards/" + board_id +"/name"
params = {"key": key,
"token": token,
"value" : board_name}
return requests.put(url=url, params=params)
and method:
def test_put_new_name(self):
result_post = post_board_name(self.name)
board_id = result_post.json()['id']
result_put = put_new_board_name(board_id, board_name=self.name)
self.assertEqual(result_put.status_code, 200)
self.assertIn(result_put.json()['name'], self.name)
Related
Hello and thanks in advance for any help
I used this post (How to fix <Response [400]> error when making a request to the Bitget Futures Rest API in Python?) to get me well on my way to creating a post buy limit order for the Bitget API.
I keep getting this error though: {'code': '40409', 'msg': 'wrong format', 'requestTime': 1673551362880, 'data': None}
Looking up that error code suggests it's the time - I presume that used for the nonce - which is incorrect somehow?
For my post order I've tried using exactly the same format as the very useful answer provided by Tariq - https://stackoverflow.com/users/12907359/tarique in the above post - (Thanks Tariq!) ...therefore, effectively the only things that change from his code (which works for me), is the fact that mine a POST rather than a GET, and the 'query' content is different.
I'm posting below Tariq's code which works and my own as well where I'm getting the error. Any ideas very welcome, thanks!
I have commented where appropriate to highlight Tariq's original code and where I have created similar functions of my own.
import hashlib
import hmac
import base64
import requests
import time
import json
api_passphrase = "your secret"
api_key = "api key"
api_sec = "api secret"
api_url = "https://api.bitget.com"
#symbol = "BTCUSDT_UMCBL" # this is futures
symbol = 'BTC/USDT' # this is spot - I've added this here
def parse_params_to_str(params):
url = '?'
for key, value in params.items():
url = url + str(key) + '=' + str(value) + '&'
return url[0:-1]
def get_signature(message):
mac = hmac.new(bytes(api_sec, encoding='utf8'), bytes(message, encoding='utf-8'), digestmod='sha256')
d = mac.digest()
return base64.b64encode(d)
def bitget_request_info(request_path, body, query, method): # this is Tariq's
std_time = time.time() * 1000
new_time = int(std_time)
if str(body) == '{}' or str(body) == 'None':
converted_body = ''
else:
converted_body = json.dumps(body)
message = str(new_time) + method + request_path + parse_params_to_str(query) + converted_body
headers = {"ACCESS-KEY": api_key,
"ACCESS-SIGN": get_signature(message),
"ACCESS-TIMESTAMP": str(new_time),
"ACCESS-PASSPHRASE": api_passphrase,
"Content-Type": "application/json",
"Locale": "en-US"
}
if method == "GET":
request_resp = requests.get((api_url + request_path), headers=headers, params=query)
return request_resp
def bitget_post_buy_order(request_path, body, query, method): # This is my version for the post buy order
std_time = time.time() * 1000
new_time = int(std_time)
if str(body) == '{}' or str(body) == 'None':
converted_body = ''
else:
converted_body = json.dumps(body)
message = str(new_time) + method + request_path + parse_params_to_str(query) + converted_body
headers = {"ACCESS-KEY": api_key,
"ACCESS-SIGN": get_signature(message),
"ACCESS-TIMESTAMP": str(new_time),
"ACCESS-PASSPHRASE": api_passphrase
"Content-Type": "application/json",
"Locale": "en-US"
}
if method == "POST":
request_resp = requests.post((api_url + request_path), headers=headers, params=query)
return request_resp
def get_info(): # this was Tariq's (I put it in a def)
order_resp = bitget_request_info("/api/mix/v1/account/account", None, query, "GET")
print(order_resp.json())
def place_buy_order(): # this is my version
place_order = bitget_post_buy_order("/api/spot/v1/trade/orders", None, query, "POST")
print(place_order.json())
def get_query_deets(): # this was Tariq's original
global query
query = {
"symbol": symbol,
"marginCoin": "USDT"
}
return(query)
def buy_order_deets(): # this is mine for the post buy order query
global query
query = {
'symbol': symbol,
'side': 'buy',
'orderType': 'limit',
'price': 9999,
'quantity': 0.01
}
return(query)
def main():
buy_order_deets()
place_buy_order()
#get_query_deets()
#get_info()
main()
According to official documentation https://bybit-exchange.github.io/docs/derivativesV3/unified_margin/#t-constructingtherequest I try to get account balance endpoint: GET /unified/v3/private/account/wallet/balance. But anyway I get this response: {"retCode":10004,"retMsg":"error sign!","result":null,"retExtInfo":null,"time":1665751778451}
from urllib.parse import urlencode
import requests
import hashlib
import time
import hmac
import uuid
api_key = 'xxxxxxxxxxxx'
secret_key = 'xxxxxxxxxxxxxxxxxxxx'
httpClient = requests.Session()
recv_window = "5000"
url = "https://api.bybit.com"
def HTTP_Request(endPoint, method, payload):
time_stamp = requests.get(url + "/v3/public/time").json()
time_stamp = str(time_stamp["time"])
signature = genSignature(params, time_stamp)
headers = {
'X-BAPI-SIGN-TYPE': '2',
'X-BAPI-SIGN': signature,
'X-BAPI-API-KEY': api_key,
'X-BAPI-TIMESTAMP': time_stamp,
'X-BAPI-RECV-WINDOW': recv_window
}
response = httpClient.request(method, url+endpoint+"?"+payload, headers=headers)
print(response.text)
def genSignature(payload, time_stamp):
# encoding list obj to url format str
peyload_url_enc = urlencode(
sorted(payload.items(), key=lambda tup: tup[0])
)
param_str = str(time_stamp) + api_key + recv_window + peyload_url_enc
hash = hmac.new(
key=bytes(secret_key, "utf-8"),
msg=param_str.encode("utf-8"),
digestmod="sha256"
)
signature = hash.hexdigest()
return signature
endpoint = "/unified/v3/private/account/wallet/balance"
method = "GET"
params = {"coin": "USDT"}
HTTP_Request(endpoint, method, str(params))
The problem seems to be in genSignature, but no matter how I change the function, the error remains the same
Thanks to this repository I found another method of connecting to the Bybit. So if you faced with similar problem, open the link, there are also some examples of creating different post/get queries
My working variant:
def bybit_spot_balance(AccessKey: str, SecretKey: str) -> dict:
params = {
"api_key": AccessKey,
"timestamp": round(time.time() * 1000),
"recv_window": 10000
}
# Create the param str
param_str = urlencode(
sorted(params.items(), key=lambda tup: tup[0])
)
# Generate the signature
hash = hmac.new(
bytes(SecretKey, "utf-8"),
param_str.encode("utf-8"),
hashlib.sha256
)
signature = hash.hexdigest()
sign_real = {
"sign": signature
}
param_str = quote_plus(param_str, safe="=&")
full_param_str = f"{param_str}&sign={sign_real['sign']}"
# Request information
url = "https://api.bybit.com/spot/v3/private/account"
headers = {"Content-Type": "application/json"}
body = dict(params, **sign_real)
urllib3.disable_warnings()
response = requests.get(f"{url}?{full_param_str}", headers=headers, verify=False).json()
return response
I tried to connect to the Riot api to access the summoner info. I wrote 3 files:
"RiotConsts": a file setting some constants such as "URL", "api_version", "region"
"RiotAPI": the main functions
"riot_main": to call the info I want.
I am receiving an error: "RiotAPI' object has no attribute '_request'".
class RiotAPI(object):
def __init__(self,api_key,region=Consts.REGIONS['europe_nordic_and_east']):
self.api_key = api_key
self.region = region
def request(self, api_key, params={}):
args = {'api_key': self.api_key}
for k,v in params.items():
if k not in args:
args[k] = v
response = requests.get(
Consts.URL['base'].format(
proxy = self.region,
region = self.region,
url = api_url
),
params=args
)
print (response.url)
return response.json()
def get_summoner_by_name(self, name):
api_url = Consts.URL['summoner_by_name'].format(
version=Consts.API_VERSIONS['summoner'],
summonerName=name
)
return self._request(api_url)
I expect to receive the summoner info but I got:
'RiotAPI' object has no attribute '_request'
Simpler code to connect Riot API and to have summoner infos :
class RiotApi(object):
def __init__(self, api_key: str, region="euw1"):
self.__RIOT_API_KEY = api_key
self.__HEADER = {'X-Riot-Token': self.__RIOT_API_KEY}
self.__REGION = region
self.__BASE_URL = ".api.riotgames.com/lol/"
self.__API_URL_SUMMONER_V4 = "https://" + self.__REGION + self.__BASE_URL + "summoner/v4/summoners/"
def get_summoner_by_name(self, summoner_name: str) -> dict:
"""Summoner Infos and Ids
#param summoner_name: LoL summoner name
#return json object of infos and ids
"""
url = self.__API_URL_SUMMONER_V4 + "by-name/" + summoner_name
request = requests.get(url, headers=self.__HEADER)
return request.json()
As mentioned in comment, object has no attribute error is caused by invocation of undefined attribute (or method) -- in this case _request() method. So, assuming the rest of the code are correct, you can try this code:
# class RiotAPI(object):
class RiotAPI:
def __init__(self,api_key,region=Consts.REGIONS['europe_nordic_and_east']):
self.api_key = api_key
self.region = region
# def request(self, api_key, params={}):
def request(self, api_url, params={}):
args = {'api_key': self.api_key}
for k,v in params.items():
if k not in args:
args[k] = v
response = requests.get(
Consts.URL['base'].format(
proxy = self.region,
region = self.region,
url = api_url
),
params=args
)
print (response.url)
return response.json()
def get_summoner_by_name(self, name):
api_url = Consts.URL['summoner_by_name'].format(
version=Consts.API_VERSIONS['summoner'],
summonerName=name
)
#return self._request(api_url)
return self.request(api_url)
I am trying to use a WooCommerce Python client to make a POST request but
I get "Provided Signature does not match"
The client I use is from:
WooCommerce API OAuth in python
This is my client:
#!/usr/bin/env python
import requests
import random
import string
import time
from hashlib import sha1
import hmac
import binascii
import re
import collections
from urllib import quote, urlencode
def uksort(dictionary):
return collections.OrderedDict(sorted(dictionary.items(), cmp = cmp))
class WooCommerce(object):
def __init__(self, consumer_key, consumer_secret, endpoint):
self.consumer_key = consumer_key
self.consumer_secret = consumer_secret
self.endpoint = endpoint
def _make_request(self, resource, params, method = "GET"):
oauth_params = {
"oauth_consumer_key": self.consumer_key,
"oauth_nonce": self._gen_nonce(),
"oauth_timestamp": self._gen_timestamp(),
"oauth_signature_method": "HMAC-SHA1",
}
oauth_params["oauth_signature"] = self._gen_signature(resource, dict(params.items() + oauth_params.items()), method)
params = dict(params.items() + oauth_params.items())
if method == "GET":
print self.endpoint + resource + "?" + urlencode(params)
elif method == "POST":
print self.endpoint + resource + "?" + urlencode(params)
req = urllib.request.Request(self.endpoint + resource + "?" + urlencode(params))
open = urllib.request.urlopen(req)
requestContent = open.read()
#print(open)
def _gen_nonce(self):
ran_string = ''.join(random.choice(string.ascii_uppercase + string.digits) for i in range(32)).encode("base64")
alnum_hash = re.sub(r'[^a-zA-Z0-9]', "", ran_string)
return alnum_hash
def _gen_timestamp(self):
return int(time.time())
def _gen_signature(self, resource, params, method):
base_request_uri = quote(self.endpoint + resource, safe = "")
normalized_params = self._normalize_params(params)
sorted_params = uksort(normalized_params)
query_string = "%26".join([key + "%3D" + value for key, value in sorted_params.iteritems()])
raw_string = method + "&" + base_request_uri + "&" + query_string
hashed = hmac.new(self.consumer_secret, raw_string, sha1)
return binascii.b2a_base64(hashed.digest()).rstrip("\n")
def _normalize_params(self, params):
normalized = {}
for key, value in params.iteritems():
key = quote(str(key), safe = "")
value = quote(str(value), safe = "")
normalized[key] = value
return normalized
And I use it like this from another class:
woo_client = WooCommerce('ck_7bb1951bee7454b2e29bf5eef9205e0e', 'cs_155cd9420201c0a7e140bebd6a9794c7', 'http://dima.bg/wc-api/v2')
data = {
"product": {
"title": "testname",
}
}
result = self.woo_client._make_request("/products/", data, 'POST')
Can you see something wrong with my URL ? Thanks for your time.
http://xxxxxxxxx.xx/wc-api/v2/products/?product=%7B%27title%27%3A+%27testname%27%7D&oauth_nonce=NThWODczRFIyWkxRNFZOVkUxNFdRSVo0QjFSNllIVFk&oauth_timestamp=1423647865&oauth_consumer_key=ck_7bb1951bee7454b2e29bf5eef9205e0e&oauth_signature_method=HMAC-SHA1&oauth_signature=3PSnEEf08gFthIRAr8AUKQiDjco%3D
I used your code as a starting point for my own solution to this problem and had some success! Here's what I did:
import requests
import random
import string
import time
from hashlib import sha1
import hmac
import binascii
import re
from urllib import quote, urlencode
import httplib2
from collections import OrderedDict
def key_compare(a, b):
return cmp(a[0], b[0])
class Restful_Client(object):
"""docstring for Restful_Client"""
def __init__(self, endpoint, consumer_key, consumer_secret):
super(Restful_Client, self).__init__()
self.consumer_key = consumer_key
self.consumer_secret = consumer_secret
self.endpoint = endpoint
def make_request(self, resource, params, method='GET' ):
oauth_params = {
'oauth_consumer_key': self.consumer_key,
'oauth_nonce': self.gen_nonce(),
'oauth_timestamp': self.gen_timestamp(),
'oauth_signature_method': 'HMAC-SHA1',
# 'oauth_version':'1.0'
}
oauth_params['oauth_signature'] = self.gen_signature(
resource,
OrderedDict( params.items() + oauth_params.items() ),
method
)
params = OrderedDict( params.items() + oauth_params.items() )
clean_params = self.sort_params(self.normalize_params(params))
uri = endpoint + resource
p_string = urlencode(clean_params)
print 'p string:'
print '\n'.join(p_string.split('&'))
return httplib2.Http().request(uri + '?' + p_string)
def gen_signature(self, resource, params, method):
base_request_uri = quote(self.endpoint + resource, safe = "")
clean_params = self.sort_params(
self.normalize_params(
self.normalize_params(
params
)
)
)
query_string = '%26'.join([
key + '%3D' + value\
for key, value in clean_params.iteritems()
])
raw_string = '&'.join([method, base_request_uri, query_string])
print "raw string: "
print '\n'.join(raw_string.split('%26'))
hashed = hmac.new(self.consumer_secret, raw_string, sha1)
return binascii.b2a_base64( hashed.digest() )[:-1]
def normalize_string(self, string):
return quote(str(string), safe="")
def normalize_params(self, params):
return OrderedDict( [
(self.normalize_string(key), self.normalize_string(value))\
for key, value \
in params.iteritems()
])
def sort_params(self, params):
return OrderedDict(sorted(
params.iteritems(),
cmp=key_compare
))
def gen_timestamp(self):
return int(time.time())
# return 1429451603
def gen_nonce(self):
return hex(self.gen_timestamp())
#todo: make this more secure
if __name__ == "__main__":
store_url = '<STORE URL HERE>'
api_base = 'wc-api'
api_ver = 'v2'
endpoint = "%s/%s/%s/" % (store_url, api_base, api_ver)
consumer_key = '<CK HERE>'
consumer_secret = '<CS HERE>'
resource = 'customers'
parameters = {
# 'fields':'id,first_name'
}
rc = Restful_Client(endpoint, consumer_key, consumer_secret)
r, c = rc.make_request(resource, parameters, 'GET')
print r
print c
What I would suggest is ensuring that the parameters are encoded the correct number of times, I found that the parameters needed to be "double encoded" to work. It looks like in this case your parameters (the key and value strings) are not "double encoded" as suggested in the API doc http://woothemes.github.io/woocommerce-rest-api-docs/#authentication
You can test this hypothesis by trying a simple GET request without any parameters like the one in my code. if that works then that might be your problem. Good luck!
I'm having an awfully hard time with Yahoo's authentication/authorization. I've enabled BOSS in my account, set up a payment method, and now I'm trying to run a search using some python code:
import urllib2
import oauth2 as oauth
import time
OAUTH_CONSUMER_KEY = "blahblahblah"
OAUTH_CONSUMER_SECRET = "blah"
def oauth_request(url, params, method="GET"):
params['oauth_version'] = "1.0",
params['oauth_nonce'] = oauth.generate_nonce(),
params['oauth_timestamp'] = int(time.time())
consumer = oauth.Consumer(key=OAUTH_CONSUMER_KEY,
secret=OAUTH_CONSUMER_SECRET)
params['oauth_consumer_key'] = consumer.key
req = oauth.Request(method=method, url=url, parameters=params)
req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None)
return req
if __name__ == "__main__":
url = "http://yboss.yahooapis.com/ysearch/web"
req = oauth_request(url, params={"q": "cats dogs"})
req_url = req.to_url()
print req_url
result = urllib2.urlopen(req_url)
I keep getting a urllib2.HTTPError: HTTP Error 401: Unauthorized exception. I can't figure out whether there's something wrong with my key, or the method of signing, or if I'm somehow tampering with my data after signing, or what the deal is. Anyone have suggestions?
I made some small changes to make your example work. See code for comments.
import urllib2
import oauth2 as oauth
import time
OAUTH_CONSUMER_KEY = "blahblahblah"
OAUTH_CONSUMER_SECRET = "blah"
def oauth_request(url, params, method="GET"):
# Removed trailing commas here - they make a difference.
params['oauth_version'] = "1.0" #,
params['oauth_nonce'] = oauth.generate_nonce() #,
params['oauth_timestamp'] = int(time.time())
consumer = oauth.Consumer(key=OAUTH_CONSUMER_KEY,
secret=OAUTH_CONSUMER_SECRET)
params['oauth_consumer_key'] = consumer.key
req = oauth.Request(method=method, url=url, parameters=params)
req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None)
return req
if __name__ == "__main__":
url = "http://yboss.yahooapis.com/ysearch/web"
req = oauth_request(url, params={"q": "cats dogs"})
# This one is a bit nasty. Apparently the BOSS API does not like
# "+" in its URLs so you have to replace "%20" manually.
# Not sure if the API should be expected to accept either.
# Not sure why to_url does not just return %20 instead...
# Also, oauth2.Request seems to store parameters as unicode and forget
# to encode to utf8 prior to percentage encoding them in its to_url
# method. However, it's handled correctly for generating signatures.
# to_url fails when query parameters contain non-ASCII characters. To
# work around, manually utf8 encode the request parameters.
req['q'] = req['q'].encode('utf8')
req_url = req.to_url().replace('+', '%20')
print req_url
result = urllib2.urlopen(req_url)
Here is a Python code snippet that works for me against Yahoo! BOSS:
import httplib2
import oauth2
import time
OAUTH_CONSUMER_KEY = "Blah"
OAUTH_CONSUMER_SECRET = "Blah"
if __name__ == "__main__":
url = "http://yboss.yahooapis.com/ysearch/web?q=cats%20dogs"
consumer = oauth2.Consumer(key=OAUTH_CONSUMER_KEY,secret=OAUTH_CONSUMER_SECRET)
params = {
'oauth_version': '1.0',
'oauth_nonce': oauth2.generate_nonce(),
'oauth_timestamp': int(time.time()),
}
oauth_request = oauth2.Request(method='GET', url=url, parameters=params)
oauth_request.sign_request(oauth2.SignatureMethod_HMAC_SHA1(), consumer, None)
oauth_header=oauth_request.to_header(realm='yahooapis.com')
# Get search results
http = httplib2.Http()
resp, content = http.request(url, 'GET', headers=oauth_header)
print resp
print content
Im using an Authenticate Header to submit the OAuth signature.
So I decided to ditch Python and try Perl, and it Just Worked. Here's a minimal code sample:
use strict;
use Net::OAuth;
use LWP::UserAgent;
my $CC_KEY = "blahblahblah";
my $CC_SECRET = "blah";
my $url = 'http://yboss.yahooapis.com/ysearch/web';
print make_request($url, {q => "cat dog", format => "xml", count => 5});
sub make_request {
my ($url, $args) = #_;
my $request = Net::OAuth->request("request token")
->new(
consumer_key => $CC_KEY,
consumer_secret => $CC_SECRET,
request_url => $url,
request_method => 'GET',
signature_method => 'HMAC-SHA1',
timestamp => time,
nonce => int(rand 10**6),
callback => 'oob',
extra_params => $args,
protocol_version => Net::OAuth::PROTOCOL_VERSION_1_0A,
);
$request->sign;
my $res = LWP::UserAgent->new(env_proxy=>1)->get($request->to_url);
return $res->content if $res->is_success;
die $res->status_line;
}
Here's another solution, this time back in python-land. This was put together by Tom De Smedt, author of the Pattern web-mining kit.
I'll communicate with the author of python-oauth2 to see if it can be fixed.
OAUTH_CONSUMER_KEY = "blahblahblah"
OAUTH_CONSUMER_SECRET = "blah"
import urllib
import hmac
import time
import random
import base64
try:
from hashlib import sha1
from hashlib import md5
except:
import sha as sha1
import md5; md5=md5.new
def hmac_sha1(key, text):
return hmac.new(key, text, sha1).digest()
def oauth_nonce(length=40):
h = "".join([str(random.randint(0, 9)) for i in range(length)])
h = md5(str(time.time()) + h).hexdigest()
return h
def oauth_timestamp():
return str(int(time.time()))
def oauth_encode(s):
return urllib.quote(s, "~")
def oauth_signature(url, data={}, method="get", secret="", token=""):
# Signature base string: http://tools.ietf.org/html/rfc5849#section-3.4.1
base = oauth_encode(method.upper()) + "&"
base += oauth_encode(url.rstrip("?")) + "&"
base += oauth_encode("&".join(["%s=%s" % (k, v) for k, v in sorted(data.items())]))
# HMAC-SHA1 signature algorithm: http://tools.ietf.org/html/rfc5849#section-3.4.2
signature = hmac_sha1(oauth_encode(secret) + "&" + token, base)
signature = base64.b64encode(signature)
return signature
q = "cat"
url = "http://yboss.yahooapis.com/ysearch/" + "web" # web | images | news
data = {
"q": q,
"start": 0,
"count": 50, # 35 for images
"format": "xml",
"oauth_version": "1.0",
"oauth_nonce" : oauth_nonce(),
"oauth_timestamp" : oauth_timestamp(),
"oauth_consumer_key" : OAUTH_CONSUMER_KEY,
"oauth_signature_method" : "HMAC-SHA1",
}
data["oauth_signature"] = oauth_signature(url, data, secret=OAUTH_CONSUMER_SECRET)
complete_url = url + "?" + urllib.urlencode(data)
response = urllib.urlopen(complete_url)
print response.read()
Here is sample code to access Yahoo! BOSS API v2 using with python-oauth as oauth liberary.
OAUTH_CONSUMER_KEY = "<oauth consumer key>"
OAUTH_CONSUMER_SECRET = "<oauth consumer secret>"
URL = "http://yboss.yahooapis.com/ysearch/web"
import urllib
import oauth.oauth as oauth
data = {
"q": "yahoo boss search",
"start":0,
"count":2,
"format":"json"
}
consumer = oauth.OAuthConsumer(OAUTH_CONSUMER_KEY, OAUTH_CONSUMER_SECRET)
signature_method_plaintext = oauth.OAuthSignatureMethod_PLAINTEXT()
signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()
oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=None, http_method='GET', http_url=URL, parameters=data)
oauth_request.sign_request(signature_method_hmac_sha1, consumer, "")
complete_url = oauth_request.to_url()
response = urllib.urlopen(complete_url)
print "REQUEST URL => %s" % complete_url
print ""
print "RESPONSE =>"
print response.read()
I stepped into the urllib2.open code using the debugger, and found that the response has this header:
WWW-Authenticate: OAuth oauth_problem="version_rejected", realm="yahooapis.com"
So I guess I'm having some kind of version mismatch of OAuth.