submit request (post) internally in python-eve - python

I have a resource in eve e.g. ABC, I want to manipulate another resource e.g. BCD when some condition meet while I am posting a new item to ABC, I know I can hook the event for post/pre_POST_ABC but is there a 'internal' way to do post on BCD without going through via the HTTP again?

In your callback function you could either:
A) use the data driver to store data directly to the database
Something like this:
def update_ABC(request, payload):
accounts = app.data.driver.db['abc_collection']
account = accounts.insert(docs)
app = Eve()
app.on_post_POST_ABC += update_ABC
app.run()
Would do the trick. You would be bypassing the framework this way, and storing directly on the database.
B) Use app.test_client.post() to POST through directly through the application.
app.test_client().post('/bcd', json.dumps({"field":"value"}, content_type='application_json'))
This is probably a better option since the request goes through the framework (meta fields like data_created are handled for you.)
Update: With v0.5+ you can now use post_internal to achieve the same result. There are equivalent internal methods available for other CRUD methods as well.

Related

Python Eve Conditional / Bulk Delete

From Nicola's SO answer here and my own testing it seems clear that Eve does not support conditional deletes at resource endpoints.
I know I could use a GET: "where={...}" request to the _ids and _etags of the documents I would like to delete, and then send out a series of requests at each item endpoint to delete them with the If-Match header appropriately set to each item's _etag:
for each item:
DELETE: http://localhost:5000/items/<item._id>
...but I would like to avoid sending out multiple HTTP requests if possible.
One solution may be predefined database filters, but these would be static filters where I'd like to dynamically filter deletion depending on some URL parameters. Pre-event hooks may be the solution I'm seeking.
Does Eve support bulk deletion? And if not, what is the recommended way to extend Eve's functionality to provide conditional and/or bulk deletes?
I added a pre-event hook to DELETE, and this seems to be working with the tests I've run so far:
def add_delete_filters(resource, request, lookup):
if 'where' in request.args:
conditions = request.args.getlist('where')
for cond_str in conditions:
cond = json.loads(cond_str)
for attrib in cond:
lookup[attrib] = cond[attrib]
app = Eve()
app.on_pre_DELETE += add_delete_filters

Python-Eve: Use Pre-Request Event Hooks to modify data before inserting to DB

I am adding new data into my Database by doing a POST-request on my eve-API.
Since there need to be added some data from the Python side I thought I could add these data by using a pre-request event hook.
So is there a way to modify the data contained in the POST-request using a pre-request hook before inserting the data into the database? I already understood how to implement such a hook but do not have any clue about how to modify data before inserting to DB.
You probably want to look at database hooks, specifically at insert hooks:
When a POST requests hits the API and new items are about to be stored in the database, these vents are fired:
on_insert for every resource endpoint.
on_insert_<resource_name> for the specific resource endpoint.
Callback functions could hook into these events to arbitrarily add new fields or edit existing ones.
In the code below:
def before_insert(resource_name, documents):
if resource_name == 'myresource':
for document in documents:
document['field'] = 'value'
app = Eve()
app.on_insert += before_insert
app.run()
Every time a POST hits the API the before_insert function is invoked. The function updates field1 for every document. Since this callback is invoked before the payload is sent to the database, changes will be persisted to the database.
An interesting alternative would be:
def before_insert(resource_name, documents):
for document in documents:
document['field'] = 'value'
app = Eve()
app.on_insert_myresource += before_insert
app.run()
In the callback we are not testing the endpoint name anymore. This is because we hooked our callback to the on_insert_myresoure event so the function will only be called when POST request are performed on the myresource endpoint. Better separation of concerns, code is simpler and also, improved performance since the callback is not going to be hit an all API inserts. Side note, eventually you can hook multiple callbacks to the same event (hence the use of the addition operator +=).
In my case I wanted to duplicate documents if a given property is in data.
I have to use pre_POST event hook to do that.
def pre_notifications(request):
data = json.loads(request.get_data())
if 'payload' in data and 'condition' in data:
notification = data['payload']
documents = []
users = app.data.pymongo().db.users.find()
for user in users:
copy_notification = copy(notification)
copy_notification['user_email'] = user['user_email']
documents.append(copy_notification)
request._cached_data = json.dumps(documents).encode('utf-8')
First, I tried to replace request.data but it does not work. Doing some search on code I figured out the _cached_data property. Then it works.
Just to complement the answer of #Gustavo (I cannot leave a comment in his answer). You can update the request._cached_json property without serializing your data.
Using his example:
def pre_notifications(request):
data = json.loads(request.get_data())
if 'payload' in data and 'condition' in data:
notification = data['payload']
documents = []
users = app.data.pymongo().db.users.find()
for user in users:
copy_notification = copy(notification)
copy_notification['user_email'] = user['user_email']
documents.append(copy_notification)
request._cached_json = documents

Python Eve contains filter

There's some way to return items that field contains some value? Eg.
GET /people?contains="foo"
Return all persons that have the word 'foo' in the name.
Thanks in advance
You could use mongodb $regex operator, which is blacklisted by default in Eve (MONGO_QUERY_BLACKLIST = ['$where', '$regex']).
Add MONGO_QUERY_BLACKLIST = ['$where'] to your settings.py. Then you can query your API like this:
?where={"name": {"$regex": ".*foo.*"}}.
Be careful however. If you don't control the client, enabling regexes could potentially increase your API vulnerability.
I am not conversant with Eve myself. But looking at it's webpage seems like it exposes all of Flask's functionalities.
You need to be looking at this page in the documentation that talks about accessing the request data.
In your Flask app, define a method that accepts both POST and GET requests and then you can access foo by doing request.args.get('contains', '').
This is what I mean:
#app.route('/people', methods=['POST', 'GET'])
def get_people():
search_key = request.args.get('contains', '')
#search for people containing 'foo' in your DB
Hope this gives you a starting point as to how to go about things.

Python Werkzeug: modify Request values (forms and args) prior to retrieval

Is there a way in Werkzeug to edit the request values (forms and args) before using it?
I need to encode the request values from utf8 to iso88591. I created a function to handle this.
I would like to use this function on all form values so that I avoid the second line of the following:
lcl_var = request.form['post_arg']
lcl_var = encode_utf8_to_iso88591(lcl_var)
I couldn't figure out what I needed from the Werkzeug docs. I imagine there's a way to subclass the Request class and override a one of its methods that handles the values. Would really appreciate a concrete example on how to implement this well.
A limited example exists on extending request parsing at the Werkzeug docs. It's a little buried, but it's sound.
http://werkzeug.pocoo.org/docs/request_data/#how-to-extend-parsing
Since Werkzeug is a pretty low-level tool over HTTP, this functionality could also be implemented in your request dispatcher (assuming a structure similar to the one in the Werkzeug tutorial, the function that applies the url map to the request.)
EDIT:
It seems that per the Werkzeug docs, the best way to do this is to process your own request property out of the stream. It'd be nice to do this is a way that preserves the immutability of the request.form property:
def encode(value):
#Your logic for the new dict vals
return 'foo!'
class MixInRequest(Request):
max_content_length = 1024 * 1024 * 4
#cached_property
def lcl_data(self):
if self.method in ['POST','PUT','PATCH']:
fields = dict([(key, encode(val)) for (key,val) in self.form.items()])
return ImmutableMultiDict(fields)
return None
This sets a request property lcl_data (named after your function) that will parse on first access and cache for subsequent calls. It only functions for methods that would populate request.form.
Full example here:
https://gist.github.com/DeaconDesperado/7292574

Flask : understanding POST method to transmit data

my question is quite hard to describe, so I will focus on explaining the situation. So let's say I have 2 different entities, which may run on different machines. Let's call the first one Manager and the second one Generator. The manager is the only one which can be called via the user.
The manager has a method called getVM(scenario_Id), which takes the ID of a scenario as a parameter, and retrieve a BLOB from the database corresponding to the ID given as a parameter. This BLOB is actually a XML structure that I need to send to the Generator. Both have a Flask running.
On another machine, I have my generator with a generateVM() method, which will create a VM according to the XML structure it recieves. We will not talk about how the VM is created from the XML.
Currently I made this :
Manager
# This method will be called by the user
#app.route("/getVM/<int:scId>", methods=['GET'])
def getVM(scId):
xmlContent = db.getXML(scId) # So here is what I want to send
generatorAddr = sgAdd + "/generateVM" # sgAdd is declared in the Initialize() [IP of the Generator]
# Here how should I put my data ?
# How can I transmit xmlContent ?
testReturn = urlopen(generatorAddr).read()
return json.dumps(testReturn)
Generator
# This method will be called by the user
#app.route("/generateVM", methods=['POST'])
def generateVM():
# Retrieve the XML content...
return "Whatever"
So as you can see, I am stuck on how to transmit the data itself (the XML structure), and then how to treat it... So if you have any strategy, hint, tip, clue on how I should proceed, please feel free to answer. Maybe there are some things I do not really understand about Flask, so feel free to correct everything wrong I said.
Best regards and thank you
PS : Lines with routes are commented because they mess up the syntax coloration
unless i'm missing something couldn't you just transmit it in the body of a post request? Isn't that how your generateVM method is setup?
#app.route("/getVM/<int:scId>", methods=['GET'])
def getVM(scId):
xmlContent = db.getXML(scId)
generatorAddr = sgAdd + "/generateVM"
xml_str = some_method_to_generate_xml()
data_str = urllib.urlencode({'xml': xml_str})
urllib.urlopen(generatorAddr, data=data_str).read()
return json.dumps(testReturn)
http://docs.python.org/2/library/urllib.html#urllib.urlopen

Categories