← Back to team overview

ladon-dev-team team mailing list archive

Re: REST implementation

 

Let's try to define the end-user result, and then discuss the
implementation afterwards.

In the following I have defined a very simple type as a case we can
collaborate through.

Maybe we have an object called Client that needs to be RESTful. I think the
nicest thing to do would be to SubClass LadonType (ie. RESTfulLadonType)
which has some virtual methods:

class Client(RESTfulLadonType):

  uid = PORTABLE_STRING
  name = PORTABLE_STRING
  account = PORTABLE_STRING
  credit = bool

  def get(self,id):
    pass

  def put(self,id):
    pass

  def delete(self,id):
    pass

Ladon sees right away that some og the objects used in the Service class
are RESTful and adds restful to the list of protocols.
Your turn!

/ Jakob


2012/10/23 Jakob Simon-Gaarde <jakobsg@xxxxxxxxx>

> I knew this already :-) And that's not nessesarilly a bad thing, depending
> on what you are developing. I just think we need to think through how to
> implement it in Ladon. Since you have enjoyed using Ladon until now, you
> probably already know that I am a hugh fan of end-user simplicity.
>
> And since REST is strictly object-oriented, I really think we should be
> looking at decorating the LadonType instead of methods. I haven't thought
> it all the way through yet, but I'm almost sure that would be the place to
> do it.
>
>
> / Jakob
>
>
> 2012/10/23 Mykhailo Stadnyk <mikhus@xxxxxxxxx>
>
>> Exacly! REST is a constraint to objects by design! You've caught it. But
>> it's good principal for data-oriented services with no overhead by any
>> top-level protocol. It's a principal of building RESTful services. And will
>> be good if Ladon will support it
>>
>> 2012/10/23 Jakob Simon-Gaarde <jakobsg@xxxxxxxxx>
>>
>>>  But, I think I know what you mean. It's the fact that it utilizes all
>>> HTTP methods that makes you say that REST is in the HTTP protocol. I just
>>> don't nessesarily find that this is very revolutionary. It's more of a
>>> constraint, cause you are limited to objects by design.
>>>
>>> / Jakob
>>>
>>>
>>> 2012/10/23 Mykhailo Stadnyk <mikhus@xxxxxxxxx>
>>>
>>>> For REST HTTP is not underlying level but the protocol itself. In terms
>>>> of REST design of service is known, so all you need is just to describe a
>>>> list of objects. And then for each object there is 4 operations you are
>>>> able to perform Create (HTTP PUT), Read (HTTP GET), Update (HTTP POST) and
>>>> Delete (HTTP DELETE). That's why in terms of REST you do not need the
>>>> description. You have only URLs each representing the object you can do
>>>> direct HTTP requests and you have no overhead of having some top-level
>>>> protocol, etc. That's why I said that REST is HTTP, because there is
>>>> nothing on top of HTTP in REST. And there is nothing to describe, only the
>>>> object URLs.
>>>>
>>>> It means that RESTful service architecture is not as flexible as SOAP,
>>>> so if you, as developer decided to create RESTful service it means that you
>>>> already chosen the service architecture. It also means that not every SOAP
>>>> service can be RESTful, but RESTful service could be delivered with SOAP.
>>>> So you, as developer MUST decide first if you are building a RESTful
>>>> service, if so - go ahead, make it and Ladon give you benefit to deliver
>>>> your RESTful service throug varius different supported protocols.
>>>>
>>>> Again. You CAN deliver RESTful service throug
>>>> SOAP/XML-RPC/JSON-WSP/etc. But you CAN NOT deliver abstract
>>>> SOAP/XML-RPC/JSON-WSP service through REST if it wasn't designed as RESTful
>>>>
>>>> In my implementation I've tried to make everything with maximum
>>>> flexibility, so each RESTFul service could have more than 4 CRUD methods,
>>>> for each HTTP request method you can have as many service methods as you
>>>> need if the service methods have different signatures (arguments)
>>>>
>>>> like
>>>>
>>>> class Service:
>>>>     @restfulize(method="GET")
>>>>     def get_one_record( r_id):
>>>>          pass
>>>>     @restfulize(method="GET")
>>>>     def get_last_record( r_time):
>>>>          pass
>>>>     @restfulize(method="GET")
>>>>     def get_all_records():
>>>>          pass
>>>>     @restfulize(method="GET")
>>>>     def get_filtered_records( filters):
>>>>          pass
>>>>
>>>> As you see in this example 4 service methods are bind to HTTP GET
>>>> method. Depending of parameters bypassed through HTTP I try to determine
>>>> which method to call, but from terms of protocol you always do
>>>>
>>>> GET /Service/rest?filters['name']=*bla&filters['another_key']=somevalue
>>>> HTTP/1.1
>>>> Host: yourservice.com
>>>> ...
>>>>
>>>> The same for other HTTP methods. You can have 2 different methods for
>>>> POST, for example for User service example you may have split User data
>>>> update and user password update, etc..
>>>>
>>>> Or maybe your service may not provide some methods. Like you create the
>>>> public read-only service, so you don't provide PUT/POST/DELETE
>>>>
>>>> P.S. REST does not provide the way do bypass which service method to
>>>> call by name. REST is not RPC. You have only the Resource, it's URL and 4
>>>> operations. It is REST.
>>>>
>>>> Best regards,
>>>> Mike
>>>>
>>>>
>>>> 2012/10/23 Jakob Simon-Gaarde <jakobsg@xxxxxxxxx>
>>>>
>>>>> Everything in Ladon is HTTP :-)
>>>>>
>>>>> But HTTP is just the underlying protocol, and under that is a
>>>>> transport protocol etc.
>>>>>
>>>>> When I am talking about protocol in Ladon I mean service protocols
>>>>> like soap, json-wsp and soon json-rpc which are also protocols. I would put
>>>>> REST at this level aswell.
>>>>>
>>>>> I haven't really used REST before, but from what I can see it is
>>>>> nothing more than another service protocol which utilizes the HTTP standard
>>>>> a bit more, like using PUT,POST,GET,DELETE methods. Also it looks like
>>>>> there is not so much constraint on how parameters are passed so they can be
>>>>> JSON or XML or query-string.
>>>>>
>>>>> I don't like that there is no service description format for REST
>>>>> services. That is kind of a key feature in Ladon, that all supported
>>>>> protocols should be able to describe themselves.
>>>>>
>>>>>
>>>>> / Jakob
>>>>>
>>>>> 2012/10/22 Mykhailo Stadnyk <mikhus@xxxxxxxxx>
>>>>>
>>>>>> What do you mean?
>>>>>>
>>>>>> Actually REST is HTTP in terms of protocol, so what do I need to
>>>>>> implement? Maybe you can explain me more detailed your thought.
>>>>>>
>>>>>> Best regards,
>>>>>> Mike
>>>>>>
>>>>>> 2012/10/22 Jakob Simon-Gaarde <jakobsg@xxxxxxxxx>
>>>>>>
>>>>>>> My first question about your RESTful implementation is, why are you
>>>>>>> not implementing it as a protocol?
>>>>>>>
>>>>>>> / Jakob
>>>>>>>
>>>>>>> 2012/10/22 Jakob Simon-Gaarde <jakobsg@xxxxxxxxx>
>>>>>>>
>>>>>>>> Just a remark about the package name tracepyd. That might be an
>>>>>>>> unfortunate package name, cause on that odd platform called Windows
>>>>>>>> pyd-files are binary python modules. Just a remark :-)
>>>>>>>>
>>>>>>>>
>>>>>>>> 2012/10/22 Jakob Simon-Gaarde <jakobsg@xxxxxxxxx>
>>>>>>>>
>>>>>>>>> Hi Mike.
>>>>>>>>>
>>>>>>>>> Let's talk about tracepyd first, does it solve this bug:
>>>>>>>>>
>>>>>>>>> https://bugs.launchpad.net/ladon/+bug/877727
>>>>>>>>>
>>>>>>>>> rpclib has a terribly ugly solution for this, as you can see in
>>>>>>>>> the bug, and we want to be better than rpclib :-)
>>>>>>>>>
>>>>>>>>> / Jakob
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> 2012/10/22 Jakob Simon-Gaarde <jakobsg@xxxxxxxxx>
>>>>>>>>>
>>>>>>>>>> Hi Mike
>>>>>>>>>>
>>>>>>>>>> Looking into this now, you will have my reply today :-)
>>>>>>>>>>
>>>>>>>>>> / Jakob
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> 2012/10/22 Mykhailo Stadnyk <mikhus@xxxxxxxxx>
>>>>>>>>>>
>>>>>>>>>>> Currently it's an experimental feature which is not ready to be
>>>>>>>>>>> integrated. I didn't test it at all (keep working on it).
>>>>>>>>>>>
>>>>>>>>>>> So here is my personal road-map for this feature:
>>>>>>>>>>>   1. Resolve problem with decorators dependencies (something I
>>>>>>>>>>> described in previous message)
>>>>>>>>>>>   2. Write tests and test the functionality
>>>>>>>>>>>   3. I've not checked how does it deal with requests containing
>>>>>>>>>>> multipart boundaries and binary data
>>>>>>>>>>>   4. Not sure what to do with service description (currently
>>>>>>>>>>> I've just removed, but have an idea we can use it to describe service any
>>>>>>>>>>> way, even as long as REST does not specify this)
>>>>>>>>>>>   5. Write documentation/examples
>>>>>>>>>>>
>>>>>>>>>>> But I'm stuck on issue #1. I want to know:
>>>>>>>>>>>  * do you agree to add dependency in Ladon to tracepyd module or
>>>>>>>>>>> not?
>>>>>>>>>>>  * If not - do you agree to include tracepyd's implementation
>>>>>>>>>>> into Ladon (not as dependency, but to place it somewhere in tools)?
>>>>>>>>>>>  * If not - do you agree to modify @ladonize decorator
>>>>>>>>>>> functionality to add extra-params for REST (for me its ugly).
>>>>>>>>>>>  * If not - which ideas do you have then? :)
>>>>>>>>>>>
>>>>>>>>>>> I'd like to suggest to add dependency to tracepyd. Currently I
>>>>>>>>>>> have a version locally which works fine, so I can publish this solution to
>>>>>>>>>>> my branch to let you see.
>>>>>>>>>>>
>>>>>>>>>>> Thank you in advance for your time and advice,
>>>>>>>>>>> Mike
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> 2012/10/22 Jakob Simon-Gaarde <jakobsg@xxxxxxxxx>
>>>>>>>>>>>
>>>>>>>>>>>> Interesting :-)
>>>>>>>>>>>>
>>>>>>>>>>>> I'll look at it later - we must make sure that it doesn't break
>>>>>>>>>>>> anything.
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> 2012/10/22 Mykhailo Stadnyk <mikhus@xxxxxxxxx>
>>>>>>>>>>>>
>>>>>>>>>>>>> Hi Jacob.
>>>>>>>>>>>>>
>>>>>>>>>>>>> As I told I'm trying to implement RESTful services support by
>>>>>>>>>>>>> Ladon. The basic functionality is implemented - you can check my branch:
>>>>>>>>>>>>> lp:~mikhus/ladon/rest<https://code.launchpad.net/~mikhus/ladon/rest> to
>>>>>>>>>>>>> see how does it work. Here is an example of RESTful service you may run to
>>>>>>>>>>>>> see:
>>>>>>>>>>>>>
>>>>>>>>>>>>> from ladon.ladonizer import ladonize, restfulize
>>>>>>>>>>>>> from ladon.compat import PORTABLE_STRING
>>>>>>>>>>>>> from ladon.types.ladontype import LadonType
>>>>>>>>>>>>>
>>>>>>>>>>>>> class User(LadonType):
>>>>>>>>>>>>>  name = PORTABLE_STRING
>>>>>>>>>>>>> email = PORTABLE_STRING
>>>>>>>>>>>>> passwd = PORTABLE_STRING
>>>>>>>>>>>>>  def __init__():
>>>>>>>>>>>>>  self.name = ""
>>>>>>>>>>>>>  self.email = ""
>>>>>>>>>>>>>  self.passwd = ""
>>>>>>>>>>>>>
>>>>>>>>>>>>> class UserService( object) :
>>>>>>>>>>>>>  @restfulize(method="PUT")
>>>>>>>>>>>>>  @ladonize(str, str, str, rtype=User)
>>>>>>>>>>>>> def newUser( self, u_name, u_email, u_passwd) :
>>>>>>>>>>>>>  user = User() # where User is LadonType object
>>>>>>>>>>>>> user.name   = u_name
>>>>>>>>>>>>>  user.email  = u_email
>>>>>>>>>>>>> user.passwd = u_passwd
>>>>>>>>>>>>> return user
>>>>>>>>>>>>>  @restfulize(method="GET")
>>>>>>>>>>>>> @ladonize(int, rtype=[User])
>>>>>>>>>>>>>  def getUser(self, u_id) :
>>>>>>>>>>>>> return User()
>>>>>>>>>>>>>  @restfulize(method="GET")
>>>>>>>>>>>>> @ladonize(int, rtype=[User])
>>>>>>>>>>>>> def getUsers(self) :
>>>>>>>>>>>>>  return [User(), User(), User()]
>>>>>>>>>>>>>  @restfulize(method="POST")
>>>>>>>>>>>>>  @ladonize(int, str, str, str, rtype=User)
>>>>>>>>>>>>> def updUser( self, u_id, u_name, u_email, u_passwd):
>>>>>>>>>>>>>  user = User()
>>>>>>>>>>>>> user.name   = u_name
>>>>>>>>>>>>> user.email  = u_email
>>>>>>>>>>>>>  user.passwd = u_passwd
>>>>>>>>>>>>> return user
>>>>>>>>>>>>>  @restfulize(method="DELETE")
>>>>>>>>>>>>> @ladonize(int, rtype=bool)
>>>>>>>>>>>>> def delUser( self, u_id) :
>>>>>>>>>>>>>  return True
>>>>>>>>>>>>>
>>>>>>>>>>>>> By the way, there is at least one issue I don't know how to
>>>>>>>>>>>>> solve better, so I want your advice.
>>>>>>>>>>>>>
>>>>>>>>>>>>> As you see I've implemented specific decorator to mark service
>>>>>>>>>>>>> methods as RESTful. It's @restfulize decorator. The problem that it is
>>>>>>>>>>>>> implemented now as:
>>>>>>>>>>>>>
>>>>>>>>>>>>> def restfulize( *dargs, **dkwargs):
>>>>>>>>>>>>>  def decorator(fn):
>>>>>>>>>>>>> * minfo = fn._ladon_method_info*
>>>>>>>>>>>>> if 'method' in dkwargs:
>>>>>>>>>>>>>  if not minfo.restfulize( **dkwargs):
>>>>>>>>>>>>> raise RestfulMethodConfigMismatch( fn.func_name)
>>>>>>>>>>>>>
>>>>>>>>>>>>> return fn # returns ladonized method
>>>>>>>>>>>>> return decorator
>>>>>>>>>>>>>
>>>>>>>>>>>>> The problem is highlighted with bold. Will be good to make
>>>>>>>>>>>>> this decorator independent of what @ladonize defined in it's private props.
>>>>>>>>>>>>> But, as far as "fn" here is a reference to ladon's "injector" function I
>>>>>>>>>>>>> have no way to determine correctly which real method in which class is
>>>>>>>>>>>>> decorated. It means that in such case I have no way to extract
>>>>>>>>>>>>> LadonMethodInfo associated to "fn" in other way but implemented for the
>>>>>>>>>>>>> moment.
>>>>>>>>>>>>>
>>>>>>>>>>>>> The real problem is when I want to override @ladonize
>>>>>>>>>>>>> decorator. In this case @resftulize becomes nonoperational because I loose
>>>>>>>>>>>>> reference to "_ladon_method_info" and means that I should take care to
>>>>>>>>>>>>> bypass all the existing private properties in 3-d party override
>>>>>>>>>>>>> implementation, which is not good at all. So I want to have @restfulize and
>>>>>>>>>>>>> @ladonize implemented without hard dependency between them.
>>>>>>>>>>>>> Via global_service_collection() it's possible to do but you
>>>>>>>>>>>>> need to determine real method function in some way.
>>>>>>>>>>>>>
>>>>>>>>>>>>> So I see the following ways to fix it:
>>>>>>>>>>>>>
>>>>>>>>>>>>> 1. Via decorator registry (Here we solved such kind of problem
>>>>>>>>>>>>> by creating DecoratorRegistry (I've published it as
>>>>>>>>>>>>> http://pypi.python.org/pypi/tracepyd))
>>>>>>>>>>>>> 2. By adding REST functionality to @ladonize decorator itself,
>>>>>>>>>>>>> to have something like:
>>>>>>>>>>>>>     @ladonize(PORTABLE_STRING, rtype=[PORTABLE_STRING], *rest={
>>>>>>>>>>>>> "method" : "GET", "produces": "XML" }*)
>>>>>>>>>>>>>
>>>>>>>>>>>>> Maybe you can point me to some other ideas you know how to
>>>>>>>>>>>>> resolve such kind of problem.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Best regards,
>>>>>>>>>>>>> Mike
>>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> --
>>>>>>>>>>>> Med venlig hilsen / Best regards
>>>>>>>>>>>> Jakob Simon-Gaarde
>>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> --
>>>>>>>>>> Med venlig hilsen / Best regards
>>>>>>>>>> Jakob Simon-Gaarde
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> Med venlig hilsen / Best regards
>>>>>>>>> Jakob Simon-Gaarde
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> Med venlig hilsen / Best regards
>>>>>>>> Jakob Simon-Gaarde
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Med venlig hilsen / Best regards
>>>>>>> Jakob Simon-Gaarde
>>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Med venlig hilsen / Best regards
>>>>> Jakob Simon-Gaarde
>>>>>
>>>>
>>>>
>>>
>>>
>>> --
>>> Med venlig hilsen / Best regards
>>> Jakob Simon-Gaarde
>>>
>>> --
>>> Mailing list: https://launchpad.net/~ladon-dev-team
>>> Post to     : ladon-dev-team@xxxxxxxxxxxxxxxxxxx
>>> Unsubscribe : https://launchpad.net/~ladon-dev-team
>>> More help   : https://help.launchpad.net/ListHelp
>>>
>>>
>>
>
>
> --
> Med venlig hilsen / Best regards
> Jakob Simon-Gaarde
>



-- 
Med venlig hilsen / Best regards
Jakob Simon-Gaarde

Follow ups

References