i am building a kivy app that does APIs requests.
get_token() function runs first when app is launched to get all the necessary info, dicts, vars etc. After 30 min token gets expired, app has a buttons that on_press it will send request calls to the server, if the token is expired it wont work. I am trying to build a decorator #use_token that will check if simple request gets executed and receives response 200 Success code, if success: return(decorated function) else: calling get_token that will refresh token, and returning decorated function. A whole concept works, prints work at every possible block of code in every function, but when after 30 min token is expired, refresh_token() calls for get_token() inside the if statement, prints are still working but returned decorated function doesnt send requests, it prints test strings but doesnt do the main thing. That tells me that function get_token() executes inside if statement but doesn't update token information...
FIRST FUNCTION, RUNS ONES AT START:
def get_token():
url_token = "http://server.com"
payload = "{" \
"\n \"grantType\": \"password\"," \
"\n \"password\": \"string\"," \
"\n \"refreshToken\": \"string\"," \
"\n \"token\": \"string\"," \
"\n \"username\": \"admin\"" \
"\n}"
headers = {
'Content-Type': 'application/json',
'api_key': ''
}
global readyToken
readyToken = requests.request("POST", url_token, headers=headers, data=payload).json()['token']
print("Getting a NEW TOKEN!")
get_token()
DECORATOR FUNCTION:
def use_token(func):
#functools.wraps(func)
def refresh_token(*args):
url_check = "simplerequest.com"
response = requests.request("PUT", url_check, headers=HEADERS)
print("This print from url_check block "+str(response))
str_response = str(response)
if '401' in str_response:
print("401 found, Token is Expired, refreshing with get_token")
get_token()
else:
print("200 Code, success, passing, leaving else statement")
pass
print("emptying str_response and calling for decorated function:")
str_response = ""
return func(*args)
return refresh_token
DECORATED FUNCTION:
global URL_QC, HEADERS
URL_QC = "www.server.com"
HEADERS = {
'Content-Type': 'application/json',
'api_key': readyToken
}
#use_token
def change_channel(self, display_mac, ch_number):
print("Hello from DECORATED function!!!")
payload = "{\"deviceIds\": [" + str(display_mac) + "],\"menu\": \"save_sch_channel\", \"productType\": \"string\", \"value\":" + str(ch_number) + "}"
response = requests.request("PUT", URL_QC, headers=HEADERS, data=payload)
You are not updating the HEADERS variable when the readyToken changes.
HEADERS = {
'Content-Type': 'application/json',
'api_key': readyToken
}
In python, strings are passed by value. So here, you are only setting HEADERS['api_key'] to the current value of readyToken once. If you change readyToken later, the HEADERS is not updated because it only retains the original value of readyToken.
This can be solved by simply updating readyToken in the HEADERS every time it changes:
def get_token():
url_token = "http://server.com"
payload = {
"grantType": "password",
"password": "string",
"refreshToken": "string",
"token": "string",
"username": "admin"
}
headers = {
"Content-Type": 'application/json',
"api_key": ""
}
response = requests.post(url_token, headers=headers, data=payload)
print(f'Got Response: {response.json()}')
global readyToken
readyToken = response.json()['token']
# We also need to update headers!
global HEADERS
HEADERS['api_key'] = readyToken
Even better, if you are only using readyToken in the HEADERS, remove the readyToken variable entirely and simply update the global HEADERS variable:
def get_token():
...
response = requests.post(url_token, headers=headers, data=payload)
print(f'Got Response: {response.json()}')
global HEADERS
HEADERS['api_key'] = response.json()['token']
Alfa Q thank you so much, all your guesses and fixes was right! I do use token only in header, therefore i got read of readyToken and put token expression right into 'api_key' value. I also restructured the code and made a class implementing all your fixes:
token.py:
class Token:
def __init__(self):
self.url_check = "www.check.com"
self.url_token = "www.server.com"
self.payload = "{" \
"\n \"grantType\": \"password\"," \
"\n \"password\": \"password\"," \
"\n \"refreshToken\": \"string\"," \
"\n \"token\": \"string\"," \
"\n \"username\": \"admin\"" \
"\n}"
self.headers_token = {
'Content-Type': 'application/json',
'api_key': self.get_token()
}
def get_token(self):
return requests.post(self.url_token, headers=self.headers, data=self.payload).json()['token']
def use_token(self, func):
#functools.wraps(func)
def refresh_token(*args):
response = requests.put(self.url_check, headers=self.headers_token)
print("This print from url_check block: " + str(response))
if response.status_code == 200:
print('Status 200 | Token OK - No refresh necessary')
return func(*args)
elif response.status_code == 401:
print('Status 401 | Token is Expired - Refreshing')
self.get_token()
return func(*args)
else:
print(f'Status {response.status_code} | Error Occurred')
print("Print from REFRESH_TOKEN")
return refresh_token
main.py:
#token.use_token
def change_channel(self, display_mac, ch_number):
print("Hello from DECORATED function!!!")
payload = "{\"deviceIds\": [" + str(display_mac) + "],\"menu\": \"save_sch_channel\", \"productType\": \"string\", \"value\":" + str(ch_number) + "}"
response = requests.request("PUT", URL_QC, headers=token.headers_token, data=payload)
print(response)
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 am trying to pass the Oauth token generated into a #task request
This is resulting in a 401 error
from locust import HttpUser, constant, task
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class ProcessRequests(HttpUser):
def on_start(self):
tenant_id = "tenant123"
client_id = "client123"
secret = "secret123"
scope = "api://123/.default"
body ="grant_type=client_credentials&client_id=" + client_id + "&client_secret=" + secret + "&scope=" + scope
tokenResponse = self.client.post(
f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
body,
headers = { "ContentType": "application/x-www-form-urlencoded"}
)
response = tokenResponse.json()
responseToken = response['access_token']
self.headers = {'Authorization': 'Bearer' + responseToken}
#task
def get_labware(self):
self.client.get("https://url/123", name="Labware",headers=self.headers)
#task
def get_instruments(self):
self.client.get("https://url/456", name="Instruments", headers=self.headers)
I got this to work in K6/javascript so I know the parameters are correct. I just must not be parsing or passing them correctly here in python.
You need a space between Bearer and your token. Try this:
self.headers = {'Authorization': 'Bearer ' + responseToken}
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().
Here are three different ways i try to send data message fcm.
1. With pyfcm
def send_fcm(fcm_tokens, title=None, body=None, data_message=None):
push_service = FCMNotification(api_key=app.config['FCM_KEY'])
try:
if type(fcm_tokens) is list:
print(fcm_tokens, data_message)
result = push_service.notify_multiple_devices(registration_ids=fcm_tokens, data_message=data_message)
print(result, '++++++++++++++', flush=True)
else:
print(fcm_tokens, 'single device', data_message)
result = push_service.notify_single_device(registration_id=fcm_tokens, data_message=data_message)
print(result, flush=True)
except errors.InvalidDataError as e:
print(e, flush=True)
2. With firebase_admin sdk
def send_fcm_with_sdk(tokens, data_message=None):
if type(tokens) is list:
message = messaging.MulticastMessage(
data=data_message,
tokens=tokens, )
response = messaging.send_multicast(message)
print('{0} messages were sent successfully'.format(response.success_count), flush=True)
else:
message = messaging.Message(
data=data_message,
token=tokens,
)
print(message)
response = messaging.send(message)
print('Successfully sent message:', response)
3. With rest api
def send_fcm_with_rest(tokens, title=None, body=None, image=None, data_message=None):
headers = {
'Content-Type': 'application/json',
'Authorization': 'key=xxxxxxxKEY',
'Content-Type': 'application/json'
}
url = 'https://fcm.googleapis.com/fcm/send'
if type(tokens) is list:
payload = {
"registration_ids": tokens,
"collapse_key": "type_a",
"data": data_message
}
else:
payload = {
"to": tokens,
"collapse_key": "type_a",
"data": data_message
}
print(json.dumps(payload))
resp = requests.post(url, headers=headers, data=json.dumps(payload))
print(resp.text.encode('utf8'), flush=True)
return resp
The strange thing is all three of them run correctly and show a success message, with success_id, but none of fcm is received on the android side.
I tried sending from POSTMAN, and from postman, fcm are received on Andriod side.
Can anyone please tell me what is the issue in my code?
Your code is OK, however you should use flask==1.1.2 version as it is the stable release.
All the three methods for sending fcm is correct, later we figure out it was the issue from the frontend guy. So anyone can use these three methods if they want to.
I am trying to create and populate a playlist using the Spotify Web API. I am following this official reference, and I am using Python 3 with the requests module. Here is my code:
def spotify_write_playlist(auth, name, tracks, public=True):
ids = []
for track in tracks:
track_id = track.services['spotify']
if track_id: ids.append(track_id)
headers = {
"authorization":"Bearer " + auth.token,
"content-type":"application/json"
}
data = {
"name":name,
"public":public
}
r = makeRequest("https://api.spotify.com/v1/users/" + auth.username + "/playlists", "post", 201, json=data, headers=headers)
playlist_id = json.loads(r.content)['id']
data = {"uris":ids}
r = makeRequest("https://api.spotify.com/v1/users/" + auth.username + "/playlists/" + playlist_id + "/tracks", "post", 201, json=data, headers=headers)
return playlist_id
def makeRequest(url, method="get", expectedCode=200, *args, **kwargs):
while True:
r = requests.request(method, url, **kwargs)
if r.status_code == 429:
time.sleep(TMR_DELAY)
continue
elif r.status_code == expectedCode:
return r
else:
if "spotify.com" in url:
raise spotify.ApiError(r.status_code, expectedCode, r.content)
else:
raise youtube.ApiError(r.status_code, expectedCode, r.content)
The makeRequest function is a wrapper around requests.request that handles ratelimiting.
The above code when run with a bunch of sample tracks returns an error 400 at the first call of makeRequest, so my sample tracks can't be the issue as only the name and public variables are involved with that call.
The error response has no body, so no description of the error. This suggests I am probably missing something very obvious. Can anyone help?
The playlist creation request stopped erroring, and I have no idea why. I must of changed whatever was wrong when trying to diagnose the problem. I thought it was trying to create a playlist of the same name but spotify allows duplicate playlists. Maybe something was wrong with the token or token object I was using. Sorry.
However, I also found another issue when the previous one was cleared: the when adding to tracks you specify the track uri (spotify:track:abcdef123) not just the track id (abcdef123). I have revised spotify_write_playlists below:
def spotify_write_playlist(auth, name, tracks, public=True):
ids = []
for track in tracks:
track_id = track.services['spotify']
if track_id: ids.append("spotify:track:"+track_id)
headers = {
"authorization":"Bearer " + auth.token,
"content-type":"application/json"
}
data = {
"name":name,
"public":public
}
r = makeRequest("https://api.spotify.com/v1/users/" + auth.username + "/playlists", "post", 201, json=data, headers=headers)
playlist_id = json.loads(r.content)['id']
data = {"uris":ids}
r = makeRequest("https://api.spotify.com/v1/users/" + auth.username + "/playlists/" + playlist_id + "/tracks", "post", 201, json=data, headers=headers)
return playlist_id