I am trying to port the "Prepared Request" example from this link: https://docs.python-requests.org/en/latest/user/advanced/ using httpx AsnycClient.
from requests import Request, Session
s = Session()
req = Request('GET', url, data=data, headers=headers)
prepped = s.prepare_request(req)
# do something with prepped.body
prepped.body = 'Seriously, send exactly these bytes.'
# do something with prepped.headers
prepped.headers['Keep-Dead'] = 'parrot'
resp = s.send(prepped,
stream=stream,
verify=verify,
proxies=proxies,
cert=cert,
timeout=timeout
)
print(resp.status_code)
But I could not find any method to prepare the request in the httpx async lib.
In particular, it seems that "prepare_request" method is not implemented.
Can anyone tell me how do I prepare requests using AsyncClient?
Any hint is much appreciated.
One could use httpx.Request class to write an prepare_request factory.
import httpx
def prepare_request(method, url, **kwargs):
# a very simple factory
return httpx.Request(method, url, **kwargs)
request = prepare_request("GET", "https://www.google.com")
client = httpx.Client()
response = client.send(request)
print(response, response.num_bytes_downloaded )
used sync client for demonstration only
Related
I'm using the client.Client class from gremlin_python.driver to connect to AWS Neptune. See the following
def _prepare_request(method, url, *, data=None, params=None, headers=None, service='neptune-db'):
_ = requests.Session()
request = requests.Request(method=method, url=url, data=data, params=params, headers=headers)
credentials = Session().get_credentials()
frozen_creds = credentials.get_frozen_credentials()
req = AWSRequest(method=method, url=url, data=data, params=params, headers=headers)
SigV4Auth(frozen_creds, service, os.environ['AWS_REGION']).add_auth(req)
prepared_iam_req = req.prepare()
request.headers = dict(prepared_iam_req.headers)
return request.prepare()
# https
http_protocol = 'https'
uri = f'{http_protocol}://{self.host}:{self.port}/gremlin'
request = _prepare_request('GET', uri)
# wss
ws_url = 'wss://{}:{}/gremlin'.format(self.host, self.port)
ws_request = httpclient.HTTPRequest(ws_url, headers=dict(request.headers))
self.conn = client.Client(ws_request, 'g')
Now my question how can I used the client.Client object from above to get a traversal object "g".
There is a similar example at https://pypi.org/project/gremlinpython/#description showing this. But I can't use the DriverRemoteConnection in the above code.
>>> from gremlin_python.process.anonymous_traversal import traversal
>>> from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection
>>> g = traversal().withRemote(DriverRemoteConnection('ws://localhost:8182/gremlin','g'))
Adding an answer in case others find this discussion.
When working with Amazon Neptune using Python, there is a nice open source set of utilities located here that make it easy to create SigV4 signed requests and perform many other common tasks.
Several of the examples are also useful with any Gremlin Server and not just Neptune.
I have a python function that calls out to an API using the request function. I want to test a 200 path, and then test a 500 error. I can't seem to figure out how to do it when looking at the requests-mock documentation.
Here is what I want to test.
def get_sku_data(sku: str):
"""
Retrieve a single product from api
"""
uri = app.config["SKU_URI"]
url = f"{uri}/{sku}"
headers = {
"content-type": "application/json",
"cache-control": "no-cache",
"accept": "application/json",
}
try:
retry_times = 3
response = retry_session(retries=retry_times).get(url, headers=headers)
return response.json()
except ConnectionError as ex:
raise Exception(f"Could not connect to sku api at {uri}. {ex}.")
except requests.exceptions.RetryError:
raise Exception(f"Attempted to connect to {uri} {retry_times} times.")
def retry_session(
retries, backoff_factor=0.3, status_forcelist=(500, 502, 503, 504)
) -> requests.Session:
"""
Performs a retry
"""
session = requests.Session()
retry = Retry(
total=retries,
read=retries,
connect=retries,
backoff_factor=backoff_factor,
status_forcelist=status_forcelist,
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
Here is my test stub that I'm trying to get going
# These test use pytest https://pytest.readthedocs.io/en/latest/
# Note: client is a pytest fixture that is dependency injected from src/tests/conftest.py
import json
import pytest
from src.app import create_app
import requests
import requests_mock
#pytest.fixture
def app():
app = create_app()
return app
def test():
session = requests.Session()
adapter = requests_mock.Adapter()
session.mount("mock", adapter)
adapter.register_uri("GET", "mock://12", text="data")
resp = session.get("mock://12")
assert resp.status_code == 200
assert resp.text == "data"
I'm a bit new to python testing, so any help would be very much appreciated.
My perception of how it worked was wrong.
I register the URI I'm testing against and instead of calling session.get, I actually call the function under test.
How do you use Bitbucket's 2.0 API to decline a pull request via Python?
According to their documentaion, it should be something like:
import requests
kwargs = {
'username': MY_BITBUCKET_ACCOUNT,
'repo_slug': MY_BITBUCKET_REPO,
'pull_request_id': pull_request_id
}
url = 'https://api.bitbucket.org/2.0/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}/decline'.format(**kwargs)
headers = {'Content-Type': 'application/json'}
response = requests.post(url, auth=(USERNAME, PASSWORD), headers=headers)
However, this fails with response.text simply saying "Bad Request".
This similar code works for me with their other API endpoints, so I'm not sure why the decline method is failing.
What am I doing wrong?
You have to authenticate with Oath. I wrote a wrapper for making these requests. Here is a simple example that works. The only thing I couldn't figure out was how to add a reason it was declined. I ended up making a request before I declined the PR that added a comment on why it was declined.
import os
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session
class Bitbucket(object):
def __init__(self, client_id, client_secret, workplace, repo_slug):
self.workplace = workplace # username or company username
self.repo_slug = repo_slug
self.token_url = 'https://bitbucket.org/site/oauth2/access_token'
self.api_url = 'https://api.bitbucket.org/2.0/'
self.max_pages = 10
self.client = BackendApplicationClient(client_id=client_id)
self.oauth = OAuth2Session(client=self.client)
self.oauth.fetch_token(
token_url=self.token_url,
client_id=client_id,
client_secret=client_secret
)
def get_api_url(self, endpoint):
return f'{self.api_url}repositories/{self.workplace}/{self.repo_slug}/{endpoint}'
bitbucket = Bitbucket(os.environ['BITBUCKET_KEY'], os.environ['BITBUCKET_SECRET'], workplace='foo', repo_slug='bar')
pr_id = 1234
resp = bitbucket.oauth.post(f"{bitbucket.get_api_url('pullrequests')}/{pr_id}/decline")
if resp.status_code == 200:
print('Declined')
else:
print('Someting went wrong.')
I have a class called Client which uses the requests module to interact with a service. It has methods like:
def get_resource(self, url, headers):
response = requests.get(url, headers, auth=self.auth)
return response
Now I want to call some methods before and after each call to the requests module. Something like:
def get_resource(self, url, headers):
self.add_request_header(headers)
response = requests.get(url, headers, auth=self.auth)
self.process_response_headers()
return response
I'm having trouble finding a way to do this without having to rewrite all Client methods. The most straightforward way is to change the calls to the request module to calls to self and add the calls to the methods there.
def get_resource(self, url, headers):
response = self.__get(url, headers, auth=self.auth)
return response
def __get(self, headers, auth):
self.add_request_header(headers)
response = requests.get(url, headers, auth=self.auth)
self.process_response_headers()
return response
But this requires me to change all the call sites and duplicate functionality of the request module. I've tried to use decorators to add these method calls to the request module functions, but got stuck with how to pass in self to the decorator.
I'm sure there's an elegant way to do this in Python.
You can use monkey patching.
read this : Python: Monkeypatching a method of an object
import requests
def get(self, url, params=None, **kwargs):
self.add_request_header(self.headers)
response = requests.get(url, self.headers, auth=self.auth)
self.process_response_headers()
setattr(requests.Session, 'get', requests.Session.get)
s = requests.Session()
I think in this case decorators will not be as good as it sounds and OOP is a better approach to your problem. You could use a base class Client:
class Client(object):
def __init__(self, auth):
self.auth = auth
def add_request_header(self, headers):
pass
def process_response_headers(self):
pass
def get_resource(self, url, headers):
self.add_request_header(headers)
response = requests.get(url, headers, auth=self.auth)
self.process_response_headers()
return response
And create another subclasses with other implementations of add_request_header and/or process_response_headers so later you just need to instantiate the class that better suites your case
I know how to do it with a current URL e.g.
>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
>>> r = requests.get('http://httpbin.org/get', params=payload)
>>> print(r.url)
But what about if after you visit a URL, such as one with OAuth, for example
authorize_url = facebook.get_authorize_url(**params)
requests.get(authorized_url)
The URL will then direct to one such as https://localhost:5000/authorized?code=AQCvF. How do I get the code=AQCvF?
I could probably do something like, get the address of the current browser and then parse the URL, but is there a cleaner way?
Complete code is below:
index.j2
<p>Login with Facebook</p>
routes.py
app.add_route('/facebook-login', LoginHandler('index.j2'))
app.add_route('/authorized', AuthorizedHandler('index.j2'))
handlers.py
from rauth.service import OAuth2Service
import requests
import os
# rauth OAuth 2.0 service wrapper
graph_url = 'https://graph.facebook.com/'
facebook = OAuth2Service(name='facebook',
authorize_url='https://www.facebook.com/dialog/oauth',
access_token_url=graph_url + 'oauth/access_token',
client_id=FB_CLIENT_ID,
client_secret=FB_CLIENT_SECRET,
base_url=graph_url)
class AuthorizedHandler(TemplateHandler):
def on_get(self, req, res):
code = self.requests.get['code']
data = dict(code=code, redirect_uri=REDIRECT_URI)
session = facebook.get_auth_session(data=data)
# response
me = session.get('me').json()
print('me', me)
UserController.create(me['username'], me['id'])
class LoginHandler(TemplateHandler):
async def on_get(self, req, res):
# visit URL and client authorizes
params = {'response_type': 'code',
'redirect_uri': REDIRECT_URI}
webbrowser.open(facebook.get_authorize_url(**params))
response = requests.get(facebook.get_authorize_url(**params))
print(response.url)
You can get the .url attribute from the Response object - this would be the final response URL:
response = requests.get(authorized_url)
print(response.url)
Then, you can urlparse the url to extract the GET parameters:
In [1]: from urllib.parse import parse_qs, urlparse
In [2]: url = "https://localhost:5000/authorized?code=AQCvF"
In [3]: parse_qs(urlparse(url).query)
Out[3]: {'code': ['AQCvF']}
Your code would work fine if you were using a synchronous Python framework, but it appears you are using an async framework, as implied by the async def on_get(self, req, res).
You will either have to write an async HTTP request function, use aiohttp.web, or your framework might have one built in and you can replace requests.get(facebook.get_authorize_url(**params)) with res.redirect(facebook.get_authorize_url(**params)).