Trying to check for none 200 Response in the current_track() function. What could be a problem? It throwing JSONDecodeError error. But if I understood raise_for_ status correctly it should have prevented the function from trying to load a JSON from a faulty web-page? If I run the script without this check and with uncommenting lines check_playback() it successfully catches JSONDecodeError.
The script is fetching data from Spotify and putting it to the status on vk.com.
import config
import webbrowser
import requests
import furl
import secrets
import string
import time
import os
import simplejson as json
URL_CODE_BASE_VK = 'https://oauth.vk.com/authorize'
URL_CODE_BASE_SP = 'https://accounts.spotify.com/authorize'
URL_TOKEN_VK = 'https://oauth.vk.com/access_token'
URL_TOKEN_SP = 'https://accounts.spotify.com/api/token'
URL_TRACK = 'https://api.spotify.com/v1/me/player/currently-playing'
URL_STATUS = 'https://api.vk.com/method/status.set'
EXP_IN_TOKEN_SP = 3400
EXP_IN_TOKEN_VK = 86400
FILE_TOKEN_VK = 'vk_token.json'
FILE_TOKEN_SP = 'sp_token.json'
def get_auth_code_vk():
url_code_params = {
'client_id': config.CLIENT_ID_VK,
'response_type': 'code',
'redirect_uri': 'https://oauth.vk.com/blank.html',
'v': 5.92,
'scope': 'status',
'state': gen_state(),
'display': 'page'
}
code = url_open(URL_CODE_BASE_VK, url_code_params)
return parse_code(code)
def get_auth_code_sp():
url_code_params = {
'client_id': config.CLIENT_ID_SP,
'response_type': 'code',
'redirect_uri': 'https://www.spotify.com/',
'scope': 'user-read-currently-playing',
'state': gen_state()
}
code = url_open(URL_CODE_BASE_SP, url_code_params)
return parse_code(code)
def gen_state():
symbols = string.ascii_lowercase + string.digits
return ''.join(secrets.choice(symbols) for _ in range(12))
def url_open(url_base, url_params):
url_code_full = furl.furl(url_base).add(url_params).url
webbrowser.open_new_tab(url_code_full)
input_url = input('Enter the whole URL, that you have been redirected on: ')
return input_url
def parse_code(url):
return (url.split("code=")[1]).split("&state=")[0]
def get_token_vk():
data = {
'grant_type': 'authorization_code',
'code': get_auth_code_vk(),
'redirect_uri': 'https://oauth.vk.com/blank.html',
'client_id': 6782333,
'client_secret': config.CLIENT_SECRET_VK
}
response = requests.post(url=URL_TOKEN_VK, data=data).json()
write_file(FILE_TOKEN_VK, response)
def get_token_sp():
data = {
'grant_type': 'authorization_code',
'code': get_auth_code_sp(),
'redirect_uri': 'https://www.spotify.com/',
'client_id': config.CLIENT_ID_SP,
'client_secret': config.CLIENT_SECRET_SP
}
response = requests.post(url=URL_TOKEN_SP, data=data).json()
write_file(FILE_TOKEN_SP, response)
def write_file(tkn_file, response):
dict = {}
dict['token'] = response["access_token"]
dict['time'] = time.time()
with open(tkn_file, 'w') as file:
file.write(json.dumps(dict))
def load_file(tkn_file):
with open(tkn_file) as file:
data = json.load(file)
return data
def set_status():
params = {
'v': 5.92,
'access_token': load_file(FILE_TOKEN_VK)['token'],
'text': current_track()
}
set_status = requests.get(url=URL_STATUS, params=params)
def track_data():
tkn_file = load_file(FILE_TOKEN_SP)['token']
headers = {
'Accept': 'application/json',
'Authorization': f'Bearer {tkn_file}'
}
return requests.get(url=URL_TRACK, headers=headers)
def current_track():
response = track_data()
print(response)
try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
return "Error: " + str(e)
# data = track_data().json()
data = response.json()
artist = data['item']['artists'][0]['name']
track = data['item']['name']
return(f'{artist} - {track}')
def check_playback():
set_status()
print(current_track())
# try:
# set_status()
# print(current_track())
# except json.decoder.JSONDecodeError:
# print('Not playing')
def token_missing(file):
return not os.path.isfile(file)
def token_expired(file, exp_in):
return time.time() - load_file(file)['time'] > exp_in
def token_not_valid(file, exp_in):
return token_missing(file) or token_expired(file, exp_in)
def run_script():
if token_not_valid(FILE_TOKEN_VK, EXP_IN_TOKEN_VK):
get_token_vk()
if token_not_valid(FILE_TOKEN_SP, EXP_IN_TOKEN_SP):
get_token_sp()
check_playback()
if __name__ == "__main__":
run_script()
Error screen
raise_for_status() will only raise an exception if the server reported an error to you (and even then, only if it actually followed the HTTP spec and returned a HTTP error code).
There is no way for the library to know that the response is incorrect. Even if it was correctly formatted JSON, it can't know what schema you expect it to follow (what fields should be present, and what types those fields should have). Even if it knew the schema and had verified it, there is no way for it to know that the data is actually correct and not made up on the spot.
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()
I have a class called Org, and I'm trying to access its method from multiple functions (that are defined outside of the class). I'm calling main() first, followed by discover_buildings(). The main() executes without error, however, I get AttributeError: 'Org' has no attribute 'headers' error after I call discover_buildings(). What is it that I'm doing wrong? (I was expecting the headers attribute to be shared across the different methods)
class Org(object):
def __init__(self, client_id, client_secret, grant_type='client_credentials'):
self.grant_type = grant_type
self.client_id = client_id
self.client_secret = client_secret
self.url = CI_A_URL
def auth(self):
""" authenticate with bos """
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': self.grant_type
}
r = requests.post(self.url + 'o/token/', data=params)
if r.status_code == 200:
self.access_token = r.json()['access_token']
self.headers = {
'Authorization': 'Bearer %s' %self.access_token,
'Content-Type': 'application/json',
}
else:
logging.error(r.content)
r.raise_for_status()
def get_buildings(self, perPage=1000):
params = {
'perPage': perPage
}
r = requests.get(self.url + 'buildings/', params=params, headers=self.headers)
result = r.json().get('data')
if r.status_code == 200:
buildings_dict = {i['name']: i['id'] for i in result}
sheet_buildings['A1'].value = buildings_dict
else:
logging.error(r.content)
r.raise_for_status()
client_id = 'xxx'
client_secret = 'yyy'
gateway_id = 123
o = Org(client_id, client_secret)
def discover_buildings():
return o.get_buildings()
def main():
return o.auth()
Thanks, in advance, for your help!
Try using a property to calculate headers whenever you need it and then cache it.
def auth(self):
""" authenticate with bos """
# 👇you might want to isolate `token` into a nested #property token
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': self.grant_type
}
# note assignment to `_headers`, not `headers`
r = requests.post(self.url + 'o/token/', data=params)
if r.status_code == 200:
self._access_token = r.json()['access_token']
# 👆
self._headers = { # 👈
'Authorization': 'Bearer %s' %self._access_token,
'Content-Type': 'application/json',
}
else:
logging.error(r.content)
r.raise_for_status()
#cache after the first time.
_headers = None
#property
def headers(self):
""" call auth when needed
you might want to isolate `token`
into its own property, allowing different
headers to use the same token lookup
"""
if self._headers is None:
self.auth()
return self._headers
the problem is the way you define "discover_buildings"
you define it first with "o" just initialised not after the authentication.
to handle this:
rewrite discover to take 'o' as a parameter
or
check first to see 'o' has 'headers' if not authenticate 'o' and do the rest
def discover_buildings():
if not getattr(o, 'headers'):
o.auth()
return o.get_buildings()
You didn't define self.headers. You need to run o.auth() (or define self.headers) before you run o.get_buildings().
I am trying to upload a pdf generated in Python as an attachment to a salesforce object using the simple_salesforce Python package. I have tried several different ways to accomplish this, but have had no luck so far. Here is the code
import base64
import json
from simple_salesforce import Salesforce
instance = ''
sessionId = sf.session_id
def pdf_encode(pdf_filename):
body = open(pdf_filename, 'rb') #open binary file in read mode
body = body.read()
body = base64.encodebytes(body)
body = pdf_encode('PDF_Report.pdf')
response = requests.post('https://%s.salesforce.com/services/data/v29.0/sobjects/Attachment/' % instance,
headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % sessionId },
data = json.dumps({
'ParentId': parent_id,
'Name': 'test.txt',
'body': body
})
)
I get this error.
TypeError: Object of type bytes is not JSON serializable
I have also tried to use
body = base64.encodebytes(body).decode('ascii')
in my code, but I can't get that to work either. I get the error
UnicodeError: encoding with 'idna' codec failed (UnicodeError: label empty or too long)
Any suggestions on how to upload a PDF in Python 3 into Salesforce as an attachment using simple_salesforce?
I was working on this and found a few resources to upload files. I created one for myself using that.
Below is the code that you can use for Python and have the file uploaded on Salesforce.
import requests
import base64
import json
params = {
"grant_type": "password",
"client_id": "Your_Client_Id",
"client_secret": "Your_Client_Secret",
"username": "YOUR_EMAIL#procureanalytics.com.pcsandbox", # The email you use to login
"password": "YOUR_PASSWORD+YOUR_SECURITY_TOKEN" # Concat your password and your security token
}
r = requests.post("https://test.salesforce.com/services/oauth2/token", params=params)
# if you connect to a Sandbox, use test.salesforce.com instead
access_token = r.json().get("access_token")
instance_url = r.json().get("instance_url")
print("Access Token:", access_token)
print("Instance URL", instance_url)
#######################################################################################
# Helper function
#######################################################################################
def sf_api_call(action, parameters = {}, method = 'get', data = {}):
"""
Helper function to make calls to Salesforce REST API.
Parameters: action (the URL), URL params, method (get, post or patch), data for POST/PATCH.
"""
headers = {
'Content-type': 'application/json',
'Accept-Encoding': 'gzip',
'Authorization': 'Bearer %s' % access_token
}
if method == 'get':
r = requests.request(method, instance_url+action, headers=headers, params=parameters, timeout=30)
elif method in ['post', 'patch']:
r = requests.request(method, instance_url+action, headers=headers, json=data, params=parameters, timeout=10)
else:
# other methods not implemented in this example
raise ValueError('Method should be get or post or patch.')
print('Debug: API %s call: %s' % (method, r.url) )
if r.status_code < 300:
if method=='patch':
return None
else:
return r.json()
else:
raise Exception('API error when calling %s : %s' % (r.url, r.content))
# Test connection
print(json.dumps(sf_api_call('/services/data/v40.0/query/', {
'q': 'SELECT Account.Name, Name, CloseDate from Opportunity where IsClosed = False order by CloseDate ASC LIMIT 1'
}), indent=2))
#######################################################################################
# File Upload from directory
#######################################################################################
# 1) Create a ContentVersion
path = "Folder_name\Sample_pdf.pdf"
with open(path, "rb") as f:
encoded_string = base64.b64encode(f.read()).decode("utf-8")
ContentVersion = sf_api_call('/services/data/v40.0/sobjects/ContentVersion', method="post", data={
'Title': 'Sample_pdf file',
'PathOnClient': path,
'VersionData': encoded_string,
})
ContentVersion_id = ContentVersion.get('id')
# 2) Get the ContentDocument id
ContentVersion = sf_api_call('/services/data/v40.0/sobjects/ContentVersion/%s' % ContentVersion_id)
ContentDocument_id = ContentVersion.get('ContentDocumentId')
# 3) Create a ContentDocumentLink
Id = "Abcd123" # This Id can be anything: Account_Id or Lead_Id or Opportunity_Id
ContentDocumentLink = sf_api_call('/services/data/v40.0/sobjects/ContentDocumentLink', method = 'post', data={
'ContentDocumentId': ContentDocument_id,
'LinkedEntityId': Id,
'ShareType': 'V'
})
How to use
Step 1:
Key in your email address and password here. Please note that the password here is a string of 'your password' and your 'security token'.
# Import libraries
import requests
import base64
import json
params = {
"grant_type": "password",
"client_id": "Your_Client_Id",
"client_secret": "Your_Client_Secret",
"username": "YOUR_EMAIL#procureanalytics.com.pcsandbox", # The email you use to login
"password": "YOUR_PASSWORD+YOUR_SECURITY_TOKEN" # Concat your password and your security token
}
r = requests.post("https://test.salesforce.com/services/oauth2/token", params=params)
# if you connect to a Sandbox, use test.salesforce.com instead
access_token = r.json().get("access_token")
instance_url = r.json().get("instance_url")
print("Access Token:", access_token)
print("Instance URL", instance_url)
You can get your security token on Salesforce through Account >> Settings >> Reset My Security Token.
You will receive an email from salesforce with your security token.
Step 2:
Choose appropriate link for request.post
For Sandbox environment:
r = requests.post("https://test.salesforce.com/services/oauth2/token", params=params)
For Production enviroment:
r = requests.post("https://login.salesforce.com/services/oauth2/token", params=params)
After your initial connection is ready, the output on the second cell should look something like this:
Access Token: !2864b793dbce2ad32c1ba7d71009ec84.b793dbce2ad32c1ba7d71009ec84
Instance URL https://your_company_name--pcsandbox.my.salesforce.com
Step 3:
Under the 'File upload from a directory' cell (Cell #5), specify your file path.
In my case, this is
# 1) Create a ContentVersion
path = "Folder_name\Sample_pdf.pdf"
with open(path, "rb") as f:
encoded_string = base64.b64encode(f.read()).decode("utf-8")
Step 4:
Under the same cell, mention the Id in which you would like to upload your file.
The sample code below is uploading a file on Accounts object for an account with Id: Abcd123
# 3) Create a ContentDocumentLink
Id = "Abcd123" # This Id can be anything: Account_Id or Lead_Id or Opportunity_Id
ContentDocumentLink = sf_api_call('/services/data/v40.0/sobjects/ContentDocumentLink', method = 'post', data={
'ContentDocumentId': ContentDocument_id,
'LinkedEntityId': Id,
'ShareType': 'V'
})
I am trying to fetch product data from an api.
By default this api returns 20 products and in a single request the api can return max 500 products if we use api's parameter Limit=500.
So for fetching all products we need to use one more parameter with Limit- Offset(Number of products to skip).
I have written following function to achieve this but in case of full data my function is not working well and it's giving me error like- Login failed, Signature mismatching.
def get_data(userid, api_key, action, pagination=True):
timeformat = datetime.datetime.now().replace(microsecond=0).isoformat() + '+08:00'
endpoint = 'https://example.com'
page_json = {}
# set required parameters for this api
parameters = {
'UserID': userid,
'Version': '1.0',
'Action': action,
'Format': 'JSON',
'Timestamp': timeformat
}
if pagination:
page = 0
parameters['Limit'] = 500
while True:
parameters['Offset'] = 500 * page
# set the required cryptographic signature
concatenated = urllib.parse.urlencode(sorted(parameters.items()))
parameters['Signature'] = HMAC(api_key, concatenated.encode('utf-8'), sha256).hexdigest()
page += 1
try:
response = requests.get(endpoint, params=parameters)
page_json = response.json()
except requests.exceptions.ConnectionError:
print("Connection refused!")
sleep(5)
else:
try:
concatenated = urllib.parse.urlencode(sorted(parameters.items()))
# set the required cryptographic signature
parameters['Signature'] = HMAC(api_key, concatenated.encode('utf-8'), sha256).hexdigest()
response = requests.get(endpoint, params=parameters)
page_json = response.json()
except requests.exceptions.ConnectionError:
print("Connection refused!")
sleep(5)
return page_json
It looks like I am not fitting my signature parameter line correctly in case of full data.I printed the value of concatenated and it looks like-
page is 1
concatenated:: Action=GetProducts&Format=JSON&Limit=500&Offset=500&Signature=3d9cd320a4bf816aeea828b9392ed2d5a27cd584b3a337338909c0ab161a101e&Timestamp=2018-05-26T12%3A58%3A38%2B08%3A00&UserID=contact%40example.com.sg&Version=1.0
try: {'ErrorResponse': {'Head': {'ErrorCode': '7', 'ErrorMessage': 'E7:Login failed. Signature mismatching', 'ErrorType': 'Sender', 'RequestAction': 'GetProducts', 'RequestId': '0bb606c015273197313552686ec46f'}}}
page is 2
concatenated:: Action=GetProducts&Format=JSON&Limit=500&Offset=1000&Signature=c1bda1a5ab21c4e4182cc82ca7ba87cb9fc6c5f24c36f9bb006f9da906cf7083&Timestamp=2018-05-26T12%3A58%3A38%2B08%3A00&UserID=contact%40example.com.sg&Version=1.0
try: {'ErrorResponse': {'Head': {'ErrorCode': '7', 'ErrorMessage': 'E7:Login failed. Signature mismatching', 'ErrorType': 'Sender', 'RequestAction': 'GetProducts', 'RequestId': '0bb606c015273197321748243ec3a5'}}}
Can you please look into my function and help me to find out what I have written wrong and what it should be like?
Try this please:
if pagination:
page = 0
parameters['Limit'] = 500
while True:
parameters['Offset'] = 500 * page
# set the required cryptographic signature
concatenated = urllib.parse.urlencode(sorted(parameters.items()))
parameters['Signature'] = HMAC(api_key, concatenated.encode('utf-8'), sha256).hexdigest()
page += 1
try:
response = requests.get(endpoint, params=parameters)
page_json = response.json()
except requests.exceptions.ConnectionError:
print("Connection refused!")
sleep(5)
del parameters['Signature']
Okay so I'm using code very similar to this (https://gist.github.com/metadaddy-sfdc/1374762)
to get authentication token and do simple query's using the libur2 for the rest api in python for a sales force database, but when I tried to follow the instructions which were given in this answer How to make HTTP DELETE method using urllib2?,
I cannot get it to work so that I can use delete, both codes use liburl but they seem to be in different format, so that I don't know how to apply the solution offered on stack exchange, to my code, as you can tell I am a beginner so any help would be greatly appreciated
edit:
here is the code I'm using with keys/passwords blanked
import urllib
import urllib2
import json
import pprint
import re
import subprocess
def authorise():
consumer_key = '**********************'
consumer_secret = '**************'
username = '***********'
password = '*****************'
login_server = 'https://login.salesforce.com'
token_url = login_server+'/services/oauth2/token'
params = urllib.urlencode({
'grant_type': 'password',
'client_id': consumer_key,
'client_secret': consumer_secret,
'username': username,
'password': password
})
data = urllib2.urlopen(token_url, params).read()
oauth = json.loads(data)
return oauth
def country_id_query(params):
query_url = oauth['instance_url']+'/services/data/v23.0/query?%s' % params
headers = {
'Authorization': 'OAuth '+oauth['access_token']
}
req = urllib2.Request(query_url, None, headers)
data = urllib2.urlopen(req).read()
result = json.loads(data)
id = result['records'][0]['Id']
return id
oauth = authorise()
token = oauth['access_token']
print "\ntoken is = " + token
params = urllib.urlencode({
'q': 'SELECT id from Country__c WHERE name = \'A New Found Land\''
})
id = country_id_query(params)
print "\nCountry id is "+id + "\n"
I am looking to find out what I need to add to this to get DELETE working
Okay, found the solution to above for anyone with a similar problem:
def delete_country(id):
query_url = oauth['instance_url']+'/services/data/v23.0/sobjects/Country__c/%s' % id + '/'
headers = {
'Authorization': 'OAuth '+oauth['access_token']
}
opener = urllib2.build_opener(urllib2.HTTPHandler)
req = urllib2.Request(query_url, None, headers)
req.get_method = lambda: 'DELETE' # creates the delete method
url = urllib2.urlopen(req) # deletes database item