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
5dc9e4cb
Commit
5dc9e4cb
authored
Sep 02, 2011
by
Jacek Furmankiewicz
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
POST returns 201 by default, missing mandatory args throw 400, initial validator investigation
parent
31c6946f
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
123 additions
and
34 deletions
+123
-34
arguments.py
corepost/test/arguments.py
+6
-0
url_routing.feature
corepost/test/feature/url_routing.feature
+4
-2
validate.feature
corepost/test/feature/validate.feature
+18
-0
misc.py
corepost/test/misc.py
+24
-0
utils.py
corepost/utils.py
+13
-0
web.py
corepost/web.py
+58
-32
No files found.
corepost/test/arguments.py
View file @
5dc9e4cb
...
@@ -5,6 +5,7 @@ Argument extraction tests
...
@@ -5,6 +5,7 @@ Argument extraction tests
from
corepost.web
import
CorePost
from
corepost.web
import
CorePost
from
corepost.enums
import
Http
from
corepost.enums
import
Http
from
formencode
import
Schema
,
validators
app
=
CorePost
()
app
=
CorePost
()
...
@@ -13,5 +14,10 @@ def test(request,intarg,floatarg,stringarg,**kwargs):
...
@@ -13,5 +14,10 @@ def test(request,intarg,floatarg,stringarg,**kwargs):
args
=
(
intarg
,
floatarg
,
stringarg
)
args
=
(
intarg
,
floatarg
,
stringarg
)
return
"
%
s"
%
map
(
lambda
x
:
(
type
(
x
),
x
),
args
)
return
"
%
s"
%
map
(
lambda
x
:
(
type
(
x
),
x
),
args
)
@
app
.
route
(
"/validate/<int:rootId>/children"
,
Http
.
POST
)
@
app
.
validate
(
childid
=
validators
.
String
(
not_empty
=
True
))
def
post
(
request
,
rootId
,
childId
,
**
kwargs
):
return
"
%
s -
%
s -
%
s"
%
(
rootId
,
childId
,
kwargs
)
def
run_app_arguments
():
def
run_app_arguments
():
app
.
run
(
8082
)
app
.
run
(
8082
)
\ No newline at end of file
corepost/test/feature/url_routing.feature
View file @
5dc9e4cb
...
@@ -26,7 +26,7 @@ Feature: URL routing
...
@@ -26,7 +26,7 @@ Feature: URL routing
Scenario
:
Single resource - POST
Scenario
:
Single resource - POST
Given 'home_resource' is running
Given 'home_resource' is running
When as user 'None
:
None' I POST 'http
:
//127.0.0.1
:
8080/post'
with
'test=value&test2=value2'
When as user 'None
:
None' I POST 'http
:
//127.0.0.1
:
8080/post'
with
'test=value&test2=value2'
Then
I expect HTTP code 20
0
Then
I expect HTTP code 20
1
And I expect content contains '{'test'
:
'value', 'test2'
:
'value2'}'
And I expect content contains '{'test'
:
'value', 'test2'
:
'value2'}'
@single
@single_put
@single
@single_put
...
@@ -46,9 +46,11 @@ Feature: URL routing
...
@@ -46,9 +46,11 @@ Feature: URL routing
Scenario
:
Single resource - multiple methods at same URL
Scenario
:
Single resource - multiple methods at same URL
Given 'home_resource' is running
Given 'home_resource' is running
When as user 'None
:
None' I POST 'http
:
//127.0.0.1
:
8080/postput'
with
'test=value&test2=value2'
When as user 'None
:
None' I POST 'http
:
//127.0.0.1
:
8080/postput'
with
'test=value&test2=value2'
Then
I expect HTTP code 200
# POST return 201 by default
Then
I expect HTTP code 201
And I expect content contains '{'test'
:
'value', 'test2'
:
'value2'}'
And I expect content contains '{'test'
:
'value', 'test2'
:
'value2'}'
When as user 'None
:
None' I PUT 'http
:
//127.0.0.1
:
8080/postput'
with
'test=value&test2=value2'
When as user 'None
:
None' I PUT 'http
:
//127.0.0.1
:
8080/postput'
with
'test=value&test2=value2'
# PUT return 201 by default
Then
I expect HTTP code 200
Then
I expect HTTP code 200
And I expect content contains '{'test'
:
'value', 'test2'
:
'value2'}'
And I expect content contains '{'test'
:
'value', 'test2'
:
'value2'}'
...
...
corepost/test/feature/validate.feature
0 → 100644
View file @
5dc9e4cb
Using step definitions from
:
'../steps'
@validate
Feature
:
Argument Validators
CorePost should be able to correctly validate path, query and form arguments
@validate
Scenario Outline
:
Path argument extraction
Given 'arguments' is running
When as user 'None
:
None' I POST 'http
:
//127.0.0.1
:
8082/validate/23/children'
with
'<args>'
Then
I expect HTTP code
<code>
And
I expect content contains '<content>'
Examples
:
|
args
|
code
|
content
|
|
childId=jacekf
|
201
|
23
-
jacekf
-
{}
|
|
childId=jacekf&otherId=test
|
201
|
23
-
jacekf
-
{'otherId':
'test'}
|
\ No newline at end of file
corepost/test/misc.py
0 → 100644
View file @
5dc9e4cb
'''
Created on 2011-09-02
Misc tests
@author: jacekf
'''
def
dec
(
f
):
print
"DEC"
def
wrap
():
print
"WRAP"
v
=
f
()
return
v
return
wrap
@
dec
def
test
():
print
"TEST3232"
if
__name__
==
"__main__"
:
test
()
test
()
test
()
\ No newline at end of file
corepost/utils.py
0 → 100644
View file @
5dc9e4cb
'''
Various CorePost utilities
'''
from
inspect
import
getargspec
def
getMandatoryArgumentNames
(
f
):
'''Returns a tuple of the mandatory arguments required in a function'''
args
,
_
,
_
,
defaults
=
getargspec
(
f
)
if
defaults
==
None
:
return
args
else
:
return
args
[
0
:
len
(
args
)
-
len
(
defaults
)]
\ No newline at end of file
corepost/web.py
View file @
5dc9e4cb
...
@@ -3,7 +3,7 @@ Main server classes
...
@@ -3,7 +3,7 @@ Main server classes
@author: jacekf
@author: jacekf
'''
'''
import
re
,
copy
import
re
,
copy
,
exceptions
from
twisted.internet
import
reactor
,
defer
from
twisted.internet
import
reactor
,
defer
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
...
@@ -11,15 +11,17 @@ from twisted.web.http import parse_qs
...
@@ -11,15 +11,17 @@ from twisted.web.http import parse_qs
from
collections
import
defaultdict
from
collections
import
defaultdict
from
enums
import
MediaType
from
enums
import
MediaType
from
corepost.enums
import
Http
from
corepost.enums
import
Http
from
corepost.utils
import
getMandatoryArgumentNames
from
formencode
import
validators
,
Schema
,
Validator
class
RequestRouter
:
class
RequestRouter
:
""" 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 '''
__urlMatcher
=
re
.
compile
(
r"<(int|float|):?([a-zA-Z0-9]+)>"
)
__urlMatcher
=
re
.
compile
(
r"<(int|float|):?([a-zA-Z0-9]+)>"
)
__urlRegexReplace
=
{
""
:
r"(?P<arg>.+)"
,
"int"
:
r"(?P<arg>\d+)"
,
"float"
:
r"(?P<arg>\d+.?\d*)"
}
__urlRegexReplace
=
{
""
:
r"(?P<arg>.+)"
,
"int"
:
r"(?P<arg>\d+)"
,
"float"
:
r"(?P<arg>\d+.?\d*)"
}
__typeConverters
=
{
"int"
:
int
,
"float"
:
float
}
__typeConverters
=
{
"int"
:
int
,
"float"
:
float
}
def
__init__
(
self
,
f
,
url
,
method
,
accepts
,
produces
,
cache
,
schema
):
def
__init__
(
self
,
f
,
url
,
method
,
accepts
,
produces
,
cache
):
self
.
__url
=
url
self
.
__url
=
url
self
.
__method
=
method
self
.
__method
=
method
self
.
__accepts
=
accepts
self
.
__accepts
=
accepts
...
@@ -27,7 +29,9 @@ class RequestRouter:
...
@@ -27,7 +29,9 @@ class RequestRouter:
self
.
__cache
=
cache
self
.
__cache
=
cache
self
.
__f
=
f
self
.
__f
=
f
self
.
__argConverters
=
{}
# dict of arg names -> group index
self
.
__argConverters
=
{}
# dict of arg names -> group index
self
.
__schema
=
schema
self
.
__schema
=
None
self
.
__validators
=
{}
self
.
__mandatory
=
getMandatoryArgumentNames
(
f
)[
1
:]
#parse URL into regex used for matching
#parse URL into regex used for matching
m
=
RequestRouter
.
__urlMatcher
.
findall
(
url
)
m
=
RequestRouter
.
__urlMatcher
.
findall
(
url
)
...
@@ -49,19 +53,23 @@ class RequestRouter:
...
@@ -49,19 +53,23 @@ class RequestRouter:
@
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'''
return
self
.
__cache
return
self
.
__cache
@
property
@
property
def
schema
(
self
):
def
schema
(
self
):
"""Returns the formencode Schema, if this URL uses custom validation schema"""
''''Returns the formencode Schema, if this URL uses custom validation schema'''
return
self
.
__schema
return
self
.
__schema
def
addValidator
(
self
,
fieldName
,
validator
):
'''Adds additional field-specific formencode validators'''
self
.
__validators
[
fieldName
]
=
validator
def
getArguments
(
self
,
url
):
def
getArguments
(
self
,
url
):
"""
'''
Returns None if nothing matched (i.e. URL does not match), empty dict if no args found (i,e, static URL)
Returns None if nothing matched (i.e. URL does not match), empty dict if no args found (i,e, static URL)
or dict with arg/values for dynamic URLs
or dict with arg/values for dynamic URLs
"""
'''
g
=
self
.
__matcher
.
search
(
url
)
g
=
self
.
__matcher
.
search
(
url
)
if
g
!=
None
:
if
g
!=
None
:
args
=
g
.
groupdict
()
args
=
g
.
groupdict
()
...
@@ -76,14 +84,17 @@ class RequestRouter:
...
@@ -76,14 +84,17 @@ class RequestRouter:
return
None
return
None
def
call
(
self
,
request
,
**
kwargs
):
def
call
(
self
,
request
,
**
kwargs
):
"""Forwards call to underlying method"""
'''Forwards call to underlying method'''
for
arg
in
self
.
__mandatory
:
if
arg
not
in
kwargs
:
raise
TypeError
(
"Missing mandatory argument '
%
s'"
%
arg
)
return
self
.
__f
(
request
,
**
kwargs
)
return
self
.
__f
(
request
,
**
kwargs
)
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
,
router
,
args
):
self
.
__router
=
router
self
.
__router
=
router
self
.
__args
=
args
self
.
__args
=
args
...
@@ -110,6 +121,7 @@ class CorePost(Resource):
...
@@ -110,6 +121,7 @@ class CorePost(Resource):
self
.
__urls
=
defaultdict
(
dict
)
self
.
__urls
=
defaultdict
(
dict
)
self
.
__cachedUrls
=
defaultdict
(
dict
)
self
.
__cachedUrls
=
defaultdict
(
dict
)
self
.
__methods
=
{}
self
.
__methods
=
{}
self
.
__routers
=
{}
self
.
__path
=
path
self
.
__path
=
path
self
.
__schema
=
schema
self
.
__schema
=
schema
...
@@ -117,21 +129,31 @@ class CorePost(Resource):
...
@@ -117,21 +129,31 @@ class CorePost(Resource):
def
path
(
self
):
def
path
(
self
):
return
self
.
__path
return
self
.
__path
def
__registerFunction
(
self
,
f
,
url
,
methods
,
accepts
,
produces
,
cache
,
schema
):
def
__registerFunction
(
self
,
f
,
url
,
methods
,
accepts
,
produces
,
cache
):
if
f
not
in
self
.
__methods
.
values
():
if
f
not
in
self
.
__methods
.
values
():
if
not
isinstance
(
methods
,(
list
,
tuple
)):
if
not
isinstance
(
methods
,(
list
,
tuple
)):
methods
=
(
methods
,)
methods
=
(
methods
,)
for
method
in
methods
:
for
method
in
methods
:
rq
=
RequestRouter
(
f
,
url
,
method
,
accepts
,
produces
,
cache
,
schema
)
rq
=
RequestRouter
(
f
,
url
,
method
,
accepts
,
produces
,
cache
)
self
.
__urls
[
method
][
url
]
=
rq
self
.
__urls
[
method
][
url
]
=
rq
self
.
__routers
[
f
]
=
rq
# needed so that we can lookup the router for a specific function
self
.
__methods
[
url
]
=
f
self
.
__methods
[
url
]
=
f
def
route
(
self
,
url
,
methods
=
(
Http
.
GET
,),
accepts
=
MediaType
.
WILDCARD
,
produces
=
None
,
cache
=
True
,
schema
=
None
):
def
route
(
self
,
url
,
methods
=
(
Http
.
GET
,),
accepts
=
MediaType
.
WILDCARD
,
produces
=
None
,
cache
=
True
):
"""Main decorator for registering REST functions """
"""Main decorator for registering REST functions """
def
wrap
(
f
):
def
wrap
(
f
,
*
args
,
**
kwargs
):
self
.
__registerFunction
(
f
,
url
,
methods
,
accepts
,
produces
,
cache
,
schema
)
self
.
__registerFunction
(
f
,
url
,
methods
,
accepts
,
produces
,
cache
)
return
f
return
wrap
def
validate
(
self
,
schema
=
None
,
**
kwargs
):
'''
Main decorator for registering additional validators for incoming URL arguments
'''
def
wrap
(
f
,
**
kwargs
):
print
kwargs
return
f
return
f
return
wrap
return
wrap
...
@@ -187,27 +209,31 @@ class CorePost(Resource):
...
@@ -187,27 +209,31 @@ class CorePost(Resource):
# if POST/PUT, check if we need to automatically parse JSON
# if POST/PUT, check if we need to automatically parse JSON
# TODO
# TODO
#validate input against formencode schema if defined
self
.
__validateArguments
(
urlrouter
,
request
,
allargs
)
#handle Deferreds natively
#handle Deferreds natively
try
:
val
=
urlrouter
.
call
(
request
,
**
allargs
)
val
=
urlrouter
.
call
(
request
,
**
allargs
)
if
isinstance
(
val
,
defer
.
Deferred
):
if
isinstance
(
val
,
defer
.
Deferred
):
# we assume the method will call request.finish()
# we assume the method will call request.finish()
return
NOT_DONE_YET
return
NOT_DONE_YET
else
:
else
:
#special logic for POST to return 201 (created)
if
request
.
method
==
Http
.
POST
:
if
hasattr
(
request
,
'code'
):
if
request
.
code
==
200
:
request
.
setResponseCode
(
201
)
else
:
request
.
setResponseCode
(
201
)
return
val
return
val
except
exceptions
.
TypeError
as
ex
:
return
self
.
__renderError
(
request
,
400
,
"
%
s"
%
ex
)
except
Exception
as
ex
:
return
self
.
__renderError
(
request
,
500
,
"Unexpected server error:
%
s"
%
type
(
ex
))
else
:
else
:
return
self
.
__renderError
(
request
,
404
,
"URL '
%
s' not found
\n
"
%
request
.
path
)
return
self
.
__renderError
(
request
,
404
,
"URL '
%
s' not found
\n
"
%
request
.
path
)
def
__validateArguments
(
self
,
urlrouter
,
request
,
allargs
):
schema
=
urlrouter
.
schema
if
urlrouter
.
schema
!=
None
else
self
.
__schema
if
schema
!=
None
:
from
formencode
import
Schema
def
__renderError
(
self
,
request
,
code
,
message
):
def
__renderError
(
self
,
request
,
code
,
message
):
"""Common method for rendering errors"""
"""Common method for rendering errors"""
request
.
setResponseCode
(
code
)
request
.
setResponseCode
(
code
)
...
...
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