Commit 89864450 authored by Jacek Furmankiewicz's avatar Jacek Furmankiewicz

major change to enable custom routing to REST services from a single parent Resource

parent 39d43ed7
...@@ -2,6 +2,25 @@ ...@@ -2,6 +2,25 @@
Common classes Common classes
''' '''
from zope.interface import Interface, Attribute
#########################################################
#
# INTERFACES
#
#########################################################
class IRestServiceContainer(Interface):
"""An interface for all REST services that can be added within a root CorePost resource"""
services = Attribute("All the REST services contained in this resource")
#########################################################
#
# CLASSES
#
#########################################################
class Response: class Response:
""" """
Custom response object, can be returned instead of raw string response Custom response object, can be returned instead of raw string response
......
This diff is collapsed.
...@@ -3,7 +3,7 @@ Argument extraction tests ...@@ -3,7 +3,7 @@ Argument extraction tests
@author: jacekf @author: jacekf
''' '''
from corepost.web import CorePost, validate, route from corepost.web import RestServiceContainer, validate, route
from corepost.enums import Http from corepost.enums import Http
from formencode import Schema, validators from formencode import Schema, validators
...@@ -11,7 +11,7 @@ class TestSchema(Schema): ...@@ -11,7 +11,7 @@ class TestSchema(Schema):
allow_extra_fields = True allow_extra_fields = True
childId = validators.Regex(regex="^jacekf|test$") childId = validators.Regex(regex="^jacekf|test$")
class ArgumentApp(CorePost): class ArgumentApp():
@route("/int/<int:intarg>/float/<float:floatarg>/string/<stringarg>",Http.GET) @route("/int/<int:intarg>/float/<float:floatarg>/string/<stringarg>",Http.GET)
def test(self,request,intarg,floatarg,stringarg,**kwargs): def test(self,request,intarg,floatarg,stringarg,**kwargs):
...@@ -29,5 +29,5 @@ class ArgumentApp(CorePost): ...@@ -29,5 +29,5 @@ class ArgumentApp(CorePost):
return "%s - %s - %s" % (rootId,childId,kwargs) return "%s - %s - %s" % (rootId,childId,kwargs)
def run_app_arguments(): def run_app_arguments():
app = ArgumentApp() app = RestServiceContainer((ArgumentApp(),))
app.run(8082) app.run(8082)
\ No newline at end of file
...@@ -3,21 +3,21 @@ Server tests ...@@ -3,21 +3,21 @@ Server tests
@author: jacekf @author: jacekf
''' '''
from corepost.web import CorePost, route from corepost.web import RestServiceContainer, route
from corepost.enums import Http from corepost.enums import Http
from corepost.filters import IRequestFilter, IResponseFilter from corepost.filters import IRequestFilter, IResponseFilter
import zope.interface from zope.interface import implements
class AddCustomHeaderFilter(): class AddCustomHeaderFilter():
"""Implements just a request filter""" """Implements just a request filter"""
zope.interface.implements(IRequestFilter) implements(IRequestFilter)
def filterRequest(self,request): def filterRequest(self,request):
request.received_headers["Custom-Header"] = "Custom Header Value" request.received_headers["Custom-Header"] = "Custom Header Value"
class Change404to503Filter(): class Change404to503Filter():
"""Implements just a response filter that changes 404 to 503 statuses""" """Implements just a response filter that changes 404 to 503 statuses"""
zope.interface.implements(IResponseFilter) implements(IResponseFilter)
def filterResponse(self,request,response): def filterResponse(self,request,response):
if response.code == 404: if response.code == 404:
...@@ -25,7 +25,7 @@ class Change404to503Filter(): ...@@ -25,7 +25,7 @@ class Change404to503Filter():
class WrapAroundFilter(): class WrapAroundFilter():
"""Implements both types of filters in one class""" """Implements both types of filters in one class"""
zope.interface.implements(IRequestFilter,IResponseFilter) implements(IRequestFilter,IResponseFilter)
def filterRequest(self,request): def filterRequest(self,request):
request.received_headers["X-Wrap-Input"] = "Input" request.received_headers["X-Wrap-Input"] = "Input"
...@@ -33,14 +33,15 @@ class WrapAroundFilter(): ...@@ -33,14 +33,15 @@ class WrapAroundFilter():
def filterResponse(self,request,response): def filterResponse(self,request,response):
response.headers["X-Wrap-Output"] = "Output" response.headers["X-Wrap-Output"] = "Output"
class FilterApp(CorePost): class FilterService():
path = "/"
@route("/",Http.GET) @route("/",Http.GET)
def root(self,request,**kwargs): def root(self,request,**kwargs):
return request.received_headers return request.received_headers
def run_filter_app(): def run_filter_app():
app = FilterApp(filters=(Change404to503Filter(),AddCustomHeaderFilter(),WrapAroundFilter(),)) app = RestServiceContainer(services=(FilterService(),),filters=(Change404to503Filter(),AddCustomHeaderFilter(),WrapAroundFilter(),))
app.run(8083) app.run(8083)
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -3,17 +3,15 @@ Server tests ...@@ -3,17 +3,15 @@ Server tests
@author: jacekf @author: jacekf
''' '''
from corepost.web import CorePost, route from corepost.web import RestServiceContainer, route
from corepost.enums import Http, MediaType, HttpHeader from corepost.enums import Http, MediaType, HttpHeader
from twisted.internet import defer from twisted.internet import defer
from xml.etree import ElementTree from xml.etree import ElementTree
from UserDict import UserDict
import json, yaml import json, yaml
class HomeApp(CorePost): class HomeApp():
def __init__(self,*args,**kwargs): def __init__(self,*args,**kwargs):
CorePost.__init__(self, *args, **kwargs)
self.issue1 = "issue 1" self.issue1 = "issue 1"
@route("/",Http.GET) @route("/",Http.GET)
...@@ -117,7 +115,7 @@ class HomeApp(CorePost): ...@@ -117,7 +115,7 @@ class HomeApp(CorePost):
return self.issue1 return self.issue1
def run_app_home(): def run_app_home():
app = HomeApp() app = RestServiceContainer((HomeApp(),))
app.run() app.run()
if __name__ == "__main__": if __name__ == "__main__":
......
''' '''
A CorePost module1 that can be merged into the main CorePost Resource A RestServiceContainer module1 that can be merged into the main RestServiceContainer Resource
''' '''
from corepost.web import CorePost, route from corepost.web import RestServiceContainer, route
from corepost.enums import Http from corepost.enums import Http
from twisted.web.resource import Resource
from twisted.internet import reactor
from twisted.web.server import Site
class HomeApp(CorePost): class HomeApp():
@route("/") @route("/")
def home_root(self,request,**kwargs): def home_root(self,request,**kwargs):
return "HOME %s" % kwargs return "HOME %s" % kwargs
class Module1(CorePost): class Module1():
path = "/module1"
@route("/",Http.GET) @route("/",Http.GET)
def module1_get(self,request,**kwargs): def module1_get(self,request,**kwargs):
...@@ -24,7 +22,8 @@ class Module1(CorePost): ...@@ -24,7 +22,8 @@ class Module1(CorePost):
def module1e_sub(self,request,**kwargs): def module1e_sub(self,request,**kwargs):
return request.path return request.path
class Module2(CorePost): class Module2():
path = "/module2"
@route("/",Http.GET) @route("/",Http.GET)
def module2_get(self,request,**kwargs): def module2_get(self,request,**kwargs):
...@@ -35,12 +34,8 @@ class Module2(CorePost): ...@@ -35,12 +34,8 @@ class Module2(CorePost):
return request.path return request.path
def run_app_multi(): def run_app_multi():
app = Resource() app = RestServiceContainer((HomeApp(),Module1(),Module2()))
app.putChild('', HomeApp()) app.run(8081)
app.putChild('module1',Module1())
app.putChild('module2',Module2())
factory = Site(app)
reactor.listenTCP(8081, factory) #@UndefinedVariable
reactor.run() #@UndefinedVariable
if __name__ == "__main__":
run_app_multi()
\ No newline at end of file
'''
Server tests
@author: jacekf
'''
from corepost import Response
from corepost.web import RestServiceContainer
class DB():
"""Fake in-memory DB for testing"""
customers = {}
class Customer():
"""Represents customer entity"""
def __init__(self,customerId,firstName,lastName):
(self.customerId,self.firstName,self.lastName) = (customerId,firstName,lastName)
self.addresses = {}
class CustomerAddress():
"""Represents customer address entity"""
def __init__(self,customer,streetNumber,streetName,stateCode,countryCode):
(self.customer,self.streetNumber,self.streetName.self.stateCode,self.countryCode) = (customer,streetNumber,streetName,stateCode,countryCode)
class CustomerRestService():
path = "/customer"
def getAll(self,request):
return DB.customers
def get(self,request,customerId):
return DB.customers[customerId] if customerId in DB.customers else Response(404, "Customer %s not found" % customerId)
def post(self,request,customerId,firstName,lastName):
if customerId in DB.customers:
return Response(409,"Customer %s already exists" % customerId)
else:
DB.customers[customerId] = Customer(customerId, firstName, lastName)
return Response(201)
def put(self,request,customerId,firstName,lastName):
if customerId in DB.customers:
DB.customers[customerId].firstName = firstName
DB.customers[customerId].lastName = lastName
return Response(200)
else:
return Response(404, "Customer %s not found" % customerId)
def delete(self,request,customerId):
if customerId in DB.customers:
del(DB.customers[customerId])
return Response(200)
else:
return Response(404, "Customer %s not found" % customerId)
def deleteAll(self,request):
DB.customers.clear()
return Response(200)
class CustomerAddressRestService():
path = "/customer/<customerId>/address"
def getAll(self,request,customerId):
return DB.customers
def get(self,request,customerId):
return DB.customers[customerId] if customerId in DB.customers else Response(404, "Customer %s not found" % customerId)
def post(self,request,customerId,firstName,lastName):
if customerId in DB.customers:
return Response(409,"Customer %s already exists" % customerId)
else:
DB.customers[customerId] = Customer(customerId, firstName, lastName)
return Response(201)
def put(self,request,customerId,firstName,lastName):
if customerId in DB.customers:
DB.customers[customerId].firstName = firstName
DB.customers[customerId].lastName = lastName
return Response(200)
else:
return Response(404, "Customer %s not found" % customerId)
def delete(self,request,customerId):
if customerId in DB.customers:
del(DB.customers[customerId])
return Response(200)
else:
return Response(404, "Customer %s not found" % customerId)
def deleteAll(self,request):
DB.customers.clear()
return Response(200)
def run_rest_app():
app = RestServiceContainer(restServices=(CustomerRestService(),))
app.run(8085)
if __name__ == "__main__":
run_rest_app()
\ No newline at end of file
...@@ -22,3 +22,11 @@ def convertToJson(obj): ...@@ -22,3 +22,11 @@ def convertToJson(obj):
return json.dumps(obj) return json.dumps(obj)
except Exception as ex: except Exception as ex:
raise RuntimeError(str(ex)) raise RuntimeError(str(ex))
def checkExpectedInterfaces(objects,expectedInterface):
"""Verifies that all the objects implement the expected interface"""
for obj in objects:
if not expectedInterface.providedBy(obj):
raise RuntimeError("Object %s does not implement %s interface" % (obj,expectedInterface))
\ No newline at end of file
...@@ -3,32 +3,35 @@ Main server classes ...@@ -3,32 +3,35 @@ Main server classes
@author: jacekf @author: jacekf
''' '''
from collections import defaultdict from corepost import Response, IRestServiceContainer
from corepost import Response from corepost.enums import Http
from corepost.enums import Http, HttpHeader from corepost.routing import UrlRouter, RequestRouter
from corepost.utils import getMandatoryArgumentNames, convertToJson
from corepost.routing import UrlRouter, CachedUrl, RequestRouter
from enums import MediaType from enums import MediaType
from formencode import FancyValidator, Invalid from formencode import FancyValidator, Invalid
from twisted.internet import reactor, defer from twisted.internet import reactor
from twisted.web.http import parse_qs from twisted.internet.defer import Deferred
from twisted.web.resource import Resource from twisted.web.resource import Resource
from twisted.web.server import Site, NOT_DONE_YET from twisted.web.server import Site, NOT_DONE_YET
import re, copy, exceptions, json, yaml from zope.interface import implements
from xml.etree import ElementTree
from xml.etree.ElementTree import Element #########################################################
from twisted.internet.defer import Deferred #
# CLASSES
#
#########################################################
class CorePost(Resource): class RestServiceContainer(Resource):
''' '''
Main resource responsible for routing REST requests to the implementing methods Main resource responsible for routing REST requests to the implementing methods
''' '''
isLeaf = True isLeaf = True
implements(IRestServiceContainer)
def __init__(self,schema=None,filters=()): def __init__(self,services=(),schema=None,filters=()):
''' '''
Constructor Constructor
''' '''
self.services = services
self.__router = RequestRouter(self,schema,filters) self.__router = RequestRouter(self,schema,filters)
Resource.__init__(self) Resource.__init__(self)
...@@ -82,7 +85,6 @@ class CorePost(Resource): ...@@ -82,7 +85,6 @@ class CorePost(Resource):
factory = Site(self) factory = Site(self)
reactor.listenTCP(port, factory) #@UndefinedVariable reactor.listenTCP(port, factory) #@UndefinedVariable
reactor.run() #@UndefinedVariable reactor.run() #@UndefinedVariable
################################################################################################## ##################################################################################################
# #
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment