I'm trying to rename a ArangoDB collection using pyArango. This is what I have so far:
connection = pyArango.Connection('http://random-address', username='random-username', password='random-password')
test_db = Database(connection, 'test-db')
collection = test_db["new"]
collection.action("PUT", "rename", name="newname")
The code fails in line 4:
{'error': True, 'code': 400, 'errorNum': 1208, 'errorMessage': 'name
must be non-empty'}
I'm probably using the action method incorrectly but the documentation does not provide any examples. Anybody got an idea?
A JSON object {"name": "newname"} needs to be passed as request body. The new name can not be passed as URL path parameter. The problem is the implementation of collection.action():
def action(self, method, action, **params) :
"a generic fct for interacting everything that doesn't have an assigned fct"
fct = getattr(self.connection.session, method.lower())
r = fct(self.URL + "/" + action, params = params)
return r.json()
The keyword arguments end up as dict called params. This object is passed to the request function fct() as named parameter params. This parameter receives the dict and converts it to URL path parameters, e.g. ?name=newname which is not supported by the HTTP API of the server.
There is unfortunately no way to pass a payload via action(). You can write some custom code however:
from pyArango.connection import *
connection = Connection('http://localhost:8529', username='root', password='')
try:
connection.createDatabase('test-db')
except CreationError:
pass
test_db = Database(connection, 'test-db')
try:
test_db.createCollection(name='new')
except CreationError:
pass
collection = test_db['new']
r = connection.session.put(collection.URL + '/rename', data='{"name":"newname"}')
print(r.text)
collection = test_db['newname']
You can also use a dict for the payload and transform it to JSON if you want:
import json
...put(..., data=json.dumps({"name": "newname"}))
I've fixed it like this:
def rename_collection(arango_uri, username, password, database, collection, new_name):
url = '{}/_db/{}/_api/collection/{}/rename'.format(arango_uri, database, collection)
params = {"name": new_name}
response = requests.put(url, data=json.dumps(params), auth=HTTPBasicAuth(username, password))
return response
Related
I am trying to set the return value of a get request in python in order to do a unit test, which tests if the post request is called with the correct arguments. Assume I have the following code to test
# main.py
import requests
from django.contrib.auth.models import User
def function_with_get():
client = requests.session()
some_data = str(client.get('https://cool_site.com').content)
return some_data
def function_to_test(data):
for user in User.objects.all():
if user.username in data:
post_data = dict(data=user.username)
else:
post_data = dict(data='Not found')
client.post('https://not_cool_site.com', data=post_data)
#test.py
from unittest import mock
from unittest import TestCase
from main import function_with_get, function_to_test
class Testing(TestCase):
#mock.patch('main.requests.session')
def test_which_fails_because_of_get(self, mock_sess):
mock_sess.get[0].return_value.content = 'User1'
data = function_with_get()
function_to_test(data)
assertIn('Not Found', mock_sess.retrun_value.post.call_args_list[1])
This, sadly, does not work and I have also tried to set it without content, however, I get an error AttributeError: 'str' object has no attribute 'content'
What would be the correct way to set the return_value of the get request, so that I can test the arguments of the post request?
I think you almost have it, except you're missing the return value for session() - because session is instantiated to create the client instance. I think you can drop the [0] too.
Try:
mock_sess.return_value.get.return_value.content = 'User1'
Try with .text because this should work for strings.
s = requests.Session()
s.get('https://httpbin.org/cookies/ set/sessioncookie/123456789')
r = s.get('https://httpbin.org/ cookies')
print(r.text)
http://docs.python-requests.org/en/master/user/advanced/
I'm writing a unit test to check to see if the telecasts key is in the JSON data returned in this function (in my views.py):
def my_function(request, date1='', date2='', date3='', date4=''):
::some other functions...::
return HttpResponse(data, content_type='application/json')
As you see, the JSON I want to check is sent via a HttpResponse as the variable data
This JSON data, when received on the front-end, is structured like:
{"records": [ {"program": "WWE ENTERTAINMENT", "telecasts": 201,...}, {..} ]
So this is how I'm trying to write the unit test, but I'm getting an error when I run it:
def my_test(self):
"""Data returned has telecasts key"""
request = self.client.get(
'/apps/myapp/2014-08-01/2015-06-10/2016-01-13/2016-03-23/',
{dictionary of optional parameters}
)
force_authenticate(request, user=self.user)
response = my_function(
request,
'2014-08-01',
'2015-06-10',
'2016-01-13',
'2016-03-23'
)
telecasts_check = response['records'][0]['telecasts']
self.assertRaises(KeyError, lambda: telecasts_check)
self.client.get makes the request and returns the response so it is totally unnecessary to call myfunction directly to get the response down below.
Another thing is, HttpResponse has a property named content which can be a bytestring or an iterator that stores the response content.
In your case, you can convert that to a dictionary using json.loads and access any values just like you already are doing:
import json
def my_test(self):
...
response = self.client.get(...)
result = json.loads(response.content)
telecasts_check = result['records'][0]['telecasts']
...
I have to test out a certain view that gets certain information from request.args.
I can't mock this since a lot of stuff in the view uses the request object.
The only alternative I can think of is to manually set request.args.
I can do that with the test_request_context(), e.g:
with self.app.test_request_context() as req:
req.request.args = {'code': 'mocked access token'}
MyView()
Now the request inside this view will have the arguments that I've set.
However I need to call my view, not just initialize it, so I use this:
with self.app.test_client() as c:
resp = c.get('/myview')
But I don't know how to manipulate the request arguments in this manner.
I have tried this:
with self.app.test_client() as c:
with self.app.test_request_context() as req:
req.request.args = {'code': 'mocked access token'}
resp = c.get('/myview')
but this does not set request.args.
Pass the query_string argument to c.get, which can either be a dict, a MultiDict, or an already encoded string.
with app.test_client() as c:
r = c.get('/', query_string={'name': 'davidism'})
The test client request methods pass their arguments to Werkzeug's EnvironBuilder, which is where this is documented.
I already have a REST API with GAE python built using webapp2. I was looking at protorpc messages used in protorpc and Cloud Enpoints and really like how I can define the request and responses. Is there a way to incorporate that into my webapp2 handlers?
Firstly I use a decorator on the webapp2 method. I define the decorator as follows*:
# Takes webapp2 request (self.request on baseHandler) and converts to defined protoRpc object
def jsonMethod(requestType, responseType, http_method='GET'):
"""
NB: if both URL and POST parameters are used, do not use 'required=True' values in the protorpc Message definition
as this will fail on one of the sets of parms
"""
def jsonMethodHandler(handler):
def jsonMethodInner(self, **kwargs):
requestObject = getJsonRequestObject(self, requestType, http_method, kwargs)
logging.info(u'request={0}'.format(requestObject))
response = handler(self, requestObject) # Response object
if response:
# Convert response to Json
responseJson = protojson.encode_message(response)
else:
responseJson = '{}'
logging.info(u'response json={0}'.format(responseJson))
if self.response.headers:
self.response.headers['Content-Type'] = 'application/json'
if responseJson:
self.response.write(responseJson)
self.response.write('')
return jsonMethodInner
return jsonMethodHandler
The jsonMethod decorator uses a protorpc message for 'requestType' and 'responseType'.
I have constrained the http_method to be either GET, POST or DELETE for a method; you may wish to change this.
Note that this decorator must be applied to instance methods on a webapp2.RequestHandler class (see the example below) as it needs to access the webapp2 request and response objects.
The protorpc message is populated in getJsonRequestObject():
def getJsonRequestObject(self, requestType, http_method, kwargs):
"kwargs: URL keywords eg: /api/test/<key:\d+> => key"
"request.GET: used for 'GET' URL query string arguments"
"request.body: used for 'POST' or 'DELETE' form fields"
logging.info(u'URL parameters: {0}'.format(kwargs))
if http_method == 'POST' or http_method == 'DELETE':
requestJson = self.request.body
if requestJson == None:
requestJson = '' # Cater for no body (eg: IE10)
try:
logging.info("Content type = {}".format(self.request.content_type))
logRequest = requestJson if len(requestJson) < 1024 else requestJson[0:1024]
try:
logging.info(u'Request JSON: {0}'.format(logRequest))
except:
logging.info("Cannot log request JSON (invalid character?)")
postRequestObject = protojson.decode_message(requestType, requestJson)
except:
logError()
raise
if self.request.query_string:
# combine POST and GET parameters [GET query string overrides POST field]
getRequestObject = protourlencode.decode_message(requestType, self.request.query_string)
requestObject = combineRequestObjects(requestType, getRequestObject, postRequestObject)
else:
requestObject = postRequestObject
elif http_method == 'GET':
logging.info(u'Query strings: {0}'.format(self.request.query_string))
requestObject = protourlencode.decode_message(requestType, self.request.query_string)
logging.info(u'Request object: {0}'.format(requestObject))
else:
raise ValidationException(u'Invalid HTTP method: {0}'.format(http_method))
if len(kwargs) > 0:
#Combine URL keywords (kwargs) with requestObject
queryString = urllib.urlencode(kwargs)
keywordRequestObject = protourlencode.decode_message(requestType, queryString)
requestObject = combineRequestObjects(requestType, requestObject, keywordRequestObject)
return requestObject
getJsonRequestObject() handles GET, POST and webapp2 URL arguments (note: these are entered as kwargs).
combineRequestObjects() combines two objects of the requestType message:
def combineRequestObjects(requestType, request1, request2):
"""
Combines two objects of requestType; Note that request2 values will override request1 values if duplicated
"""
members = inspect.getmembers(requestType, lambda a:not(inspect.isroutine(a)))
members = [m for m in members if not m[0].startswith('_')]
for key, value in members:
val = getattr(request2, key)
if val:
setattr(request1, key, val)
return request1
Finally, a decorated webapp2 method example:
from protorpc import messages, message_types
class FileSearchRequest(messages.Message):
"""
A combination of file metadata and file information
"""
filename = messages.StringField(1)
startDateTime = message_types.DateTimeField(2)
endDateTime = message_types.DateTimeField(3)
class ListResponse(messages.Message):
"""
List of strings response
"""
items = messages.StringField(1, repeated=True)
...
class FileHandler(webapp2.RequestHandler):
#jsonMethod(FileSearchRequest, ListResponse, http_method='POST')
def searchFiles(self, request):
# Can now use request.filename etc
...
return ListResponse(items=items)
Hopefully this will give you some idea of how to go about implementing your own webapp2/protorpc framework.
You can also check and see how Cloud Endpoints is implementing their protorpc message handling. You may also need to dive into the protorpc code itself.
Please note that I have attempted to simplify my existing implementation, so you may come across various issues that you will need to address in your implementation.
In addition methods like 'logError()' and classes like 'ValidationException' are non-standard, so you will need to replace them as you see fit.
You may also wish to remove the logging at some point.
I have a WS (ZOPE/PLONE) that accept some XMLRPC calls.
So, I write a python snippet of code for do a call to WS and do something.
I follow messagge format that I found here, and that's my snippet of code:
import httplib
def queryInventory():
try:
xmlrpc_envelope = '<?xml version="1.0"?>'\
'<methodCall>'\
'<methodName>easyram</methodName>'\
'<params>'\
'<param>'\
'<value>%s</value>'\
'</param>'\
'</params>'\
'</methodCall>'
params = '<EasyRAM>'\
'<authentication><user>EasyRAM</user><pwd>EasyRAM</pwd><hotel>52</hotel></authentication>'\
'<operation type="QueryInventory" rate="master"><date from="2012-03-10" to="2012-03-10" /><date from="2012-03-22" to="2012-03-22" /></operation>'\
'</EasyRAM>'
data = xmlrpc_envelope % params
print data
headers = {"Content-type": "text/xml"}
conn = httplib.HTTPSConnection('myHost')
aa = '/ws/xmlrpc/public/EasyRAM'
conn.request("POST", aa, data, headers)
response = conn.getresponse()
print "EasyRAM.queryInventory() response: status=%s, reason=%s" % (response.status, response.reason)
print "EasyRAM.queryInventory() response=%s" % response.read()
conn.close()
except Exception, ss:
print "EasyRAM.queryInventory() -> Error=%s" % ss
raise
return ''
queryInventory()
The problem is that i receive the following error message:
Invalid request The parameter, params , was omitted from the request. Make sure to specify all required parameters, and try the request again.
Like the parameter isn't passed.
If I modify my snippet by wrapping my parameter (called params) into <string></string> in that way:
xmlrpc_envelope = '<?xml version="1.0"?>'\
'<methodCall>'\
'<methodName>easyram</methodName>'\
'<params>'\
'<param>'\
'<value><string>%s</string></value>'\
'</param>'\
'</params>'\
'</methodCall>'
something happen, but isn't what I want; in fact my parameter result to be empty (or void, if you like).
Any ideas or suggestions?
PS.: I know that exists an xml-rpc library for python called xmlrpclib, but I have to develop in that way, because this is an example for client that can't use directly a library like that
I just resolved.
If I add a function like this:
def escape(s, replace=string.replace):
s = replace(s, "&", "&")
s = replace(s, "<", "<")
return replace(s, ">", ">",)
and before calling the connection method I do something like:
params = escape(params)
Then all goes well.
Hope that could be useful for future purposes