2 Nov 2017

Using Python to create a Forge Server/Client Web Application

Default blog image

Ever wondered how to use Forge APIs from a Python Web Application? Look no further, Py-Forge is what you've been waiting for ...

    Python has become one of preferred language used in Machine Learning and since we are all going to do ML pretty soon, it seems like a good idea to start building some Python skills now!

    The last time I used Python was when I was a student, more than a decade ago, so even if the syntax hasn't changed since those old days, the frameworks, the tools and the ecosystem did a lot, so I assumed I knew nothing about Python. This post intents to guide you through the steps I took to build a simple web server/client app in Python, do not consider it being a reference on the topic. I wanted to evaluate how hard or easy it is to pick up that tech and pull out a simple, but non-trivial cloud application.

Step 1: Pick an IDE

    Like any other popular programming language these days, there are plenty of IDE available. Being a huge fan of WebStorm for my Node.js & JavaScript projects, I naturally looked at JetBrains products first: Their Python IDE is called PyCharm and they've got a community edition which is free, so I cut it short and went out with it, done!

Step 2: Pick a Web Framework

    Again plenty of choices, various point of view, different approaches... popular names that seem to come up often on the web include Django, Flask or Pyramid. I have no time to try them all, so I looked for a vs blog article and quickly found that: Django vs Flask vs Pyramid: Choosing a Python Web Framework

Flask is defined as a micro framework that more suitable for small projects. Even if I'm not planning to build something big at the moment, you never know, so I skipped to the next choice.

Django is pretty popular, however it imposes some technological choices like for example the database: this seems like a pretty big constraint for a web application in 2017! Not being able to change the DB your app is using, pretty strong limitation... ruled out!

Pyramid is mentioned as the most flexible framework of the three, no technical restriction about the tech that powers your app and in favour of composing your recipe yourself. That sounds more like what we do with Node and React these days. So Pyramid it will be.

Step 3: Pick a boilerplate

    From the Pyramid documentation I open up their Quick Project Startup with Cookiecutters which let's you clone a boilerplate and run it literally within seconds. Once you have got the boiler, you can run in a terminal the following commands in order to create a virtual environment, download the dependencies and run the server in dev mode. The name of my project will be py-forge:

> export VENV=./py-forge

> python -m venv $VENV

> ./bin/pip install -e .

> ./bin/pserve development.ini --reload

    The default client should run on localhost:6543 if you did not change anything. Open your favourite browser at this address and you should see something like this:

Pyramid Python boiler

Step 4: Make your own App

    The boiler is pretty basic so I did not even bother reading further documentation on Pyramid, I looked at the code and started to customize it. The sample is doing server-side rendering using templates named *.jinja2. The first version of my App should have two views:

    > The home view should show a list of models, pulled out from a local mongoDB

    > The second view should display in the Forge Viewer the selected model when clicking an entry from the home view

Looking at their MongoDB and Pyramid tutorial, I could connect to my DB pretty easily, here is my server main:

from pyramid.config import Configurator
from urllib.parse import urlparse
from pymongo import MongoClient
from gridfs import GridFS

#////////////////////////////////////////////////////////////////////
# Server main
#
#////////////////////////////////////////////////////////////////////
def main(global_config, **settings):

    config = Configurator(settings=settings)

    config.include('pyramid_jinja2')

    config.add_static_view(
        'static', 'static',
        cache_max_age=settings['cache_max_age'])

    db_url = urlparse(settings['mongo_uri'])

    config.registry.db = MongoClient(
           host=db_url.hostname,
           port=db_url.port,
    )

    def add_db(request):
        db = config.registry.db[db_url.path[1:]]
        if db_url.username and db_url.password:
            db.authenticate(db_url.username, db_url.password)
        return db

    def add_fs(request):
        return GridFS(request.db)

    config.add_request_method(add_db, 'db', reify=True)
    config.add_request_method(add_fs, 'fs', reify=True)

    # Routes definition
    config.add_route('forge-token', '/forge/token')
    config.add_route('viewer', '/viewer')
    config.add_route('home', '/')

    config.scan()

    return config.make_wsgi_app()

    I then created two templates based on the initial one home.jinja2 and viewer.jinja2. Those are being rendered by the views defined in views.py. I could also add an endpoint /forge/token for the Forge token using a json renderer, painless:

from pyramid.view import view_config
from bson.objectid import ObjectId
import requests
import os

#////////////////////////////////////////////////////////////////////
# /home route handler
#
#////////////////////////////////////////////////////////////////////
@view_config(route_name='home', renderer='templates/home.jinja2')
def home_view(request):

    models = request.db['gallery.models'].find()

    return {
        'title': 'Models',
        'models': models
    }

#////////////////////////////////////////////////////////////////////
# /viewer route handler
#
#////////////////////////////////////////////////////////////////////
@view_config(route_name='viewer', renderer='templates/viewer.jinja2')
def viewer_view(request):

    model_id = request.params['id']

    model_info = request.db['gallery.models'].find_one({
        '_id': ObjectId(model_id)
    })

    return {
        'token_url': '/forge/token',
        'model_info': model_info
    }


#////////////////////////////////////////////////////////////////////
# Get Forge token
#
#////////////////////////////////////////////////////////////////////
def get_token(client_id, client_secret):

    base_url = 'https://developer.api.autodesk.com'
    url_authenticate = base_url + '/authentication/v1/authenticate'

    data = {
        'grant_type': 'client_credentials',
        'client_secret': client_secret,
        'client_id': client_id,
        'scope': 'data:read'
    }

    r = requests.post(url_authenticate, data=data)

    if 200 == r.status_code:
        return r.json()

    return None

#////////////////////////////////////////////////////////////////////
# /forge/token route
#
#////////////////////////////////////////////////////////////////////
@view_config(route_name='forge-token', renderer='json')
def forge_token(request):

    client_secret = os.environ['FORGE_DEV_CLIENT_SECRET']
    client_id = os.environ['FORGE_DEV_CLIENT_ID']

    token = get_token(client_id, client_secret)

    return token

    The rest of the app is pure JavaScript related to the Forge viewer, so I will skip that as this post focus solely on Python. You can find the complete project at https://github.com/leefsmp/py-forge with detailed instruction on how to run it.

    All in all it took me a couple of hours to pull this out, including writing this blog (I already had the JS code from another project). My verdict: doing basic Web with Python + Pyramid is a breeze!

    Finally here is a quick video that demos the project in action running locally on my machine. Next time I will take a look at how to deploy that stuff on the Cloud, till then... happy coding! 

  [ Don't forget to follow @F3lipek on twitter for the latest news and cool samples about Forge and Web development and star our repoz! ;) ]

Related Article