Web Service calls and operations support

See Framework Support for the frameworks and libraries supported.

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)

@{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  

@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 in enlighten :

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 in enlighten :

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 in enlighten :

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)

Optionally alters routing URL (str)

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 dependant of 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*.*    

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
@required_POST callLink Python Web Service POST Operation Python Method restrict handler (function or method) to POST operation only
@required_safe callLink Python Web Service GET Operation Python Method restrict handler (function or method)  to GET or HEAD operation.
@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 argsrequest_method_list : iterable(str)

Django REST framework specific (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 argshttp_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

https://www.cdrf.co/

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'),
]

And result from Enlighten after running analysis:

From the 1st and 2nd code snippets we obtains:

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).

In opposition, 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_PostResource*with 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:

Supported API (FastAPI)

Link type

Caller

Callee

Remark

fastapi.FastAPI()

N/A

N/A

N/A

Supported options: root_path

fastapi.APIRouter()

N/A

N/A

N/A

Supported options: prefix

Supported API decorators
({app}: fastapi.applications.FastAPI)





@{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


Supported API decorators
({route}: fastapi.routing.APIRouter)




@{route}.get()

CallLink

Python Web Service GET Operation

Python Method


@{route}.put()

CallLink

Python Web Service PUT Operation

Python Method


@{route}.post()

CallLink

Python Web Service POST Operation

Python Method


@{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"}

Results from Enlighten:

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"}

Results from Enlighten:

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}

Results from Enlighten:

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

Results from Enlighten:

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 in Enlighten:

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 in Enlighten:

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

//

from pyramid.view.py:

Supported Decorator 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 in Enlighten:

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 in Enlighten:

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 in Enlighten:

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 in Enlighten:

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 in Enlighten:

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. 

Supported API (Sanic)

Link type

Caller

Callee

Remarks

sanic.app.Sanic()

N/A

N/A

N/A


Supported API decorators ({app}: sanic.app.Sanic)

@{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 methods ({app}: sanic.app.Sanic)

{app}.add_route()

CallLink

Python Web Service {GET,PUT,POST,DELETE} Operation

Python Method

Default operation is "GET"

{app}.get_name()

N/A

N/A

N/A

Optionnal argument "force_create" is analysed

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 “/”.

Result in Enlighten:

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 in Enlighten:

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: