I am currently using EndpointsModel to create a RESTful API for all my models on AppEngine. Since it is RESTful, these api have a lot of repeat code which I want to avoid.
For example:
class Reducer(EndpointsModel):
name = ndb.StringProperty(indexed=False)
@endpoints.api(
name="bigdata",
version="v1",
description="""The BigData API""",
allowed_client_ids=ALLOWED_CLIENT_IDS,
)
class BigDataApi(remote.Service):
@Reducer.method(
path="reducer",
http_method="POST",
name="reducer.insert",
user_required=True,
)
def ReducerInsert(self, obj):
pass
## and GET, POST, PUT, DELETE
## REPEATED for each model
I want to make them become generic. So I try to dynamic add method to the class. What I have tried so far:
from functools import partial, wraps
def GenericInsert(self, obj, cls):
obj.owner = endpoints.get_current_user()
obj.put()
return obj
# Ignore GenericDelete, GenericGet, GenericUpdate ...
import types
from functools import partial
def register_rest_api(api_server, endpoint_cls):
name = endpoint_cls.__name__
# create list method
query_method = types.MethodType(
endpoint_cls.query_method(
query_fields=('limit', 'pageToken'),
path="%ss" % name,
http_method="GET",
name="%s.list" % name,
user_required=True
)(partial(GenericList, cls=endpoint_cls)))
setattr(api_server, "%sList", query_method)
# create insert method
# ...
register_rest_api(BigDataApi, Reducer)
But I got 'functools.partial' object has no attribute '__module__' exception.
I think it is because there are some conflicts between endpoints.method
's decorator and partial. But no idea how to avoid it.
Traceback (most recent call last):
File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 239, in Handle
handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 298, in _LoadHandler
handler, path, err = LoadObject(self._handler)
File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 84, in LoadObject
obj = __import__(path[0])
File "/Users/Sylvia/gcdc2013/apis.py", line 795, in <module>
register_rest_api(BigDataApi, Reducer)
File "/Users/Sylvia/gcdc2013/apis.py", line 788, in register_rest_api
)(partial(GenericList, cls=endpoint_cls)))
File "/Users/Sylvia/gcdc2013/endpoints_proto_datastore/ndb/model.py", line 1544, in RequestToQueryDecorator
@functools.wraps(api_method)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'functools.partial' object has no attribute '__module__'
related articles:
I also stumbled upon this, I was really surprised, for me the issue was that partial objects are missing certain attributes, specifically
__module__
and__name__
Being that
wraps
by default usesfunctools.WRAPPER_ASSIGNMENTS
to update attributes, which defaults to('__module__', '__name__', '__doc__')
in python 2.7.6 anyway, there are a couple ways of dealing with this ...Update only present attributes ...
Here we simply filter out all those attribute which aren't present.
Another approach would be to strictly deal with partial objects only, you could fold
wraps
withsingledispatch
and create wrapped partial objects whose attributes would be taken from the deepestfunc
attribute.Something along the lines:
This is slightly better since now we can get the original functions docs which before defaulted to using the partial objects doc string.
This can be modified to only search if the current partial object doesn't already have the set attribute, which should be slightly faster when nesting many partial objects ...
UPDATE
It seems that python(CPython) 3 (at least 3.4.3) doesn't have this issue, since I don't know nor should I assume all versions of python 3 or other implementations such as Jython also share this issue here is another future ready approach
a couple things to note:
wraps
function only if we fail to wrap a partial, in case future versions of python2 or other versions fix this issue.wraps
to copy the docs and other infoifilter
since it was removed in python3, I've timeit with and withoutifilter
but the results where inconclusive, at least in python (CPython) 2.7.6, the difference was marginal at best either way...