can't search eve rest API - additional lookup not working - python

I was following the example here: https://github.com/pyeve/eve-demo/blob/master/settings.py
When I go to localhost:5000/apps, I can see all the documents in my collection, but when I search for an email at localhost:5000/apps/example#gmail.com, it says '404 not found'.
I've confirmed the regex, and the email addresses are in the documents. Can anyone see what might be wrong?
run.py
from eve import Eve
if __name__ == '__main__':
app = Eve()
app.run()
settings.py:
RESOURCE_METHODS = ['GET', 'POST', 'DELETE']
ITEM_METHODS = ['GET', 'PATCH', 'PUT', 'DELETE']
MONGO_HOST = 'localhost'
MONGO_PORT = 27017
MONGO_DBNAME = 'test_database'
apps = {
'item_title' : 'app',
'additional_lookup' : {
'url' : 'regex("\b[\w.-]+?#\w+?\.\w+?\b")',
'field' : 'developer_email',
},
'schema': {
'address' : {
'type' : 'string'
},
'developer_email' : {
'type' : 'string',
'minlength' : 1,
'maxlength' : 15,
'required' : True,
'unique' : True,
}
}
DOMAIN = {
'apps' : apps,
}

In your settings.py you aren't doing the lookup correctly. It should be.
apps = {
'item_title' : 'app',
'additional_lookup' : {
'url' : 'apps/regex("\b[\w.-]+?#\w+?\.\w+?\b")',
'field' : 'developer_email',
},
'schema': {
'address' : {
'type' : 'string'
},
'developer_email' : {
'type' : 'string',
'minlength' : 1,
'maxlength' : 15,
'required' : True,
'unique' : True,
}
}

You can't add more than one additional lookup to the same endpoint. What you can do however, is have multiple endpoints consuming the same datasource. By default standard item entry point is defined as /apps/'objectID'/. You will have to configure another endpoint to /apps/'new endpoint'.
Python-Eve: More than one additional lookup

#Vorticity pointed out a fix in a related question. Try the following:
'additional_lookup': {
'url': 'regex("[a-zA-Z0-9_.+-]+#[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")',
'field': 'developer_email'
}
You should be able to retrieve your item with or without url encoding eg:
localhost:5000/apps/example#gmail.com
localhost:5000/apps/example%40gmail.com
If you have any interest in making your items retrievable at the item level by email _only (not object id), you can use item_lookup_field together with item_url:
apps = {
...
'item_url': 'regex("[a-zA-Z0-9_.+-]+#[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")',
'item_lookup_field': 'developer_email'
}

Related

Eve: how to use different endpoints to access the same collection with different filters

I have an Eve app publishing a simple read-only (GET) interface. It is interfacing a MongoDB collection called centroids, which has documents like:
[
{
"name":"kachina chasmata",
"location":{
"type":"Point",
"coordinates":[-116.65,-32.6]
},
"body":"ariel"
},
{
"name":"hokusai",
"location":{
"type":"Point",
"coordinates":[16.65,57.84]
},
"body":"mercury"
},
{
"name":"cañas",
"location":{
"type":"Point",
"coordinates":[89.86,-31.188]
},
"body":"mars"
},
{
"name":"anseris cavus",
"location":{
"type":"Point",
"coordinates":[95.5,-29.708]
},
"body":"mars"
}
]
Currently, (Eve) settings declare a DOMAIN as follows:
crater = {
'hateoas': False,
'item_title': 'crater centroid',
'url': 'centroid/<regex("[\w]+"):body>/<regex("[\w ]+"):name>',
'datasource': {
'projection': {'name': 1, 'body': 1, 'location.coordinates': 1}
}
}
DOMAIN = {
'centroids': crater,
}
Which will successfully answer to requests of the form http://hostname/centroid/<body>/<name>. Inside MongoDB this represents a query like: db.centroids.find({body:<body>, name:<name>}).
What I would like to do also is to offer an endpoint for all the documents of a given body. I.e., a request to http://hostname/centroids/<body> would answer the list of all documents with body==<body>: db.centroids.find({body:<body>}).
How do I do that?
I gave a shot by including a list of rules to the DOMAIN key centroids (the name of the database collection) like below,
crater = {
...
}
body = {
'item_title': 'body craters',
'url': 'centroids/<regex("[\w]+"):body>'
}
DOMAIN = {
'centroids': [crater, body],
}
but didn't work...
AttributeError: 'list' object has no attribute 'setdefault'
Got it!
I was assuming the keys in the DOMAIN structure was directly related to the collection Eve was querying. That is true for the default settings, but it can be adjusted inside the resources datasource.
I figured that out while handling an analogous situation as that of the question: I wanted to have an endpoint hostname/bodies listing all the (unique) values for body in the centroids collection. To that, I needed to set an aggregation to it.
The following settings give me exactly that ;)
centroids = {
'item_title': 'centroid',
'url': 'centroid/<regex("[\w]+"):body>/<regex("[\w ]+"):name>',
'datasource': {
'source': 'centroids',
'projection': {'name': 1, 'body': 1, 'location.coordinates': 1}
}
}
bodies = {
'datasource': {
'source': 'centroids',
'aggregation': {
'pipeline': [
{"$group": {"_id": "$body"}},
]
},
}
}
DOMAIN = {
'centroids': centroids,
'bodies': bodies
}
The endpoint, for example, http://127.0.0.1:5000/centroid/mercury/hokusai give me the name, body, and coordinates of mercury/hokusai.
And the endpoint http://127.0.0.1:5000/bodies, the list of unique values for body in centroids.
Beautiful. Thumbs up to Eve!

Google DLP: "ValueError: Protocol message Value has no "stringValue" field."

I have a method where I build a table for multiple items for Google's DLP inspect API which can take either a ContentItem, or a table of values
Here is how the request is constructed:
def redact_text(text_list):
dlp = google.cloud.dlp.DlpServiceClient()
project = 'my-project'
parent = dlp.project_path(project)
items = build_item_table(text_list)
info_types = [{'name': 'EMAIL_ADDRESS'}, {'name': 'PHONE_NUMBER'}]
inspect_config = {
'min_likelihood': "LIKELIHOOD_UNSPECIFIED",
'include_quote': True,
'info_types': info_types
}
response = dlp.inspect_content(parent, inspect_config, items)
return response
def build_item_table(text_list):
rows = []
for item in text_list:
row = {"values": [{"stringValue": item}]}
rows.append(row)
table = {"table": {"headers": [{"name": "something"}], "rows": rows}}
return table
When I run this I get back the error ValueError: Protocol message Value has no "stringValue" field. Even though the this example and the docs say otherwise.
Is there something off in how I build the request?
Edit: Here's the output from build_item_table
{
'table':
{
'headers':
[
{'name': 'value'}
],
'rows':
[
{
'values':
[
{
'stringValue': 'My name is Jenny and my number is (555) 867-5309, you can also email me at anemail#gmail.com, another email you can reach me at is email#email.com. '
}
]
},
{
'values':
[
{
'stringValue': 'Jimbob Doe (555) 111-1233, that one place down the road some_email#yahoo.com'
}
]
}
]
}
}
Try string_value .... python uses the field names, not the type name.

Projection using MongoEngine Raw-Query

Does anyone know, how I can implement the following MongoDB query using a MongoEngine Raq-Query?
db.getCollection('subscribers').find({
'_id': ObjectId("579e60b0c525fd2037e8dd31"),
'history.content.read_process_msg': {
'$exists':true
},
'history.content.read_processed': {
'$exists':true
},
'history.content.read_processed': false
},
{'history.$':1})
I read, that the raw-query doesn't support projections and that one should use .only() instead. But the problem here is, that it returns all the empty documents also…
Any advice?
Edit: Here are my models and a sample document:
class Subscriber(Document):
service = StringField()
history = EmbeddedDocumentListField('SubscriberHistory')
def __str__(self):
return self.service
class SubscriberHistory(EmbeddedDocument):
action = StringField()
content = DictField()
def __str__(self):
return self.action
And the sample:
{
"_id" : ObjectId("579e60b0c525fd2037e8dd31"),
"service" : "foo",
"history" : [
{
"action" : "outbound",
"content" : {
"read_processed" : false,
"message_data" : {
"text" : "w00t?"
},
"read_process_msg" : {
"$ref" : "bots_messages",
"$id" : ObjectId("57a6529dc525fd8066ee25b3")
}
},
"created_at" : ISODate("2016-08-06T21:12:00.986Z")
}
]
}

Tastypie list endpoints added using prepend_urls

I am trying to figure out how to show all endpoints from an API I've written. In browsing to the root of the API, I see a list of resources and a single get endpoint. How do I generate a list to show all endpoints? I tried django-tastypie-swagger but had the same results. It only listed a few GET methods for each resource and didn't show all the prepend_urls that I added to the resource. Any help is appreciated!
Edit:
I have some helper methods in the get_manufacturer_wedges to help with the response. Side note, I'm very new to python and this whole stack. I come from a c#.net background. I am building a golf app.
class ManufacturerResource(BaseMongoResource):
class Meta:
max_limit = 0
queryset = Manufacturer.objects.all().order_by('id')
allowed_methods = ('get')
resource_name = 'manufacturers'
include_resource_uri = False
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/(?P<pk>[\w\d_.-]+)/wedges/$" % self._meta.resource_name,
self.wrap_view('get_manufacturer_wedges'), name="api_get_manufacturer_wedges"),
]
def get_manufacturer_wedges(self, request, **kwargs):
prim_key = kwargs['pk'] + "|Wedge"
wedges = Club.objects(_id__startswith=prim_key).order_by('name')
return self.create_response(request, HelperMethods.obj_to_list(wedges))
Here is the output when I go to the root of the api (/api/v1/):
{
"file_upload" : {
"schema" : "/api/v1/file_upload/schema/",
"list_endpoint" : "/api/v1/file_upload/"
},
"members" : {
"schema" : "/api/v1/members/schema/",
"list_endpoint" : "/api/v1/members/"
},
"manufacturers" : {
"schema" : "/api/v1/manufacturers/schema/",
"list_endpoint" : "/api/v1/manufacturers/"
},
"courses" : {
"schema" : "/api/v1/courses/schema/",
"list_endpoint" : "/api/v1/courses/"
},
"clubs" : {
"schema" : "/api/v1/clubs/schema/",
"list_endpoint" : "/api/v1/clubs/"
}
}

How to update an already retrieved document?

I'm retrieving a document like this:
user = db.users.find_one( { '_id' : ObjectId( 'anID' ) } )
But I can't figure out how to update the document if I want to change the value of 'gender'. This doesn't work:
newValue = {
'gender' : gender
}
db.users.update( user, newValue, False )
Is my syntax wrong? What's the best way to update user
Your update syntax is not correct, it should be:
update(spec, document, upsert=False, multi=False, ...)
Where spec is the same filter that you used for the find, i.e. { '_id' : ObjectId( 'anID' ) }
You can either update the document by replacing it with a modified document or use a targeted update to change only a certain value. The advantage of the targeted update is that it saves you the first round trip to the server to get the user document.
Replacement update:
user = db.users.find_one( { '_id' : ObjectId( 'anID' ) } )
user['gender'] = newGender
db.users.update( { '_id' : user['_id'] }, user, False)
Targeted update:
db.users.update( { '_id' : ObjectId( 'anID' ) }, \
{ '$set': { 'gender' : newGender } }, False )
If you don't want to replace the entire document you should use the $set operator as:
db.users.update( { '_id': user['_id'] }, { '$set': newValue }, False )

Categories