-
Notifications
You must be signed in to change notification settings - Fork 9
Tutorial
Interested in trying out WebCore or want to see how it works? This tutorial will step you through the process of getting started with a simple one-function application, then walk you through the process of evolving your application to make use of cinje templates, custom views, database connectivity through MongoDB, and JSON serialization. The end result will be a fully functional (if basic) wiki, and you with the knowledge of exactly how WebCore services your requests.
Don't worry if you've not used MongoDB, the cinje template engine, or even Python before; we'll introduce the basics and show how easy web development can be so long as you are already familiar with the basic precepts of programming such as functions, classes, flow control, variables, etc.
First, you'll need a copy of Python 3 in order to follow this guide. Getting Python itself running is beyond the scope of this documentation; you may find your Linux or Mac already comes with it pre-installed. Once you do and are able to successfully invoke the interpreter in a terminal we can get going. Python 2 will mostly work, but only if the examples are adapted to the earlier language conventions. You may also wish to prepare a local copy of MongoDB for use later.
The next thing you'll need is a place to put your project. For the sake of simplicity we'll use the virtual environment support built-in to Python 3 in order to isolate the packages we'll be installing and provide a convenient "project folder" to contain the code we'll be writing. We will assume a UNIX-like computer for now, but you Windows users should be able to follow along without too much difficulty; let us know how it goes!
Opening a terminal, which should place you in the "home" (~
) folder associated with your user account, let's make sure we have a place for projects in general then tell Python to create a new environment within, and a “src” folder for our application code:
mkdir -p Projects
python3 -m venv Projects/wiki
mkdir Projects/wiki/src
Now each time you wish to begin working on this project in a new terminal session you'll want to "activate" the environment; this will update your shell's PATH
and set PYTHONHOME
and friends to inform Python of where environment-installed packages are. Enter the project directory Python just created and "source" the activation script for your shell. The default will work in most cases:
cd Projects/wiki
source bin/activate
You might be able to abbreviate the second line as . bin/activate
, or use shell integration to automatically activate and de-activate environments for you, such as virtualenvwrapper or something similar to this Zsh configuration, though these are entirely optional.
Now that we have a project we can install packages into, let's get the package manager updated and WebCore installed. The following will install the current version of WebCore and enable installation of a default set of development dependencies; packages useful in development that are less useful in production. Also installed are the cinje template engine and the Python MongoDB driver which we'll use a little later.
pip install -U setuptools pip
pip install "WebCore[development]" cinje pymongo
Quotes are needed because of the brackets, which most shells will attempt to interpret as an expansion (like a wildcard), which won't work. See the README for a list of the tags that can be used there. What might seem to be "a lot" of packages will have been installed. The highlights include:
-
WebOb which WebCore uses to provide
context.request
andcontext.response
objects, as well as the library implementing HTTP status code exceptions. Its documentation will be generally useful to keep handy. -
Backlash, a web-based interactive debugger and REPL shell.
-
Ipython and Ptpython improved terminal REPL shells. To use them together, start your REPL by running
ptipython
instead of justpython
. -
pudb which provides a full-screen console-based visual debugger, similar in styling to the debugger present in Borland IDEs under DOS. Where Backlash lets you find out what happened when something has already gone wrong, pudb lets you find out what your code is doing by stepping through it, setting break points, etc.
-
Testing tools including the pytest runner, pyflakes static analyzer / "linting" tool, and coverage calculator letting you know if all of your code is tested.
We'll touch on or use each of these as we go... and we're ready to begin!
The entry point of WebCore is its Application
class; it conducts the orchestra that is your application. You instantiate this class to configure the framework, and it is called by the web server to service each request. It speaks Python's Web Server Gateway Interface protocol as an application allowing applications you develop to leverage the large existing ecosystem of severs and server bridges. WebCore's self-organizing extension architecture can manage and integrate WSGI "middleware" components, and you can even embed other WSGI applications, too.
So, we're going to need an Application
instance. Create a new file in the src
folder within the environment, name it run.py
, and populate it like this:
# Get a reference to the Application class.
from web.core import Application
# This is our WSGI application instance.
app = Application("Hi.")
# If we're run as the "main script", serve our application over HTTP.
if __name__ == "__main__":
app.serve('waitress')
The above is basically a long-form version of the minimal example from the README splitting the process into three clear steps: import, instantiate, serve. Currently we pass in "Hi."
as our "website root", but we'll expand on this in the next section. For now, save the file out and run it in a shell:
python run.py
This will invoke the Python interpreter from our project environment and execute the above script. The resulting output should read something like:
serving on http://127.0.0.1:8080
Now you can open that link up in a browser and see your first live application saying hello. Before adding much to this file, however, we need to decide on how to structure it.
Small utility apps might be able to get away with storing all their code in a single file, but if you ever plan on coming back to your code to update it later it’s a substantially better idea to structure your code into logical components. A basic wiki, luckily, has very few components.
Because WebCore is so absolutely minimal, it doesn't offer much in the way of restrictions on how you structure your application code. WebCore considers anything it executes within your code to be just that, "application code", excluding extensions, which are extensions. Internally some terms used may seem to relate to "model, view, controller" separation, and while WebCore can be used in that way, our example here will be a bit simpler. WebCore's concept of a view, for example, never directly communicates with application code, nor do extensions.
With that understanding, create a wiki
folder under the src
folder and touch
(create empty) the following files under that wiki
folder:
-
article.py
— code relating to individual wiki pages -
extension.py
— our custom application extension -
template.py
— a place for our template code -
wiki.py
— this will contain our "root" class
This can be quickly accomplished with two terminal commands:
mkdir wiki
touch wiki/{article,extension,template,wiki}.py
And that's it. We'll fill out these files as we go.
This might be what would be considered the controller in MVC terminology as typically used by web frameworks. Some, such as Pyramid, refer to this as view, instead due to how tenuous it is applying the MVC pattern to the web. Django is a MVT (model-view-template) framework for similar reasons.
WebCore uses dispatch, defaulting to object dispatch, to look up an endpoint to use for a given request using the root object you pass to Application
as the starting point for the search. An endpoint may either be a callable such as a plain function, instance method, or executable instance (a class with __call__
method), or even a static value such as the "Hi."
we're currently passing in.
Update wiki.py
to contain the following:
# HTTP status code exception for "302 Found" redirection.
from webob.exc import HTTPFound
class Wiki:
"""Basic multi-article editable wiki."""
def __init__(self, context=None):
pass # We don't use the context at all.
def __call__(self):
"""Called to handle direct requests to the web root."""
return HTTPFound(location='/Home') # Redirect.
This illustrates the basic protocol your endpoints will need to understand. First, we import an HTTP status code exception used later. Next, we define a class we'll use as our root object, exposed to the web. Python supports the idea of a "doc string", which is a string created without assignment as the first instruction in a new module, class, or function scope. We're using a triple-quoted string here, which lets the "documentation" easily flow across multiple lines without having to worry about line continuations, joining strings, etc. Documentation text like this is visible in IDEs, can be used by automatic documentation generators, and can be seen in a REPL shell by running help(Wiki)
.
At the start of a request the class constructor (__init__
) is called and is passed the current RequestContext
as its first positional argument. As noted, we don't need it, but if you did you might assign it to self._ctx
. Python doesn't like passing unknown arguments to functions, and since we don't even use it we default the value to None
to make testing easier. If you forget to include an initializer accepting that argument your application might seem to start, but you'll encounter a TypeError
exception and 500 Internal Server Error
when attempting to access it.
The __call__
method is the callable endpoint used when the class itself is the target of the request; because we'll be using this as our root object, __call__
here will be executed for any request to /
.
Save this file and run ptipython
in your shell. The first time you run it you will be presented with an interface similar to the ordinary python
REPL, but with 100% more color, a summary of some command shortcuts, and a status bar with additional keyboard shortcuts, similar to this:
We should be able to import our newly constructed Wiki
class, instantiate it, then call it like WebCore would in order to inspect the result. Transcribe this one command at a time and watch for lines with red Out
prefixes:
from wiki.wiki import Wiki
Wiki()
_()
_.location
This is exploiting a feature of the REPL shell where the variable _
refers to the result of the previous expression, so when calling _()
as a function, it's pointing at the instance of Wiki
constructed on the line prior. Similarly, _.location
is referring to the location
attribute (where to redirect to) of the returned HTTPFound
object. Your output should be virtually identical, barring a different memory address for our instance, to:
Let's wire this into our run.py
file and see if we're redirected. While we're at it, let's set up a few of the extensions we'll be using. Open run.py
up and add the following imports to the top:
# Get references to web framework extensions.
from web.ext.annotation import AnnotationExtension
from web.ext.debug import DebugExtension
# Get a reference to our Wiki root.
from wiki.wiki import Wiki
Okay, admittedly, that last line is a little silly. Now update the Application()
instantiation to reference our new root and extensions:
# This is our WSGI application instance and extension configuration.
app = Application(Wiki, extensions=[
AnnotationExtension(),
DebugExtension(),
])