I'm trying to implement client for JSON API. I've worked with Django and i found the way it handles queries pretty interesting.
For example:
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(username='johndoe')
>>> user
<User: johndoe>
>>> user.objects.get(username='johndoe')
AttributeError: Manager isn't accessible via User instances
I'm interested what Manager exactly is and if there is a simple way to implement it. I tried to do something similar, but failed miserably. Here is my first attempt:
class Api(object):
def __init__(self, api_key):
self.api_key = key
#property
def player(self):
return Player(api=self)
class Player(object):
def __init__(self, api):
self.api = api
def get(self, id_):
'''
gets json and turns it into dictionary
'''
self.name = data['name']
self.id_ = id_
return self
So the usage would look like this:
>>> api = Api('api_key_here')
>>> player = api.player.get('player_id_here')
However to me it feels sketchy and simply wrong to do it this way(which is certainly true). Problems here are:
I have to send whole Api object to Player object
Player object will still have get() method
I don't know if using #property this way is acceptable
Here is how i would like to use my Api class:
api = Api('api_key_here')
player = api.player.get('player_id') #getting player object
item = api.item.get('item_id') #getting item object
recent_games = api.recent_games.filter(player=player, how_many=10) #getting list of game objects
I recommend you look at https://github.com/samgiles/slumber which implements exactly what you may be doing (a Rest API Client). But even if you are doing something else, slumber is implemented doing what you are trying to do. Something like:
class RestResource(object):
def __init__(self, *args, **kwargs):
self._store = kwargs
def __call__(self, id=None, action=None):
"""
Returns a new instance of self modified by one or more of the available
parameters. These allows us to do things like override format for a
specific request, and enables the api.resource(ID).get() syntax to get
a specific resource by it's ID.
"""
kwargs = {
'foo': self._store['name1'],
'bar': self._store['name2']
}
return self.__class__(**kwargs)
class Api(object):
resource_class = RestResource
def __getattr__(self, item):
kwargs = {
'name1': 'val1',
'name2': 'val2'
}
return self.resource_class(**kwargs)
and you can use like this
api = Api()
api.player.get()
Related
I have a very simple class that defines properties like:
class Person:
fields = set()
#property
def id(self):
self.fields.add('id')
return 'person.id'
The idea is for this class to record which properties have been accessed. Now the problem comes when I need to start supporting: person.metadata.key where metadata is basically an HStore object or 1 level JSON, the key is arbitrary and the idea is for the class Person so record access to any of the keys in metadata. I tried something like this:
class CustomerBulkContext:
fields = set()
class PersonMetadata:
def __getitem__(self, attr):
fields.add(f'metadata.{attr}')
return f'person.metadata.{attr}'
metadata = CustomerMetadataContext()
Now obviously the problem is that fields inside PersonMetadata is not a known variable at this point. How can I overcome this issue, I don't know if it's possible to do in Python without too much code.
In order to track accesses like person.metadata.key you'd have to have person.metadata return an object which itself tracks how it was accessed. In order to track the whole chain, you'd pass the chain so far to the object.
Something like:
class AccessTracker:
def __init__(self, path=()):
self._path = path
def __getattr__(self, name):
print('Accessed %s in %s' % (name, self._path))
return AccessTracker(self._path + (name,))
I'm writing a python REST client for an API.
The API needs authentication and I would like to have many API client objects running on the same script.
My current code for the API is something like this:
class RestAPI:
def __init__(self, id):
self.id = id
self.fetch()
def fetch(self):
requests.get(self.url+self.id, auth=self.apikey)
class Purchase(RestAPI):
url = 'http://example.com/purchases/'
class Invoice(RestAPI):
url = 'http://example.com/invoices/'
...
And I would like to use the API like that:
api_admin = Api('adminmytoken')
api_user = Api('usertoken')
…
amount = api_admin.Purchase(2).amount
api_user.Purchase(2).amount # raises because api_user is not authorized for this purchase
The problem is that each object needs to know it's apikey depending on the client I want to use.
That pattern looks like to me to a "class factory": all the classes of RestAPI need to know of the provided token.
How is it possible to cleanly do that without giving manually the token to each model ?
I think the issue here is that your design is a little backwards. Inheritance might not be the key here. What I might do is take the api token as an argument on the User class, then that gets passed to an instance-level binding on the Rest interface:
class APIUser:
def __init__(self, id, api_key, **kwargs):
self._rest = Interface(id, api_key, **kwargs)
def purchase(self, some_arg):
# the interface itself does the actual legwork,
# and you are simply using APIUser to call functions with the interface
return self._rest.fetch('PURCHASE', some_arg)
class Interface:
methods = {
# call you want (class url)
'PURCHASE': (Purchase, 'https://myexample.com/purchases'),
'INVOICE': (Invoice, 'https://myexample.com/invoices'),
# add more methods here
}
def __init__(self, id, key):
self.id = id
self.key = key
self.session = requests.Session()
def _fetch(self, method, *args, **kwargs):
# do some methods to go get data
try:
# use the interface to look up your class objects
# which you may or may not need
_class, url = self.methods[method]
except KeyError as e:
raise ValueError(f"Got unsupported method, expected "
f"{'\n'.join(self.methods)}") from e
headers = kwargs.pop('headers', {})
# I'm not sure the actual interface here, maybe you call the
# url to get metadata to populate the class with first...
req = requests.Request(_class.http_method, url+self.id, auth=self.key, headers=headers).prepare()
resp = self.session.send(req)
# this will raise the 401 ahead of time
resp.raise_for_status()
# maybe your object uses metadata from the response
params = resp.json()
# return the business object only if the user should see it
return _class(*args, **kwargs, **params)
class Purchase:
http_method = 'GET'
def __init__(self, *args, **kwargs):
# do some setup here with your params passed by the json
# from the api
user = APIUser("token", "key") # this is my user session
some_purchase = user.purchase(2) # will raise a 401 Unauthorized error from the requests session
admin = APIUser("admintoken", "adminkey") # admin session
some_purchase = admin.purchase(2)
# returns a purchase object
some_purchase.amount
There are a few reasons why you might want to go this way:
You don't get the object back if you aren't allowed to see it
Now the rest interface is in control of who sees what, and that's implicitly tied to the user object itself, without every other class needing to be aware of what's going on
You can change your url's in one place (if you need to)
Your business objects are just business objects, they don't need to do anything else
By separating out what your objects actually are, you still only need to pass the api keys and tokens once, to the User class. The Interface is bound on the instance, still giving you the flexibility of multiple users within the same script.
You also get the models you call on explicitly. If you try to take a model, you have to call it, and that's when the Interface can enforce your authentication. You no longer need your authentication to be enforced by your business objects
I have an external API that I cannot modify. For each call to this API, I need to be able to perform an operation before and after. This API is used like this:
def get_api():
"""
Return an initiated ClassAPI object
"""
token = Token.objects.last()
api = ClassAPI(
settings.CLASS_API_ID,
settings.CLASS_API_SECRET,
last_token)
return api
get_api() is called everywhere in the code and the result is then used to perform request (like: api.book_store.get(id=book_id)).
My goal is to return a virtual object that will perform the same operations than the ClassAPI adding print "Before" and print "After".
The ClassAPI looks like this:
class ClassAPI
class BookStore
def get(...)
def list(...)
class PenStore
def get(...)
def list(...)
I tried to create a class inheriting from (ClassApi, object) [as ClassAPI doesn't inherit from object] and add to this class a metaclass that decorates all the methods, but I cannot impact the methods from BookStore (get and list)
Any idea about how to perform this modifying only get_api() and adding additional classes? I try to avoid copying the whole structure of the API to add my operations.
I am using python 2.7.
You could do this with a Proxy:
class Proxy:
def __init__(self, other):
self.other = other
self.calls = []
def __getattr__(self, name):
self.calls.append(name)
return self
def __call__(self, *args, **kwargs):
self.before()
ret = self.call_proxied(*args, **kwargs)
self.after()
return ret
def call_proxied(self, *args, **kwargs):
other = self.other
calls = self.calls
self.calls = []
for item in calls:
other = getattr(other, item)
return other(*args, **kwargs)
This class intercepts unknown members in the __getattr__() method, saving the names that are used and returning itself.
When a method is called (eg. api.book_store.get(id=book_id) ), it calls a before() and after() method on itself and in between it fetches the members of other and forwards the arguments in a call.
You use this class like this:
def get_api():
...
return Proxy(api)
Update: corrected the call to self.call_proxied(*args, **kwargs). Also allow any return value to be returned.
I am using Django Rest Framework to create my API. I am using #link to return information about a particular object.
class SomeClassView(viewsets.ReadOnlyModelViewSet):
#link
def info(self, request, pk=None):
obj = models.SomeClass.objects.get(pk=pk)
return Response({"info": object.info()})
GET: /someclass/1/info
What I would like to do is extend the method so I can access it at the "class level" so that my api could accept a list of objects
class SomeClassView(viewsets.ReadOnlyModelViewSet):
#link
def info(self, request, pk=None):
if isinstance(s, str):
obj = models.SomeClass.objects.get(pk=pk)
return Response({"info": obj.info()})
else:
objs = models.SomeClass.objects.filter(pk__in=pk).all()
return Response({"infos": [obj.info() for obj in objs]})
GET: /someclass/1/info
GET: /someclass/info?pk=1&pk=2&pk=3
Is there a way I can add a class level method to my api? Or will I need to create a new class to handle the one api call?
PS: I don't mind if I need to have a separate method to make this work
The dynamically generated routes using the #link or #action decorators are hard-coded to look like /someclass/<pk>/<methodname>/. You can expose a /someclass/info endpoint by adding a custom route, e.g.:
class MyRouter(DefaultRouter):
routes = [
Route(
url=r'^{prefix}/((?P<pk>\d+)/)?info$',
mapping={'get': 'info'},
name='{basename}-info',
initkwargs={}
)
] + DefaultRouter.routes
Then your info method might look like this:
def info(self, request, pk=None):
if pk:
obj = SomeClass.objects.get(pk=pk)
return Response({'info': obj.info()})
else:
objs = SomeClass.objects.filter(pk__in=request.GET.getlist('pk'))
return Response({'infos': [obj.info() for obj in objs]})
(Note the absence of the #link decorator.)
I'm using the mailjet Python API, and I'm a little confused.
https://www.mailjet.com/docs/api/lists/contacts
It doesn't even appear possible to use this API class to call mailjets GET methods.
Can anyone confirm this is the case?
api.lists.Contacts(id='foo') # causes POST, thus 405
Here's their API classes. I don't even see ApiMethodFunction passing options to the connection class.
class ApiMethod(object):
def __init__(self, api, method):
self.api = api
self.method = method
def __getattr__(self, function):
return ApiMethodFunction(self, function)
def __unicode__(self):
return self.method
class ApiMethodFunction(object):
def __init__(self, method, function):
self.method = method
self.function = function
def __call__(self, **kwargs):
response = self.method.api.connection.open(
self.method,
self.function,
postdata=kwargs,
)
return json.load(response)
def __unicode__(self):
return self.function
It seems like a critical feature so I'm inclined to think I'm just using it incorrectly, but could it be?
How are you supposed to list Contacts if it needs id in the GET parameters?
the python library is now fixed. According to its author, you can now pass a parameter to specify if the request is going to be a POST or a GET
https://github.com/WoLpH/mailjet