Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
C
corepost
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Commits
Open sidebar
nexlab
corepost
Commits
4ecf20e7
Commit
4ecf20e7
authored
Jun 27, 2012
by
Jacek Furmankiewicz
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
https://github.com/kaosat-dev/corepost
parents
b2b41a31
4204008e
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
109 additions
and
61 deletions
+109
-61
convert.py
corepost/convert.py
+41
-14
enums.py
corepost/enums.py
+7
-1
routing.py
corepost/routing.py
+58
-37
utils.py
corepost/utils.py
+3
-9
No files found.
corepost/convert.py
View file @
4ecf20e7
...
...
@@ -7,12 +7,23 @@ for JSON/XML/YAML output
'''
import
collections
import
logging
import
json
from
UserDict
import
DictMixin
from
twisted.python
import
log
advanced_json
=
False
try
:
import
jsonpickle
advanced_json
=
True
except
ImportError
:
pass
primitives
=
(
int
,
long
,
float
,
bool
,
str
,
unicode
)
def
convertForSerialization
(
obj
):
"""Converts anything (clas,tuples,list) to the safe serializable equivalent"""
try
:
if
type
(
obj
)
in
primitives
:
# no conversion
return
obj
...
...
@@ -29,6 +40,9 @@ def convertForSerialization(obj):
else
:
# return as-is
return
obj
except
AttributeError
as
ex
:
log
.
msg
(
ex
,
logLevel
=
logging
.
WARN
)
return
obj
def
convertClassToDict
(
clazz
):
"""Converts a class to a dictionary"""
...
...
@@ -49,6 +63,19 @@ def traverseDict(dictObject):
return
newDict
def
convertToJson
(
obj
):
"""Converts to JSON, including Python classes that are not JSON serializable by default"""
if
advanced_json
:
try
:
return
jsonpickle
.
encode
(
obj
,
unpicklable
=
False
)
except
Exception
as
ex
:
raise
RuntimeError
(
str
(
ex
))
try
:
return
json
.
dumps
(
obj
)
except
Exception
as
ex
:
raise
RuntimeError
(
str
(
ex
))
def
generateXml
(
obj
):
"""Generates basic XML from an object that has already been converted for serialization"""
if
isinstance
(
obj
,
dict
)
or
isinstance
(
obj
,
DictMixin
):
...
...
corepost/enums.py
View file @
4ecf20e7
...
...
@@ -4,18 +4,24 @@ Common enums
@author: jacekf
'''
class
Http
:
"""Enumerates HTTP methods"""
GET
=
"GET"
POST
=
"POST"
PUT
=
"PUT"
DELETE
=
"DELETE"
OPTIONS
=
"OPTIONS"
HEAD
=
"HEAD"
PATCH
=
"PATCH"
class
HttpHeader
:
"""Enumerates common HTTP headers"""
CONTENT_TYPE
=
"content-type"
ACCEPT
=
"accept"
class
MediaType
:
"""Enumerates media types"""
WILDCARD
=
"*/*"
...
...
corepost/routing.py
View file @
4ecf20e7
...
...
@@ -7,23 +7,31 @@ Common routing classes, regardless of whether used in HTTP or multiprocess conte
from
collections
import
defaultdict
from
corepost
import
Response
,
RESTException
from
corepost.enums
import
Http
,
HttpHeader
from
corepost.utils
import
getMandatoryArgumentNames
,
convertToJson
,
safeDictUpdate
from
corepost.convert
import
convertForSerialization
,
generateXml
from
corepost.utils
import
getMandatoryArgumentNames
,
safeDictUpdate
from
corepost.convert
import
convertForSerialization
,
generateXml
,
convertToJson
from
corepost.filters
import
IRequestFilter
,
IResponseFilter
from
enums
import
MediaType
from
twisted.internet
import
defer
from
twisted.web.http
import
parse_qs
from
twisted.python
import
log
import
re
,
copy
,
exceptions
,
json
,
yaml
,
logging
import
re
,
copy
,
exceptions
,
yaml
,
json
,
logging
from
xml.etree
import
ElementTree
import
uuid
advanced_json
=
False
try
:
import
jsonpickle
advanced_json
=
True
except
ImportError
:
pass
class
UrlRouter
:
''' Common class for containing info related to routing a request to a function '''
__urlMatcher
=
re
.
compile
(
r"<(int|float|):?([^/]+)>"
)
__urlRegexReplace
=
{
""
:
r"(?P<arg>([^/]+))"
,
"int"
:
r"(?P<arg>\d+)"
,
"float"
:
r"(?P<arg>\d+.?\d*)"
}
__typeConverters
=
{
"int"
:
int
,
"float"
:
float
}
__urlMatcher
=
re
.
compile
(
r"<(int|float|
uuid|
):?([^/]+)>"
)
__urlRegexReplace
=
{
""
:
r"(?P<arg>([^/]+))"
,
"int"
:
r"(?P<arg>\d+)"
,
"float"
:
r"(?P<arg>\d+.?\d*)"
,
"uuid"
:
r"(?P<arg>[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})"
}
__typeConverters
=
{
"int"
:
int
,
"float"
:
float
,
"uuid"
:
uuid
.
UUID
}
def
__init__
(
self
,
f
,
url
,
methods
,
accepts
,
produces
,
cache
):
self
.
__f
=
f
...
...
@@ -140,16 +148,16 @@ class RequestRouter:
'''
Constructor
'''
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
.
__urls
=
{
Http
.
GET
:
defaultdict
(
dict
),
Http
.
POST
:
defaultdict
(
dict
),
Http
.
PUT
:
defaultdict
(
dict
),
Http
.
DELETE
:
defaultdict
(
dict
)
,
Http
.
OPTIONS
:
defaultdict
(
dict
),
Http
.
PATCH
:
defaultdict
(
dict
),
Http
.
HEAD
:
defaultdict
(
dict
)
}
self
.
__cachedUrls
=
{
Http
.
GET
:
defaultdict
(
dict
),
Http
.
POST
:
defaultdict
(
dict
),
Http
.
PUT
:
defaultdict
(
dict
),
Http
.
DELETE
:
defaultdict
(
dict
)
,
Http
.
OPTIONS
:
defaultdict
(
dict
),
Http
.
PATCH
:
defaultdict
(
dict
),
Http
.
HEAD
:
defaultdict
(
dict
)
}
self
.
__urlRouterInstances
=
{}
self
.
__schema
=
schema
self
.
__urlsMehods
=
{}
self
.
__registerRouters
(
restServiceContainer
)
self
.
__urlContainer
=
restServiceContainer
self
.
__requestFilters
=
[]
self
.
__responseFilters
=
[]
#filters
if
filters
!=
None
:
for
webFilter
in
filters
:
valid
=
False
...
...
@@ -167,7 +175,7 @@ class RequestRouter:
def
path
(
self
):
return
self
.
__path
def
__registerRouters
(
self
,
restServiceContainer
):
def
__registerRouters
(
self
,
restServiceContainer
):
"""Main method responsible for registering routers"""
from
types
import
FunctionType
...
...
@@ -179,24 +187,27 @@ class RequestRouter:
func
=
service
.
__class__
.
__dict__
[
key
]
# handle REST resources directly on the CorePost resource
if
type
(
func
)
==
FunctionType
and
hasattr
(
func
,
'corepostRequestRouter'
):
# if specified, add class path to each function's path
rq
=
func
.
corepostRequestRouter
#workaround for multiple passes of __registerRouters (for unit tests etc)
if
not
hasattr
(
rq
,
'urlAdapted'
):
rq
.
url
=
"
%
s
%
s"
%
(
rootPath
,
rq
.
url
)
# remove first and trailing '/' to standardize URLs
start
=
1
if
rq
.
url
[
0
:
1
]
==
"/"
else
0
end
=
-
1
if
rq
.
url
[
len
(
rq
.
url
)
-
1
]
==
'/'
else
len
(
rq
.
url
)
rq
.
url
=
rq
.
url
[
start
:
end
]
setattr
(
rq
,
'urlAdapted'
,
True
)
# 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
if
self
.
__urlsMehods
.
get
(
rq
.
url
,
None
)
is
None
:
self
.
__urlsMehods
[
rq
.
url
]
=
[]
self
.
__urlsMehods
[
rq
.
url
]
.
append
(
method
)
def
getResponse
(
self
,
request
):
"""Finds the appropriate instance and dispatches the request to the registered function. Returns the appropriate Response object"""
...
...
@@ -236,11 +247,11 @@ class RequestRouter:
# see if the path arguments match up against any function @route definition
args
=
instance
.
urlRouter
.
getArguments
(
path
)
if
args
!=
None
:
if
instance
.
urlRouter
.
cache
:
self
.
__cachedUrls
[
request
.
method
][
path
][
contentType
]
=
CachedUrl
(
instance
,
args
)
urlRouterInstance
,
pathargs
=
instance
,
args
break
#actual call
if
urlRouterInstance
!=
None
and
pathargs
!=
None
:
allargs
=
copy
.
deepcopy
(
pathargs
)
...
...
@@ -286,6 +297,10 @@ class RequestRouter:
log
.
err
(
ex
)
response
=
self
.
__createErrorResponse
(
request
,
500
,
"Unexpected server error:
%
s
\n
%
s"
%
(
type
(
ex
),
ex
))
#if a url is defined, but not the requested method
elif
not
request
.
method
in
self
.
__urlsMehods
.
get
(
path
,
[])
and
self
.
__urlsMehods
.
get
(
path
,
[])
!=
[]:
response
=
self
.
__createErrorResponse
(
request
,
501
,
""
)
else
:
log
.
msg
(
"URL
%
s not found"
%
path
,
logLevel
=
logging
.
WARN
)
response
=
self
.
__createErrorResponse
(
request
,
404
,
"URL '
%
s' not found
\n
"
%
request
.
path
)
...
...
@@ -305,9 +320,7 @@ class RequestRouter:
Takes care of automatically rendering the response and converting it to appropriate format (text,XML,JSON,YAML)
depending on what the caller can accept. Returns Response
"""
if
isinstance
(
response
,
str
):
return
Response
(
code
,
response
,{
HttpHeader
.
CONTENT_TYPE
:
MediaType
.
TEXT_PLAIN
})
elif
isinstance
(
response
,
Response
):
if
isinstance
(
response
,
Response
):
return
response
else
:
(
content
,
contentType
)
=
self
.
__convertObjectToContentType
(
request
,
response
)
...
...
@@ -318,20 +331,27 @@ class RequestRouter:
Takes care of converting an object (non-String) response to the appropriate format, based on the what the caller can accept.
Returns a tuple of (content,contentType)
"""
obj
=
convertForSerialization
(
obj
)
if
HttpHeader
.
ACCEPT
in
request
.
received_headers
:
accept
=
request
.
received_headers
[
HttpHeader
.
ACCEPT
]
if
MediaType
.
APPLICATION_JSON
in
accept
:
if
not
advanced_json
:
obj
=
convertForSerialization
(
obj
)
return
(
convertToJson
(
obj
),
MediaType
.
APPLICATION_JSON
)
elif
MediaType
.
TEXT_YAML
in
accept
:
obj
=
convertForSerialization
(
obj
)
return
(
yaml
.
dump
(
obj
),
MediaType
.
TEXT_YAML
)
elif
MediaType
.
APPLICATION_XML
in
accept
or
MediaType
.
TEXT_XML
in
accept
:
obj
=
convertForSerialization
(
obj
)
return
(
generateXml
(
obj
),
MediaType
.
APPLICATION_XML
)
else
:
# no idea, let's do JSON
if
not
advanced_json
:
obj
=
convertForSerialization
(
obj
)
return
(
convertToJson
(
obj
),
MediaType
.
APPLICATION_JSON
)
else
:
if
not
advanced_json
:
obj
=
convertForSerialization
(
obj
)
# called has no accept header, let's default to JSON
return
(
convertToJson
(
obj
),
MediaType
.
APPLICATION_JSON
)
...
...
@@ -361,19 +381,20 @@ class RequestRouter:
'''Automatically parses JSON,XML,YAML if present'''
if
request
.
method
in
(
Http
.
POST
,
Http
.
PUT
)
and
HttpHeader
.
CONTENT_TYPE
in
request
.
received_headers
.
keys
():
contentType
=
request
.
received_headers
[
"content-type"
]
data
=
request
.
content
.
read
()
if
contentType
==
MediaType
.
APPLICATION_JSON
:
try
:
request
.
json
=
json
.
loads
(
request
.
content
.
read
())
request
.
json
=
json
.
loads
(
data
)
if
data
else
{}
except
Exception
as
ex
:
raise
TypeError
(
"Unable to parse JSON body:
%
s"
%
ex
)
elif
contentType
in
(
MediaType
.
APPLICATION_XML
,
MediaType
.
TEXT_XML
):
try
:
request
.
xml
=
ElementTree
.
XML
(
request
.
content
.
read
()
)
request
.
xml
=
ElementTree
.
XML
(
data
)
except
Exception
as
ex
:
raise
TypeError
(
"Unable to parse XML body:
%
s"
%
ex
)
elif
contentType
==
MediaType
.
TEXT_YAML
:
try
:
request
.
yaml
=
yaml
.
safe_load
(
request
.
content
.
read
()
)
request
.
yaml
=
yaml
.
safe_load
(
data
)
except
Exception
as
ex
:
raise
TypeError
(
"Unable to parse YAML body:
%
s"
%
ex
)
...
...
corepost/utils.py
View file @
4ecf20e7
...
...
@@ -2,7 +2,7 @@
Various CorePost utilities
'''
from
inspect
import
getargspec
import
json
def
getMandatoryArgumentNames
(
f
):
'''Returns a tuple of the mandatory arguments required in a function'''
...
...
@@ -12,16 +12,11 @@ def getMandatoryArgumentNames(f):
else
:
return
args
[
0
:
len
(
args
)
-
len
(
defaults
)]
def
getRouterKey
(
method
,
url
):
'''Returns the common key used to represent a function that a request can be routed to'''
return
"
%
s
%
s"
%
(
method
,
url
)
def
convertToJson
(
obj
):
"""Converts to JSON, including Python classes that are not JSON serializable by default"""
try
:
return
json
.
dumps
(
obj
)
except
Exception
as
ex
:
raise
RuntimeError
(
str
(
ex
))
def
checkExpectedInterfaces
(
objects
,
expectedInterface
):
"""Verifies that all the objects implement the expected interface"""
...
...
@@ -33,4 +28,3 @@ def safeDictUpdate(dictObject,key,value):
"""Only adds a key to a dictionary. If key exists, it leaves it untouched"""
if
key
not
in
dictObject
:
dictObject
[
key
]
=
value
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment