Converting Python REST API Calls to Power Query - python

I am working with a cloud-based service that is exposed using OAuth REST API.
I have a working Python script, with which I am able to first post username, password and api token to get an authentication token. Then I use the api and authentication tokens to get data. Here is my Python script:
import requests
base_url = 'https://example.com'
user_name = 'john_doe'
password = 'pssw0rd'
api_token = '4r-eueyta-dh7cuq-26'
timeout_min = 1
headers = {'Accept': 'application/json'}
# get authentication token
auth_url = base_url + '/api/authenticate'
data = {'Username': user_name,
'Password': password,
'ApiToken': api_token,
'AuthorizationTokenTimeoutMinutes': timeout_min}
r = requests.post(auth_url, headers=headers, data=data)
# the entire string response is the auth token
auth_token = r.text
# get data
proj_url = base_url + '/api/project/active?' + \
'api-token={0}&authentication-token={1}'.format(api_token, auth_token)
r = requests.get(proj_url, headers=headers)
print(r.text)
This is working fine.
Next I want to use the same approach inside Microsoft Power BI to configure this data as a data source. I followed the approach outlined at http://angryanalyticsblog.azurewebsites.net/index.php/2016/05/16/api-strategies-with-power-bi/
As mentioned in the post, I set the variables
base_url
user_name
password
api_token
auth_token_timeout_min
as parameters and set their values. Then got the following power query script in advanced editor:
let
// get auth token
auth_url = #"base_url" & "/api/authenticate",
post_data = "Username=" & #"user_name" & "&Password=" & #"password" & "&ApiToken=" & #"api_token" & "&AuthorizationTokenTimeoutMinutes=" & #"auth_token_timeout_min",
AuthJsonQuery = Web.Contents(auth_url,
[Headers = [#"Accept"="application/json"],
Content = Text.ToBinary(post_data)]),
auth_token = Json.Document(AuthJsonQuery),
// get data
proj_url = #"base_url" & "/api/project/active?api-token=" & #"api_token" & "&authentication-token=" & auth_token,
GetJsonQuery = Web.Contents(proj_url,
[Headers = [#"Accept"="application/json"]]),
FormatAsJsonQuery = Json.Document(GetJsonQuery),
in
#"FormatAsJsonQuery"
This script gives the following error:
DataSource.Error: Web.Contents failed to get contents from 'https://example.com/api/authenticate' (415): Unsupported Media Type
Details:
DataSourceKind=Web
DataSourcePath=https://example.com/api/authenticate
Url=https://example.com/api/authenticate
The raw response from the first POST request is exactly the authentication token and I am missing something in correctly decoding it.
If I manually configure the authentication token directly in proj_url then I am able to get the data, so I am doing something wrong with getting the authentication token.
UPDATE:
My goal was to somehow make the data available inside Power BI. I took an alternative approach with using R as a data source. (You will need to install R and the httr package in R for this.) Posting that solution here:
library(httr)
base_url <- 'https://example.com'
user_name <- 'john_doe'
password <- 'pwws0rd'
api_token <- '4r-eueyta-dh7cuq-26'
timeout_min <- 1
# get authentication token
auth_url <- paste0(base_url, '/api/authenticate')
data <- list('Username' = user_name,
'Password' = password,
'ApiToken' = api_token,
'AuthorizationTokenTimeoutMinutes' = timeout_min)
resp <- POST(auth_url,
body = data,
encode = 'json')
auth_token <- content(resp, "parsed", "application/json")
# get data
proj_url <- paste0(base_url, '/api/project/active?',
'api-token=', api_token,
'&authentication-token=', auth_token)
resp <- GET(proj_url, encode = 'json')
# convert the response to data frame
results <- content(resp, "parsed", "application/json")
results <- lapply(results, function(x) {
x[sapply(x, is.null)] <- NA
unlist(x)})
projects <- do.call("rbind", results) # matrix
projects <- data.frame(projects, stringsAsFactors = FALSE)
projects is then available for importing into Power BI.

I know it is too late to answer, but I'm guessing it is because the header lacks "Content-Type=application/x-www-form-urlencoded".
Python requests library automatically adds this header part, while Power Query does not.

Related

Replicate Python requests.post in R using httr POST

I am trying to adapt my HTTP request from running it in Python to R. This is the post request in Python:
import requests
import json
r = requests.post("https://feed-dev.ihsmarkit.com/apikey",
data={'username': 'markit/resellers/API_OPS/accounts/demo.dv', 'password':
'Example#N6'})
print("POST /apikey", r.status_code, r.reason)
apikey = r.text
print(apikey)
I did some research and found the httr package in R is best for dealing with API related requests. However I tried to use the POST() function in a few attempts but got the same error 400 ("MISSING_PARAMETER": Parameter username not provided.") responses. Here are a few attempts I used:
#attempt 1
response <- POST(url = "https://feed-dev.ihsmarkit.com/apikey",
add_headers(.headers = c("Content-Type"="application/x-www-form-urlencoded")),
authenticate('markit/resellers/API_OPS/accounts/demo.dv', 'Example#N6')
)
#attempt 2
request_body <- data.frame(
username = 'markit/resellers/API_OPS/accounts/demo.dv',
password = 'Example#N6'
)
request_body_json <- toJSON(list(data = request_body), auto_unbox = TRUE)
POST(url = "https://feed-dev.ihsmarkit.com/apikey",
add_headers(.headers = c("Content-Type"="application/x-www-form-urlencoded","Accept"="application/json"),
body = request_body_json))
#attempt 3
result <- POST(url = "https://feed-dev.ihsmarkit.com/apikey",
add_headers(.headers = c("Content-Type"="application/x-www-form-urlencoded","Accept"="application/json")),
body = '{"data":{"username":"markit/resellers/API_OPS/accounts/demo.dv", "password":"Example#N6}}',
encode = 'raw')
Do you know how should I properly convert my request?
Use
response <- POST(url = "https://feed-dev.ihsmarkit.com/apikey",
encode = "form",
body= list(
username ='markit/resellers/API_OPS/accounts/demo.dv',
password = 'Example#N6')
)
Just pass your data as a list and the POST will take care of formatting it as form data when you choose encode="form". Your python code doesn't seem to use JSON at all. You just have literal dictionary values where you are storing your data. Only use authenticate() when the HTTP endpoint requires basic HTTP authentication. For endpoints that require a username/password in the body of the message, that's not how basic HTTP authentication works.

Issue pulling data from API - (Paylocity)

I am trying to fetch data from API from a tool called Paylocity.
I have managed to generate the access token and trying to connect to their API as below
client_id = client_id
client_secret = client_secret
company_id = company_id
prod_auth_url = 'https://apisandbox.paylocity.com/IdentityServer/connect/token'
body_params = urllib.parse.urlencode({'grant_type': 'client_credentials','scope':'WebLinkAPI'})
# Requests can use auth= for basic authentication
auth_response = requests.post(prod_auth_url,auth=(client_id, client_secret), data=urllib.parse.urlencode(body_params))
response = json.loads(auth_response.content)
api_call_headers = {'Authorization': 'Bearer ' + response['access']}
This however returns an error
TypeError: not a valid non-string sequence or mapping object
Figured out by changing the ** auth_response** variable as below
auth_response = requests.post(prod_auth_url,auth=(client_id, client_secret), data=body_params)
response = json.loads(auth_response.text)
auth_response.content gives you a byte string. Which likely causes the error. Use auth_response.text.
Better even: You don't need json.loads() here.
Just do:
response = auth_response.json()

Python HTTPS-requests using azure AAD authentication token. Or python version of CreateAuthorizationHeader()

and thank you for you useful help already.
I am trying to make an API call using python. Sadly, the only documentation of the API is an implementation already existing in C#.
My problem is, that after i acquire an Azure AADTokenCredential object - i simply do not know how to use it in my HTTPS request.
def get_data_from_api(credentials):
serialNumber = "123456789"
fromDate = "01/10/2019 00:00:00" # DD/m/YYYY HH:MM:SS
untilDate = "09/10/2019 00:00:00" # DD/m/YYYY HH:MM:SS
PARAMS = {
serialNumber: serialNumber,
fromDate: fromDate,
untilDate: untilDate
}
url = "https://myapi.azurewebsites.net/api/sensordata/GetBySerialNumber"
r = requests.get(url = url, header={"Authorization": credentials}, params=PARAMS)
print(r)
#data = r.json()
return data
The credentials is an msrestazure.azure_active_directory.AADTokenCredentials retrieved using the adal package.
The above code results in an error as the header object can only be strings.
My question is - How do i pass the authorization object in the correct way?
The C# implementation looks like this:
// Make a request to get the token from AAD
AuthenticationResult result = Task.Run(async () => await authContext.AcquireTokenAsync(resource, cc)).Result;
// Get the auth header which includes the token from the result
string authHeader = result.CreateAuthorizationHeader();
// ...
// Prepare a HTTP request for getting data.
// First create a client
HttpClient client = new HttpClient();
// Create the actual request. It is a GET request so pass the arguments in the url is enough
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Get, $"https://api.azurewebsites.net/api/sensordata/GetBySerialNumber?serialNumber={serialNumber}&from={fromDate}&until={untilDate}");
// Add the required authorization header that includes the token. Without it the request will fail as unauthorized
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
// Prepare the response object
HttpResponseMessage response = Task.Run(async () => await client.SendAsync(request)).Result;
So yes! I finally solved it.
My problem was that i was passing on the ADAL object to the requests phase, however what I needed to do was pass on the actual token that is retrieved using: 'credentials = context.acquire_token_with_client_credentials(resource_uri,client_id,client_secret)'.
Here credentials is a dictionary and what the requests needs in the header for authentication was:
header = {
"Authorization": "Bearer "+credentials["accessToken"]
}
r = requests.get(url=url, headers=header, params=PARAMS)
passing this on to the requests.get method worked!

Can't upload files using OneDrive API for Business

I'm trying to upload a file to OneDrive of an MS SharePoint using Python API. I've succeeded in authentication, but I can't get any service_info returned.
I've already looked here and here. I'm experiencing the exact same issues describe there. I've modified my code accordingly, but nothing seems to work.
Here's my complete code.
import onedrivesdk
from onedrivesdk.helpers import GetAuthCodeServer
from onedrivesdk.helpers.resource_discovery import ResourceDiscoveryRequest, ServiceInfo
from onedrivesdk.helpers import http_provider_with_proxy
redirect_uri = 'http://localhost:8080'
client_id='xxxxxxx'
client_secret = 'xxxxxxx'
discovery_uri = 'https://api.office.com/discovery/'
auth_server_url='https://login.microsoftonline.com/common/oauth2/authorize'
auth_token_url='https://login.microsoftonline.com/common/oauth2/token'
proxy = {
'http': 'xxx',
'https': 'xxx'
}
import json
import requests
class AnyVersionResourceDiscoveryRequest(ResourceDiscoveryRequest):
def get_all_service_info(self, access_token, sharepoint_base_url):
headers = {'Authorization': 'Bearer ' + access_token}
response = json.loads(requests.get(self._discovery_service_url,
headers=headers).text)
service_info_list = [ServiceInfo(x) for x in response['value']]
# Get all services, not just the ones with service_api_version 'v2.0'
# Filter only on service_resource_id
sharepoint_services = \
[si for si in service_info_list
if si.service_resource_id == sharepoint_base_url]
return sharepoint_services
sharepoint_base_url = 'https://xxx.sharepoint.com/'
# http = onedrivesdk.HttpProvider()
http = http_provider_with_proxy.HttpProviderWithProxy(proxy, verify_ssl=True)
auth = onedrivesdk.AuthProvider(http,
client_id,
auth_server_url=auth_server_url,
auth_token_url=auth_token_url)
auth_url = auth.get_auth_url(redirect_uri)
code = GetAuthCodeServer.get_auth_code(auth_url, redirect_uri)
auth.authenticate(code, redirect_uri, client_secret, resource=discovery_uri)
# If you have access to more than one service, you'll need to decide
# which ServiceInfo to use instead of just using the first one, as below.
# service_info = ResourceDiscoveryRequest().get_service_info(auth.access_token)[0]
service_info = AnyVersionResourceDiscoveryRequest().get_all_service_info(auth.access_token, sharepoint_base_url)[0]
auth.redeem_refresh_token(service_info.service_resource_id)
client = onedrivesdk.OneDriveClient(service_info.service_resource_id + '/_api/v2.0/', auth, http)
I'm stuck at the 3rd line from the bottom. What am I doing wrong here?

API access to trading platform using Python

I'm new to getting data using API and Python. I want to pull data from my trading platform. They've provided the following instructions:
http://www.questrade.com/api/documentation/getting-started
I'm ok up to step 4 and have an access token. I need help with step 5. How do I translate this request:
GET /v1/accounts HTTP/1.1
Host: https://api01.iq.questrade.com
Authorization: Bearer C3lTUKuNQrAAmSD/TPjuV/HI7aNrAwDp
into Python code? I've tried
import requests
r = requests.get('https://api01.iq.questrade.com/v1/accounts', headers={'Authorization': 'access_token myToken'})
I tried that after reading this: python request with authentication (access_token)
Any help would be appreciated. Thanks.
As you point out, after step 4 you should have received an access token as follows:
{
“access_token”: ”C3lTUKuNQrAAmSD/TPjuV/HI7aNrAwDp”,
“token_type”: ”Bearer”,
“expires_in”: 300,
“refresh_token”: ”aSBe7wAAdx88QTbwut0tiu3SYic3ox8F”,
“api_server”: ”https://api01.iq.questrade.com”
}
To make subsequent API calls, you will need to construct your URI as follows:
uri = [api_server]/v1/[rest_operation]
e.g.
uri = "https://api01.iq.questrade.com/v1/time"
Note: Make sure you use the same [api_server] that you received in your json object from step 4, otherwise your calls will not work with the given access_token
Next, construct your headers as follows:
headers = {'Authorization': [token_type] + ' ' + [access_token]}
e.g.
headers = {'Authorization': 'Bearer C3lTUKuNQrAAmSD/TPjuV/HI7aNrAwDp'}
Finally, make your requests call as follows
r = requests.get(uri, headers=headers)
response = r.json()
Hope this helps!
Note: You can find a Questrade API Python wrapper on GitHub which handles all of the above for you.
https://github.com/pcinat/QuestradeAPI_PythonWrapper
Improving a bit on Peter's reply (Thank you Peter!)
start by using the token you got from the QT website to obtain an access_token and get an api_server assigned to handle your requests.
# replace XXXXXXXX with the token given to you in your questrade account
import requests
r = requests.get('https://login.questrade.com/oauth2/token?grant_type=refresh_token&refresh_token=XXXXXXXX')
access_token = str(r.json()['access_token'])
refresh_token= str(r.json()['refresh_token']) # you will need this refresh_token to obtain another access_token when it expires
api_server= str(r.json()['api_server'])
token_type= str(r.json()['token_type'])
api_server= str(r.json()['api_server'])
expires_in = str(r.json()['expires_in'])
# uri = api_server+'v1/'+[action] - let's try checking the server's time:
uri = api_server+'v1/'+'time'
headers = {'Authorization': token_type +' '+access_token}
# will look sth like this
# headers will look sth like {'Authorization': 'Bearer ix7rAhcXx83judEVUa8egpK2JqhPD2_z0'}
# uri will look sth like 'https://api05.iq.questrade.com/v1/time'
# you can test now with
r = requests.get(uri, headers=headers)
response = r.json()
print(response)

Categories

Resources