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
......
...@@ -5,13 +5,13 @@ Created on 2011-10-03 ...@@ -5,13 +5,13 @@ Created on 2011-10-03
Common routing classes, regardless of whether used in HTTP or multiprocess context Common routing classes, regardless of whether used in HTTP or multiprocess context
''' '''
from collections import defaultdict from collections import defaultdict
from corepost import Response
from corepost.enums import Http, HttpHeader from corepost.enums import Http, HttpHeader
from corepost.utils import getMandatoryArgumentNames, convertToJson from corepost.utils import getMandatoryArgumentNames, convertToJson
from corepost.convert import convertForSerialization, generateXml from corepost.convert import convertForSerialization, generateXml
from corepost.filters import IRequestFilter, IResponseFilter from corepost.filters import IRequestFilter, IResponseFilter
from corepost import Response
from enums import MediaType from enums import MediaType
from formencode import FancyValidator, Invalid
from twisted.internet import reactor, defer from twisted.internet import reactor, defer
from twisted.web.http import parse_qs from twisted.web.http import parse_qs
from twisted.web.resource import Resource from twisted.web.resource import Resource
...@@ -19,6 +19,7 @@ from twisted.web.server import Site, NOT_DONE_YET ...@@ -19,6 +19,7 @@ from twisted.web.server import Site, NOT_DONE_YET
import re, copy, exceptions, json, yaml import re, copy, exceptions, json, yaml
from xml.etree import ElementTree from xml.etree import ElementTree
from xml.etree.ElementTree import Element from xml.etree.ElementTree import Element
from zope.interface.verify import verifyObject
class UrlRouter: class UrlRouter:
''' Common class for containing info related to routing a request to a function ''' ''' Common class for containing info related to routing a request to a function '''
...@@ -28,20 +29,21 @@ class UrlRouter: ...@@ -28,20 +29,21 @@ class UrlRouter:
__typeConverters = {"int":int,"float":float} __typeConverters = {"int":int,"float":float}
def __init__(self,f,url,methods,accepts,produces,cache): def __init__(self,f,url,methods,accepts,produces,cache):
self.__f = f
self.__url = url self.__url = url
self.__methods = methods if isinstance(methods,tuple) else (methods,) self.__methods = methods if isinstance(methods,tuple) else (methods,)
self.__accepts = accepts if isinstance(accepts,tuple) else (accepts,) self.__accepts = accepts if isinstance(accepts,tuple) else (accepts,)
self.__produces = produces self.__produces = produces
self.__cache = cache self.__cache = cache
self.__f = f
self.__argConverters = {} # dict of arg names -> group index self.__argConverters = {} # dict of arg names -> group index
self.__validators = {} self.__validators = {}
self.__mandatory = getMandatoryArgumentNames(f)[2:] self.__mandatory = getMandatoryArgumentNames(f)[2:]
def compileMatcherForFullUrl(self):
"""Compiles the regex matches once the URL has been updated to include the full path from the parent class"""
#parse URL into regex used for matching #parse URL into regex used for matching
m = UrlRouter.__urlMatcher.findall(url) m = UrlRouter.__urlMatcher.findall(self.url)
self.__matchUrl = "^%s$" % self.url
self.__matchUrl = "^%s$" % url
for match in m: for match in m:
if len(match[0]) == 0: if len(match[0]) == 0:
# string # string
...@@ -56,6 +58,7 @@ class UrlRouter: ...@@ -56,6 +58,7 @@ class UrlRouter:
self.__matcher = re.compile(self.__matchUrl) self.__matcher = re.compile(self.__matchUrl)
@property @property
def cache(self): def cache(self):
'''Indicates if this URL should be cached or not''' '''Indicates if this URL should be cached or not'''
...@@ -101,19 +104,31 @@ class UrlRouter: ...@@ -101,19 +104,31 @@ class UrlRouter:
if arg not in kwargs: if arg not in kwargs:
raise TypeError("Missing mandatory argument '%s'" % arg) raise TypeError("Missing mandatory argument '%s'" % arg)
return self.__f(instance,request,**kwargs) return self.__f(instance,request,**kwargs)
def __str__(self):
return "%s %s" % (self.url, self.methods)
class UrlRouterInstance():
"""Combines a UrlRouter with a class instance it should be executed against"""
def __init__(self,clazz,urlRouter):
self.clazz = clazz
self.urlRouter = urlRouter
def __str__(self):
return self.urlRouter.url
class CachedUrl: class CachedUrl:
''' '''
Used for caching URLs that have been already routed once before. Avoids the overhead Used for caching URLs that have been already routed once before. Avoids the overhead
of regex processing on every incoming call for commonly accessed REST URLs of regex processing on every incoming call for commonly accessed REST URLs
''' '''
def __init__(self,router,args): def __init__(self,urlRouterInstance,args):
self.__router = router self.__urlRouterInstance = urlRouterInstance
self.__args = args self.__args = args
@property @property
def router(self): def urlRouterInstance(self):
return self.__router return self.__urlRouterInstance
@property @property
def args(self): def args(self):
...@@ -124,16 +139,16 @@ class RequestRouter: ...@@ -124,16 +139,16 @@ class RequestRouter:
Class that handles request->method routing functionality to any type of resource Class that handles request->method routing functionality to any type of resource
''' '''
def __init__(self,urlContainer,schema=None,filters=()): def __init__(self,restServiceContainer,schema=None,filters=()):
''' '''
Constructor Constructor
''' '''
self.__urls = {Http.GET: defaultdict(dict),Http.POST: defaultdict(dict),Http.PUT: defaultdict(dict),Http.DELETE: defaultdict(dict)} self.__urls = {Http.GET: defaultdict(dict),Http.POST: defaultdict(dict),Http.PUT: defaultdict(dict),Http.DELETE: defaultdict(dict)}
self.__cachedUrls = {Http.GET: defaultdict(dict),Http.POST: defaultdict(dict),Http.PUT: defaultdict(dict),Http.DELETE: defaultdict(dict)} self.__cachedUrls = {Http.GET: defaultdict(dict),Http.POST: defaultdict(dict),Http.PUT: defaultdict(dict),Http.DELETE: defaultdict(dict)}
self.__routers = {} self.__urlRouterInstances = {}
self.__schema = schema self.__schema = schema
self.__registerRouters(urlContainer) self.__registerRouters(restServiceContainer)
self.__urlContainer = urlContainer self.__urlContainer = restServiceContainer
self.__requestFilters = [] self.__requestFilters = []
self.__responseFilters = [] self.__responseFilters = []
...@@ -155,61 +170,80 @@ class RequestRouter: ...@@ -155,61 +170,80 @@ class RequestRouter:
def path(self): def path(self):
return self.__path return self.__path
def __registerRouters(self,urlContainer): def __registerRouters(self,restServiceContainer):
"""Main method responsible for registering routers""" """Main method responsible for registering routers"""
from types import FunctionType from types import FunctionType
for _,func in urlContainer.__class__.__dict__.iteritems():
if type(func) == FunctionType and hasattr(func,'corepostRequestRouter'): for service in restServiceContainer.services:
rq = func.corepostRequestRouter # check if the service has a root path defined, which is optional
for method in rq.methods: rootPath = service.__class__.path if "path" in service.__class__.__dict__ else ""
for accepts in rq.accepts:
self.__urls[method][rq.url][accepts] = rq for key in service.__class__.__dict__:
self.__routers[func] = rq # needed so that we cdan lookup the router for a specific function func = service.__class__.__dict__[key]
# handle REST resources directly on the CorePost resource
def route(self,url,methods=(Http.GET,),accepts=MediaType.WILDCARD,produces=None,cache=True): if type(func) == FunctionType and hasattr(func,'corepostRequestRouter'):
'''Obsolete'''
raise RuntimeError("Do not @app.route() any more, as of 0.0.6 API has been re-designed around class methods, see docs and examples") # if specified, add class path to each function's path
rq = func.corepostRequestRouter
rq.url = "%s%s" % (rootPath,rq.url)
# remove trailing '/' to standardize URLs
if rq.url != "/" and rq.url[-1] == "/":
rq.url = rq.url[:-1]
# now that the full URL is set, compile the matcher for it
rq.compileMatcherForFullUrl()
for method in rq.methods:
for accepts in rq.accepts:
urlRouterInstance = UrlRouterInstance(service,rq)
self.__urls[method][rq.url][accepts] = urlRouterInstance
self.__urlRouterInstances[func] = urlRouterInstance # needed so that we can lookup the urlRouterInstance for a specific function
def getResponse(self,request): def getResponse(self,request):
"""Finds the appropriate router and dispatches the request to the registered function. Returns the appropriate Response object""" """Finds the appropriate instance and dispatches the request to the registered function. Returns the appropriate Response object"""
# see if already cached # see if already cached
response = None response = None
try: try:
if len(self.__requestFilters) > 0: if len(self.__requestFilters) > 0:
self.__filterRequests(request) self.__filterRequests(request)
# standardize URL and remove trailing "/" if necessary
standardized_postpath = request.postpath if (request.postpath[-1] != '' or request.postpath == ['']) else request.postpath[:-1]
path = '/' + '/'.join(standardized_postpath)
path = '/' + '/'.join(request.postpath)
contentType = MediaType.WILDCARD if HttpHeader.CONTENT_TYPE not in request.received_headers else request.received_headers[HttpHeader.CONTENT_TYPE] contentType = MediaType.WILDCARD if HttpHeader.CONTENT_TYPE not in request.received_headers else request.received_headers[HttpHeader.CONTENT_TYPE]
urlrouter, pathargs = None, None urlRouterInstance, pathargs = None, None
# fetch URL arguments <-> function from cache if hit at least once before # fetch URL arguments <-> function from cache if hit at least once before
if contentType in self.__cachedUrls[request.method][path]: if contentType in self.__cachedUrls[request.method][path]:
cachedUrl = self.__cachedUrls[request.method][path][contentType] cachedUrl = self.__cachedUrls[request.method][path][contentType]
urlrouter,pathargs = cachedUrl.router, cachedUrl.args urlRouterInstance,pathargs = cachedUrl.urlRouterInstance, cachedUrl.args
else: else:
# first time this URL is called # first time this URL is called
router = None instance = None
for contentTypeFunctions in self.__urls[request.method].values(): # go through all the URLs, pick up the ones matching by content type
# and then validate which ones match by path/argument to a particular UrlRouterInstance
for contentTypeInstances in self.__urls[request.method].values():
if contentType in contentTypeFunctions: if contentType in contentTypeInstances:
# there is an exact function for this incoming content type # there is an exact function for this incoming content type
router = contentTypeFunctions[contentType] instance = contentTypeInstances[contentType]
elif MediaType.WILDCARD in contentTypeFunctions: elif MediaType.WILDCARD in contentTypeInstances:
# fall back to any wildcard method # fall back to any wildcard method
router = contentTypeFunctions[MediaType.WILDCARD] instance = contentTypeInstances[MediaType.WILDCARD]
if router != None: if instance != None:
# see if the path arguments match up against any function @route definition # see if the path arguments match up against any function @route definition
args = router.getArguments(path) args = instance.urlRouter.getArguments(path)
if args != None: if args != None:
if router.cache: if instance.urlRouter.cache:
self.__cachedUrls[request.method][path][contentType] = CachedUrl(router, args) self.__cachedUrls[request.method][path][contentType] = CachedUrl(instance, args)
urlrouter,pathargs = router,args urlRouterInstance,pathargs = instance,args
break break
#actual call #actual call
if urlrouter != None and pathargs != None: if urlRouterInstance != None and pathargs != None:
allargs = copy.deepcopy(pathargs) allargs = copy.deepcopy(pathargs)
# handler for weird Twisted logic where PUT does not get form params # handler for weird Twisted logic where PUT does not get form params
# see: http://twistedmatrix.com/pipermail/twisted-web/2007-March/003338.html # see: http://twistedmatrix.com/pipermail/twisted-web/2007-March/003338.html
...@@ -227,7 +261,8 @@ class RequestRouter: ...@@ -227,7 +261,8 @@ class RequestRouter:
try: try:
# if POST/PUT, check if we need to automatically parse JSON # if POST/PUT, check if we need to automatically parse JSON
self.__parseRequestData(request) self.__parseRequestData(request)
val = urlrouter.call(self.__urlContainer,request,**allargs) urlRouter = urlRouterInstance.urlRouter
val = urlRouter.call(urlRouterInstance.clazz,request,**allargs)
#handle Deferreds natively #handle Deferreds natively
if isinstance(val,defer.Deferred): if isinstance(val,defer.Deferred):
......
...@@ -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