Web Service calls and operations support
Introduction
Once the Python extension analysis is finished, the analyzer will log the final number of web service call and operation objects created for each framework.
Generic service operations
Python Web Service GET/POST/PUT/DELETE operation objects will be used as generic objects for new supported frameworks implementing APIs to access web services on server side.
For now, these following framework have been developed with the generic objects.
- CherryPy
- FastAPI
- Sanic
- Bottle
Generic service requests
Python GET GET/POST/PUT/DELETE service request objects will be used as generic objects for new supported frameworks implementing APIs to access web services on client side.
Aiohttp
The following code will issue a http get to the url ‘https://api.github.com/events' :
import aiohttp
session = aiohttp.ClientSession()
res = session.get('https://api.github.com/events')
The aiohttp module can be also used in server mode, implementing web service operations
from aiohttp import web
async def handler(request):
return web.Response(text="Welcome in Python")
app = web.Application()
app.router.add_get('/index', handler)
web.run_app(app)
In this case a Web Service Operation object associated to the function (coroutine) handler will be generated similar to the example for flask given below.
Bottle
Summary table of supported features for Bottle web framework.
Supported API (Bottle) |
Link type |
Caller |
Callee |
Remarks |
---|---|---|---|---|
bottle.Bottle() |
N/A |
N/A |
N/A |
|
Supported API decorators ({app}: bottle.Bottle) |
Link type |
Caller |
Callee |
Remarks |
---|---|---|---|---|
@{app}.get() |
CallLink |
Python Web Service GET Operation |
Python Method |
|
@{app}.post() |
CallLink |
Python Web Service POST Operation |
Python Method |
|
@{app}.put() |
CallLink |
Python Web Service PUT Operation |
Python Method |
|
@{app}.delete() |
CallLink |
Python Web Service DELETE Operation |
Python Method |
|
@{app}.route() |
CallLink |
Python Web Service {GET,PUT,POST,DELETE} Operation |
Python Method |
default operation is ‘GET’ |
Supported API decorator with implicit instantiation |
Link type |
Caller |
Callee |
Remarks |
---|---|---|---|---|
@get() |
CallLink |
Python Web Service GET Operation |
Python Method |
|
@post() |
CallLink |
Python Web Service POST Operation |
Python Method |
|
@put() |
CallLink |
Python Web Service PUT Operation |
Python Method |
|
@delete() |
CallLink |
Python Web Service DELETE Operation |
Python Method |
|
@route() |
CallLink |
Python Web Service {GET,PUT,POST,DELETE} Operation |
Python Method |
default operation is ‘GET’ |
First example :
from bottle import Bottle
app = Bottle()
# using get decorator
@app.get("/")
def HelloWorld():
return {"message":"Hello World"}
# using generic route decorator
@app.route("/Goodbye",method="GET")
def Goodbye():
return {"message":"Goodbye World"}
# routing with parameter value
@app.get("/items/<item_id:int>")
def read_item(item_id: int):
return {"item_id":item_id}
Result:
Second example with implicit instantiations:
from bottle import get,route
# using get decorator
@get("/")
def HelloWorld():
return {"message":"Hello World"}
# using generic route decorator
@route("/Goodbye",method="GET")
def Goodbye():
return {"message":"Goodbye World"}
# routing with parameter value
@get("/items/<item_id:int>")
def read_item(item_id: int):
return {"item_id":item_id}
Result:
Third example:
from bottle import post, put, delete
@post("/users/")
async def create_User(user):
return user
@put("/users/")
async def update_User(user):
return user
@delete("/users/")
async def remove_User(user):
return user
Result:
CherryPy
Supported APIs (Cherrypy) |
Link Type |
Caller |
Callee |
Remarks |
---|---|---|---|---|
@cherrypy.expose (on method) |
callLink |
Python Web Service Get Operation |
Python Method |
|
aliases: parameters given to decorator (on method) |
callLink |
Python Web Service Get Operation |
Python Method |
Generates extra routing URL based on aliases name |
method.expose = True |
callLink |
Python Web Service Get Operation |
Python Method |
Equivalent to @cherrypy.expose (on method and unexposed class) |
cherrypy.quickstart |
N/A |
N/A |
N/A |
Instantiate undecorated class with page handler method (class) |
cherrypy.quickstart |
N/A |
N/A |
N/A |
Optionally alters routing URL (str) |
cherrypy.quickstart |
N/A |
N/A |
N/A |
Optionally defines configurations (dict) |
@cherrypy.expose (on class) |
CallLink |
Python Web Service (Get/Put/Post/Delete) Operation |
Python Method |
Exposed classes only accept method named GET, PUT, POST and DELETE |
cherrypy.dispatch.MethodDispatcher |
N/A |
N/A |
N/A |
Basic support for request.dispatcher |
Example of creation of a GET operation:
import cherrypy
class StringGenerator(object):
@cherrypy.expose
def index(self):
return "Hello world!"
Example link from ’exposed’ method index:
The CherryPy framework implicitly creates an extra GET operation with url “/” (in addition to “index/”) when the index method is exposed. Example of links created from combination of cherrypy.quickstart(), “exposed class” and cherrypy.dispatch.MethodDispatcher() the routing URL is defined as the key in a configuration dictionary that calls the dispatcher:
Limitation: Only the standard dispatcher “cherrypy.dispatch.MethodDispatcher()” is supported, i.e., no custom dispatcher is currently supported.
Django
Note: Django REST is highly dependent on the original Django. APIs and features specific to Django REST will be highlighted. Summary table of supported features for Django frameworks.
Supported API |
contributes to URL routing | Links route to handler |
determines of Http method | Mandatory arguments | Remarks |
Import shortcuts |
---|---|---|---|---|---|---|
django.urls.conf.path() | Yes | Yes | No | route:str handler:obj |
django.urls.path | |
django.urls.conf.re_path() | Yes | Yes | No | route:str handler:obj |
equivalent to path but with support of regex string | django.urls.re_path |
django.conf.urls.url() | Yes | Yes | No | route:str handler:obj |
deprecated replaced by re_path | - |
django.urls.conf.include() | Yes | No | No | module:str
|
prefixes addition to routes | django.urls.include |
django.views.generic.base.View.as_view() | No | No | Yes | - | dispatcher class to REST operations. |
django.views.generic.View django.views.View |
Django REST framework specific: rest_framework.views.APIView.as_view() |
No | No | Yes | - | dispatcher class to REST operations. (inherit django.views.generic.base.View) |
- |
Summary table of supported decorator API for Django frameworks. The following decorators are restricting handlers to a subset of HTTP method, Objects and Links will be created only if the handler is wired to a url route by a call of django.urls.conf.path(route,handler) or alternatives*.*
From |
Supported Decorators |
Link type |
Caller |
Callee |
Remarks |
---|---|---|---|---|---|
from django.views.decorators.http.py |
@required_GET |
callLink | Python Web Service GET Operation | Python Method | restrict handler (function or method) to GET operation only |
from django.views.decorators.http.py | @required_POST | callLink | Python Web Service POST Operation | Python Method | restrict handler (function or method) to POST operation only |
from django.views.decorators.http.py | @required_safe | callLink | Python Web Service GET Operation | Python Method | restrict handler (function or method) to GET or HEAD operation. |
from django.views.decorators.http.py | @required_http_methods | callLink | Python Web Service {GET/POST/PUT/DELETE/ANY} Operation | Python Method | restrict handler (function or method) to a specific list of operations madatory args: request_method_list : iterable(str) |
from rest_framework.decorators.py | @api_view | callLink | Python Web Service {GET/POST/PUT/DELETE/ANY} Operation | Python Method | restrict handler (function or method) to a specific list of operations madatory args: http_method_names: iterable(str) Equivalent to @required_http_methods |
References for ClassView from Django:
- https://github.com/django/django/tree/main/django/views
- https://github.com/django/django/tree/main/django/contrib/auth
References for Django REST Framework
An example of Django project folder structure:
mysite
└── mysite
├── db.sqlite3
├── manage.py
├── mysite
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ ├── views.py
│ └── wsgi.py
└── polls
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ ├── 0001_initial.py
│ ├── __init__.py
├── models.py
├── templates
│ └── polls
│ └── index.html
├── tests.py
├── urls.py
└── views.py
1) Code Snippet from mysite/mysite/views.py
from django.http import HttpResponse
from django.views.decorators.http import require_GET
from django.views.generic import TemplateView
@require_GET
def standard_index(request):
return HttpResponse("Hello, world. You're at the root index.")
class MyView(TemplateView):
template_name = "about.html"
def post(self,request, params):
return HttpResponse("Hello, world, you request a POST with params %s." %params)
2) Code Snippet from mysite/mysite/urls.py
from django.contrib import admin
from django.urls import include, path,re_path,url
from . import views
urlpatterns = [
re_path('^$', views.standard_index,name='root index'),
url("^about/$", views.MyView.as_view(),name="about"),
path('polls/', include('polls.urls')),
]
3) Code Snippet from mysite/mysite/polls/views.py
from django.template import loader
from django.http import HttpResponse
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {'latest_question_list': latest_question_list, }
return HttpResponse(template.render(context, request))
class Polls:
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
4) Code Snippet from mysite/mysite/polls/urls.py
from django.urls import path
from .views import index,Polls
urlpatterns = [
# ex: /polls/
path('', index, name='index'),
# ex: /polls/5/
path('<int:question_id>/', Polls.detail, name='detail'),
# ex: /polls/5/results/
path('<int:question_id>/results/', Polls.results, name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', Polls.vote, name='vote'),
]
From the 1st and 2nd code snippets we obtain:
The resulting objects and links from the 2nd, 3rd and 4th code snippets:
Remark the difference between a ClassView and a regular Class
treatment. A ClassView is a Class that inherit the method as_view() from
any django class that inherits django.views.generic.base.View (or
equivalently rest_framework.views.APIView.as_view()).
This trigger the creation of object based on method name in the class
(get, post, put, delete) and in the case of a REST framework
inheritance (retrieve, list, create, destroy, update).
Like in the above example, The
CAST_Python_Web_Service_POST_operation is created from the post
method in the class MyView.
The class MyView is a ClassView because it inherits the method
as_view() from django.views.generic.base.TemplateView (which
inherits it from django.views.generic.base.View).
On the other hand, a regular Class can’t be a handler directly but its methods can, and since there is no restriction on which http method is provided we use the “ANY” Operation.
Limitations
include() method accepts:
A string pointing to an other urls.py (supported).
A variable containing another list containing call to path(),
re_path(), url() (not supported).
CAST is considering to support the creation of Operations
for django.views.generic.base.RedirectView
Falcon
Below images are deprecated: The type Python Falcon Web Service GET operation and similar are replaced by its generic counterpart.
Falcon route annotations for web service operations (GET, PUT, POST, DELETE) are supported. In the following example, a default GET operation is ascribed to the function on_get from GetResourceclass,and the POST and PUT operations to the on_putandon_postfunctions fromPut_PostResourcewith two differents urls routing.
The link between the GET operation named after the routing URL “/” and the called functionon_get is represented by an arrow pointing to the function:
The name of a saved Web Service Operation object will be generated from the routing URL by adding a final slash when not present. In this example the name of the POST operations is “/url/example/1/” and “/url/example/2/” after the routing url “/url/example/1” and “/url/example/2”.
Sinks are supported with the following rules : If no route matches a request, but the path in the requested URI matches a sink prefix, Falcon will pass control to the associated sink, regardless of the HTTP method requested. If the prefix overlaps a registered route template, the route will take precedence and mask the sink.
In this case Web Service Operation objects generated as sinks will be named as /that/, and not as/this/since another Web Service Operation object exists with an overlapping url.
importfalcon
app=falcon.App()
class GetResource():
def on_get():
print('on_get function')
def sink_method(resp,kwargs):
resp.body="Sink"
pass
app.add_route('this/is/the/way', GetResource())
app.add_sink(sink_method, prefix='/that') # get, post, put & delete routes will be created and linked to sink_method
app.add_sink(sink_method, prefix='/this') # no routes created because Url overlaps another route
The optional *suffix *keyword argument of Falcon add_route is supported. In this way, multiple closely-related routes can be mapped to the same resource.
import falcon
app=falcon.App()
class PrefixResource(object):
def on_get(self, req, resp):
pass
def on_get_foo(self, req, resp):
pass
def on_post_foo(self, req, resp):
pass
def on_delete_bar(self, req, resp):
pass
app.add_route('get/without/prefix', PrefixResource())
app.add_route('get/and/post/prefix/foo', PrefixResource(), suffix='foo')
app.add_route('delete/prefix/bar', PrefixResource(), suffix='bar')
FastAPI
Summary table of Supported features for FastAPI web framework:
From |
Supported API (FastAPI) |
Link type |
Caller |
Callee |
Remark |
---|---|---|---|---|---|
FastAPI |
fastapi.FastAPI() |
N/A |
N/A |
N/A |
Supported options: root_path |
FastAPI |
fastapi.APIRouter() |
N/A |
N/A |
N/A |
Supported options: prefix |
({app}: fastapi.applications.FastAPI) |
@{app}.get() |
CallLink |
Python Web Service GET Operation |
Python Method |
|
({app}: fastapi.applications.FastAPI) |
@{app}.post() |
CallLink |
Python Web Service POST Operation |
Python Method |
|
({app}: fastapi.applications.FastAPI) |
@{app}.put() |
CallLink |
Python Web Service PUT Operation |
Python Method |
|
({app}: fastapi.applications.FastAPI) |
@{app}.delete() |
CallLink |
Python Web Service DELETE Operation |
Python Method |
|
({route}: fastapi.routing.APIRouter) |
@{route}.get() |
CallLink |
Python Web Service GET Operation |
Python Method |
|
({route}: fastapi.routing.APIRouter) |
@{route}.put() |
CallLink |
Python Web Service PUT Operation |
Python Method |
|
({route}: fastapi.routing.APIRouter) |
@{route}.post() |
CallLink |
Python Web Service POST Operation |
Python Method |
|
({route}: fastapi.routing.APIRouter) |
@{route}.delete() |
CallLink |
Python Web Service DELETE Operation |
Python Method |
|
Basic example of GET operation with two FastAPI instances and with root_path options:
from fastapi import FastAPI
app1 = FastAPI()
app2 = FastAPI(root_path="/tata")
# regular routing 2 examples with FastAPI
# with instance app1
@app1.get("/")
async def HelloWorld_app1():
return {"message": "Hello World"}
@app1.get("/Me")
async def HelloMe_app1():
return {"message": "Mika"}
# with app2
@app2.get("/")
async def HelloWorld_app2():
return {"message": "Hello World"}
@app2.get("/Me")
async def HelloMe_app2():
return {"message": "Mika"}
Result:
Second example of GET operation with two APIRouter instances and with “prefix” options.
from fastapi import APIRouter
routeur1 = APIRouter()
routeur2 = APIRouter(prefix="/tonton")
# regular routing 2 examples with APIRouter
# with routeur1
@routeur1.get("/")
async def HelloWorld_routeur1():
return {"message": "Hello World"}
@routeur1.get("/Me")
async def HelloMe_router1():
return {"message": "Mika"}
# with routeur2
@routeur2.get("/")
async def HelloWorld_router2():
return {"message": "Hello World"}
@routeur2.get("/Me")
async def HelloMe_router2():
return {"message": "Mika"}
Result:
Third example of GET operation with path parameters:
from fastapi import FastAPI
app = FastAPI()
# routing with parameter value
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
@app.get("/user/{name}")
async def read_user(name:str):
return {"name": name}
Result:
Fourth example with POST, PUT DELETE operation with query parameters:
from fastapi import FastAPI
app = FastAPI()
@app.post("/users/")
async def create_User(user):
return user
@app.put("/users/")
async def update_User(user):
return user
@app.delete("/users/")
async def remove_User(user):
return user
Result:
Flask
Flask route annotations for web service operations (GET, PUT, POST, DELETE) are supported. In particular, any decorator with the format @prefix.route is considered as a flask annotation where prefix can be a Flask application object or blueprint object. In the following example, a default GET operation is ascribed to the function f, and the POST and PUT operations to the upload_file function:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def f():
return 'hello world!'
@app.route('/upload', methods=['POST', 'PUT'])
def upload_file()
if request.method == 'POST':
pass
# ...
The link between the GET operation named after the routing URL “/” and the called function f is represented by an arrow pointing to the function:
The name of a saved Web Service Operation object will be generated from the routing URL by adding a final slash when not present. In this example the name of the PUT and POST operations is “/upload/” after the routing url “/upload”.
URL query parameters such as @app.route(’/user/’) are supported. In this case the generated Web Service Operation object will be named as /user/{}/, as shown in the example below.
from flask import Flask
app = Flask(__name__)
@app.route('/user/<username>')
def show_user_profile(username):
return 'User %s' % username
Similarly double slashes // in flask routing URLs are transformed into /{}/. Additional backslashes inside URL query parameters of type path [ @app.route(’/’) ] are not resolved (which in principle could catch any URL) so the web service will be named as a regular parameter /{}/.
The equivalent alternative to routing annotations using the Flask add_url_rule is also supported.
from flask import Flask
app = Flask(__name__)
def index():
pass
app.add_url_rule('/', 'index')
Plugable views are also supported for Flask add_url_rule.
from flask.views import MethodView
class InformationAPI(MethodView):
def get(self):
information = Information.from_data(request.data)
...
app.add_url_rule('/<info>/informations/', view_func=InformationAPI.as_view('informations'))
Httplib
Example for GET request:
from httplib import HTTPConnection
def f():
conn = HTTPConnection("www.python.org")
conn.request("GET", "/index.html")
Example link from method “f” to the get httplib service:
Http.client
Example for GET request:
from http.client import HTTPConnection
def f():
conn = HTTPConnection("www.python.org")
conn.request("GET", "/index.html")
In this case a Python Get Httplib Service will be generated (the httplib module from Python 2 has been renamed to http.client in Python 3).
Httplib2
The following code will issue a http get to the url ‘https://api.github.com/events' :
import httplib2
h = httplib2.Http(".cache")
(resp, content) = h.request("https://api.github.com/events")
Nameko
Summary table of supported features for Nameko web framework.
Supported API decorator (nameko.web.handlers) | Link type | Caller | Callee | Remarks |
@http() | CallLink | Python Web Service {GET,PUT,POST,DELETE} Operation | Python Method | decorated Python Method is in a class, class attribute name (type:str) must be declared. |
First example:
from nameko.web.handlers import http
class HttpService:
name = "http_service"
@http(method='GET', url='/get/<int:value>')
def get_methods(self, request, value):
return 'value={}'.format(value)
@http('POST', '/post')
def do_post(self, request):
return u"received: {}".format(request.get_data(as_text=True))
@http('GET,PUT,POST,DELETE,OPTIONS', '/multi')
def do_multi(self, request):
return request.method
Result:
Second example:
from nameko.web.handlers import http
from werkzeug.wrappers import Response
class Service:
name = "advanced_http_service"
def get_something(self):
return "Hello World!"
@http('GET,POST','/')
def index(self,request):
if request.method =="GET":
return self.get_something()
else:
return request.get_data(as_text=True)+"\n"
@http('GET', '/privileged')
def forbidden(self, request):
return 403, "Forbidden"
@http('GET', '/headers')
def redirect(self, request):
return 201, {'Location': 'https://www.example.com/widget/1'}, ""
@http('GET', '/custom')
def custom(self, request):
return Response("payload")
Result:
Pyramid
Summary of supported API:
Supported API |
contributes to URL routing | Links route to handler |
determines of Http method | Mandatory arguments | Remarks |
Import shortcuts |
---|---|---|---|---|---|---|
pyramid.config.Configurator.__init__() | Yes | No | Yes | route_prefix:str (optional arg) |
prefixes addition to routes if route_prefix arg is declared | pyramid.config.Configurator |
pyramid.config.Configurator.add_route() | Yes | No | Yes | name:str pattern:str request_method:str, tuple of str (optional arg) |
link route to unique route name | // |
pyramid.config.Configurator.add_view() | Yes | Yes | Yes | view:obj route_name:str attr:str request_method:str, tuple of str (optional arg) |
link unique route name to view and handler | // |
pyramid.config.Configurator.route_prefix_context() | Yes | No | No | route_prefix:str
|
prefixes addition to routes | // |
pyramid.config.Configurator.include() | Yes | No | No | callable:obj, str route_prefix:str |
link route to unique route name, used to group multple pyramid.config.Configurator.add_route() prefixes addition to routes if route_prefix arg is declared |
// |
Supported Decorator from pyramid.view.py | Link type | Caller | Callee | Remarks |
@view_config() | callLink | Python Web Service Operation | Python Method | replacement mechanism for pyramid.config.Configurator.add_view() |
@view_defaults() | callLink | Python Web Service Operation | Python Method | group all view which have same unique route name |
route_prefix_context():
from pyramid.view import view_config
from wsgiref.simple_server import make_server # wsgi: web server gateway interface
from pyramid.config import Configurator
from pyramid.response import Response
def handler_a(request):
return Response("Hello world from handler_a")
def handler_b(request):
return Response("Hello world from handler_b")
if __name__ == '__main__':
config = Configurator()
with config.route_prefix_context('main'): # can also do ('main')
config.add_route(name='a', pattern='home')
config.add_route(name='b', pattern='home')
config.add_view(view=handler_a, route_name='a', request_method=('GET', 'DELETE'))
config.add_view(view=handler_b, route_name='b', request_method=('GET', 'POST'))
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
For this use case, we expect to have:
- 4 webservice operations: GET and DELETE for route “/main/home”, GET and POST for route “/home”
Result:
view_defaults and view_config decorator:
from wsgiref.simple_server import make_server # wsgi: web server gateway interface
from pyramid.view import view_defaults
from pyramid.view import view_config
from pyramid.config import Configurator
from pyramid.response import Response
@view_defaults(route_name='rest')
class RESTView(object):
def __init__(self, request):
self.request = request
@view_config()
def handler1(self):
return Response('default')
@view_config(request_method='POST')
def handler2(self):
return Response('post')
@view_config(request_method='DELETE')
def handler3(self):
return Response('delete')
if __name__ == '__main__':
with Configurator() as config:
config.add_route('rest', '/')
config.scan()
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
For this use case, we expect to have:
- 3 webservice operations for route “/”: POST, DELETE with 2 corresponding handers, ANY for default handler
Result:
Multiple declarations of request_method:
from wsgiref.simple_server import make_server # wsgi: web server gateway interface
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config
@view_config(route_name="hello", request_method=("GET", "DELETE"))
class ViewHello():
def __init__(self, request) -> None:
self.request = request
def __call__(self):
return Response("Hello world")
if __name__ == '__main__':
with Configurator() as configurator:
configurator.add_route(name="hello", pattern="hello", request_method="GET")
configurator.scan()
app = configurator.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
For this use case, we expect to have:
- 1 webservice operation for route “/hello”: GET
Result:
config.include() from 2 sources:
From app.py:
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.view import view_config
from pyramid.response import Response
@view_config(permission='view', route_name='main', renderer='templates/main.pt')
def main_view(request):
return Response("main get")
@view_config(permission='view', route_name='about', renderer='templates/about.pt')
def about_view(request):
return Response("about get")
if __name__ == "__main__":
with Configurator() as config:
config.include("route", route_prefix="home")
config.scan()
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
From route.py:
def includeme(a):
a.add_route('about', 'about')
a.add_route('main', '')
For this use case, we expect to have:
- 2 webservice operations ANY for 2 routes “/home/” and “/home/about”
Result:
config.include() from 3 sources with multiple view_config decorators:
From route.py:
def add_pyramid_routes(a):
a.add_route('idea', '/ideas/{idea_id}')
a.add_route('user', '/users/{username}')
a.add_route('tag', '/tags/{tag_name}')
a.add_route('idea_add', '/idea_add')
a.add_route('idea_vote', '/idea_vote')
a.add_route('register', '/register')
a.add_route('login', '/login')
a.add_route('logout', '/logout')
a.add_route('about', '/about')
a.add_route('main', '/')
From start.py:
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
if __name__ == "__main__":
with Configurator() as config:
config.include("routes.add_pyramid_routes")
config.scan("views")
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
From views.py:
from pyramid.response import Response
from pyramid.view import view_config
@view_config(permission='view', route_name='main', renderer='templates/main.pt')
def main_view(request):
return Response("main get")
@view_config(permission='post', route_name='idea_vote')
def idea_vote(request):
return Response("idea_vote get")
@view_config(permission='view', route_name='register', renderer='templates/user_add.pt')
def user_add(request):
return Response("register get")
@view_config(permission='post', route_name='idea_add', renderer='templates/idea_add.pt')
def idea_add(request):
return Response("idea_add get")
@view_config(permission='view', route_name='user', renderer='templates/user.pt')
def user_view(request):
return Response("user get")
@view_config(permission='view', route_name='idea', renderer='templates/idea.pt')
def idea_view(request):
return Response("idea get")
@view_config(permission='view', route_name='tag', renderer='templates/tag.pt')
def tag_view(request):
return Response("tag get")
@view_config(permission='view', route_name='about', renderer='templates/about.pt')
def about_view(request):
return Response("about get")
@view_config(permission='view', route_name='login')
def login_view(request):
return Response("login get")
@view_config(permission='post', route_name='logout')
def logout_view(request):
return Response("logout get")
For this use case, we expect to have:
- 10 webservice operations ANY for 10 routes ‘/ideas/{idea_id}’, ‘/users/{username}’, ‘/tags/{tag_name}’, ‘/idea_add’, ‘/idea_vote’, ‘/register’, ‘/login’, ‘/logout’, ‘/about’, ‘/’
Result:
Requests
Example for GET request:
import requests
r = requests.get('https://api.github.com/events')
Sanic
Summary table of supported features for Sanic web framework.
From | Supported API (Sanic) | Link type | Caller | Callee | Remarks |
sanic.app.Sanic() | sanic.app.Sanic() | N/A | N/A | N/A | - |
Methods ({app}: sanic.app.Sanic) | @{app}.get() | CallLink | Python Web Service GET Operation | Python Method | - |
Methods ({app}: sanic.app.Sanic) | @{app}.post() | CallLink | Python Web Service POST Operation | Python Method | - |
Methods ({app}: sanic.app.Sanic) | @{app}.put() | CallLink | Python Web Service PUT Operation | Python Method | - |
Methods ({app}: sanic.app.Sanic) | @{app}.delete() | CallLink | Python Web Service DELETE Operation | Python Method | - |
Methods ({app}: sanic.app.Sanic) | @{app}.route() | CallLink | Python Web Service {GET,PUT,POST,DELETE} Operation | Python Method | default operation is “GET” |
Decorators ({app}: sanic.app.Sanic) | {app}.add_route() | CallLink | Python Web Service {GET,PUT,POST,DELETE} Operation | Python Method | Default operation is “GET” |
Decorators ({app}: sanic.app.Sanic) | {app}.get_name() | N/A | N/A | N/A | Optionnal argument “force_create” is analyzed |
First example :
from sanic import Sanic
from sanic.response import json
app = Sanic(__name__)
# using get decorator
@app.get("/")
async def HelloWorld(request):
return json({"message":"Hello World"})
# using generic route decorator
@app.route("/Goodbye")
async def Goodbye(request):
return json({"message":"Goodbye World"})
# routing with parameter value
@app.get("/items/<item_id:int>")
async def read_item(request,item_id: int):
return json({"item_id":item_id})
# using add_route method.
async def HelloMe(request):
return json({"message":"Hello Me"})
app.add_route(HelloMe,"/Me")
Result in enlighten :
Second example:
from sanic import Sanic
app = Sanic(__name__)
@app.post("/users/")
async def create_User(user):
return user
@app.put("/users/")
async def update_User(user):
return user
@app.delete("/users/")
async def remove_User(user):
return user
Result in enlighten :
Tornado
Server side
Summary of supported API for Tornado framework in the server-side.
Supported API (Tornado) |
Link type |
Caller |
Callee |
Remarks |
---|---|---|---|---|
tornado.web.Application.__init__() |
CallLink |
Python Web Service {GET,PUT,POST,DELETE} Operation |
Python method |
Support both types of arguments: (1) list of tuples containing the url and the reference to the handler class and (2) a list of url objects containing the same information as the former. |
tornado.web.Application.add_handlers() | CallLink | Python Web Service {GET,PUT,POST,DELETE} Operation | Python method | Only the host_handler argument is processed. The host_pattern argument (with values such as ".*") is ignored. |
Basic use case:
Simple tornado application with basic route “/” and its corresponding handler exposing web operations GET and POST. (The call to the write() method belongs to Tornado.web.Requesthandler, typically found in Tornado examples).
from tornado.web import Application, RequestHandler
class MainHandler(RequestHandler):
def get(self):
self.write("Hello, world")
def post(self):
self.write("Hello, CAST")
if __name__ == "__main__":
application = Application([
("/", MainHandler)
])
We expect 2 python webservice operation GET and POST corresponding to route “/”.
Results:
Multiple handlers use case:
Tornado application with three different class handlers:
- BasicHandler for basic route “/” with a GET operation
- MainHandler contains 4 standard webservice operations: GET, PUT, POST and DELETE as well as a non standard webservice operation display(), associated with route “/main/”
- Storyhandler handles route with parameter. Hence, regex is passed to route to indicate different requests that can be received in this route “/main/story/([0-9]+)”. The regex means to accept any valid digits passed to route, for example: “/main/story/1”, “/main/story/19”, etc. The digit will be taken as story_id which is passed to operations of the handler itself for information processing. The regex part is represented as {} by the interpreter for the sake of simplicity.
import tornado.web
class BaseHandler(tornado.web.RequestHandler):
def get(self):
self.write("Get base")
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Get main")
def post(self):
self.write("Post main")
def put(self):
self.write("Put main")
def delete(self):
self.write("Delete main")
def display(self):
self.write("You shall not pass!")
class StoryHandler(tornado.web.RequestHandler):
def set(self, story_id):
self.write("this is story %s" % story_id)
def get(self, story_id):
self.write("this is story %s" % story_id)
init_rules = [("/", BaseHandler),]
rules = [(r"/main/", MainHandler), (r"/main/story/([0-9]+)", StoryHandler)]
if __name__ == "__main__":
application = tornado.web.Application(init_rules)
application.add_handlers(".*", rules)
For this example, we expect to have:
- 1 webservice operation GET for “/” in BaseHandler.
- 4 operations GET, PUT, POST, DELETE in MainHandler. Method display() is left out.
- 1 operation GET for “/main/story/{}” in StoryHandler. Same as display() of MainHandler, method set() is not covered as a python web operation.
Result:
Urllib
Example for GET request:
import urllib.request
with urllib.request.urlopen('http://python.org/') as response:
html = response.read()
Urllib2
Example for GET request:
import urllib2
req = urllib2.Request('http://python.org/')
response = urllib2.urlopen(req)
the_page = response.read()
Example for POST request.
import urllib2
import urllib
values = {'name' : 'Michael Foord',
'location' : 'Northampton',
'language' : 'Python' }
data = urllib.urlencode(values)
req = urllib2.Request('http://python.org/', data)
response = urllib2.urlopen(req)
the_page = response.read()
PUT and DELETE calls are not supported by the urllib2 module (Python version 2.x) by default. Workarounds to bypass this limitation are not detected by the analyzer.
Urllib3
Example for GET request:
# using PoolManager
import urllib3
http = urllib3.PoolManager()
r = http.request('GET', 'http://httpbin.org/robots.txt')
# using HTTPConnectionPool
import urllib3
pool = urllib3.HTTPConnectionPool()
r = pool.request('GET', 'http://httpbin.org/robots.txt')
The urllib3 web service object is represented with the same Python GET urllib service as that used for urllib.
Web2py
Example for GET request:
from gluon.tools import fetch
def m(self):
page = fetch('http://www.google.com/')
Example link from method “m” to the get web2py service: