Showing posts with label python. Show all posts
Showing posts with label python. Show all posts

Tuesday, November 5, 2013

Pattern of Error Reporting in Python

Instead of Intro


That is not an uncommon and even not rare when an application which was supposed to be a prototype suddenly becomes a tool for everyday usage. Not a perfect thing here is that in a rush to make the program more or less user-friendly a developer has to hide internals by preserving error messages with internal details. Here I am going to share the pattern I mostly use in my "so-called-prototype" Python scripts; especially when they are translated to binaries using py2exe.

The pattern allows to introduce an extended error reporting in Python scripts w/o any extra costs.

Own Errors


The only rule I follow when there is a need to raise an error is to forget about standard ready-to-use Python exceptions. Due the following reasons:

  • Need to distinguish where an error came from: from the "Batteries" or from the application's logic;
  • Need to express an application's domain in the code.
The rule is true even for a spaghetti-style code which is supposed to be thrown away tomorrow or even today; this will cost nothing but might help with debugging.

So introduce own exception class:

class Error(Exception):
    def __init__(self, message, innerError = None):
        msg = message
        if innerError:
            msg += " *- {0}".format(str(innerError))

        Exception.__init__(self, msg)

The exception class here is straight forward for the sake of simplicity. In serious applications it is much better to introduce a field for an inner error, environment etc.

Respect Each Error


When a script's function pass flow control to another one there is a bell that the flow goes to another virtual layer. Each new layer is worth to have own logged error if there is any.

Suppose below that foo() is a first layer, bar() is a second one. So the application might look like:

def tryRussianRoulette():
    ### NOTE: that is a very bad practice to put import statements somewhere in a logic
    import random
    
    isFired = (0 == random.randint(0, 5))
    if isFired:
        raise Error("bang!")
        
def bar():
    try:
        tryRussianRoulette()
    except Error, e:
        raise Error("Russian Rouletter has fired", e)

def foo():
    try:
        bar()
    except Error, e:
        raise Error("Failed to bar-bar", e)

def main():
    foo()

def propagateToUser(error):
    if isinstance(error, Error):
        print "[Error]", str(e)
    else:
        print "[Unknown error]", str(e)

if "__main__" == __name__:   
    try:
        main()
    except Exception, e:
        propagateToUser(e)
        sys.exit(1)

    sys.exit(0)


tryRussianRoulette() is a function which may cause an error.
When application is accidentally in a production, an error for end-user (!) would look like:

[Error] Failed to bar-bar *- Russian Rouletter has fired *- bang!

In most cases (if you have not skipped error handling on each layer), the error is descriptive enough to understand the problem.

Pattern in Action


How to introduce an ability for an extended error tracing w/o writing tons of extra code? The answer is to vary try/except statement's behavior depending on system's environment variable bound to an application.

The application's code above is just extended with the function:

def isDebug():
    withLettersOnly = lambda string: filter(lambda ch: ch.isalpha(), string)
    appName = os.path.basename(sys.argv[0])
    debugKey = "{appName}_DEBUG".format(appName = withLettersOnly(appName))

    return os.environ.get(debugKey, False)

and try/except statement will be replaced with the following code:

    try:
        main()
    except Exception, e:
        if isDebug():
            raise
        else:
            propagateToUser(e)
            sys.exit(1)

If the newly developed application is run from a file named bing-bang.py, environment variable bingbangpy_DEBUG set to a non-empty value will cause a raw Python's stack trace instead user-friendly error. The similar is true for a Python's script compiled using py2exe; guess a bound environment variable name.

Instead of Summary


  1. The pattern code has been intentionally left primitive for one reason: to allow your to play around and find a suitable implementation;
  2. Introduced own exception class could contain locals() and globals() of a corresponding layer; or system details. That totally depends on your fantasy;
  3. The pattern works well for small scripts; and for "proof-of-concepts" applications which might be used in real-life until RTM. Avoid the approach in the case of more or less serious applications.


Tuesday, October 15, 2013

Python, argparse and Environment Variables

argparse more likely is the one of frequently used Python's libraries. It covers all standard cases out of the box. When a case goes beyond the box a developer is encouraged to extend the library with the specially provided API.

What to do when the argument is marked as required and its value is not changed during some quite long time? Make the argument's value persistent; store somewhere. Otherwise your application more likely has all chances to be recognized as unfriendly by an end-user. You may want to store a value of the argument somewhere in a configuration file. But what the file format should be? How to organize the file? Where should it be located? The are more questions than answers.

Why not to be able to pass the arguments to argparse's parser through the system environment? Such approach is widely recognized and makes your application easily scriptable.

Optional Arguments


When an argument is marked as optional a value from the system environment could be accessed by evaluating a default one:

parser.add_argument("-c", "--crt", type = str, default = os.environ.get("X509_CRT"), required = False, help = "Path to X509 Certificate")

Or in a bit complicated way when the value is not allowed to be empty:

parser.add_argument("-c", "--crt", type = str, default = os.environ.get("X509_CRT") or "~/.work/vpn.crt", required = False, help = "Path to X509 Certificate")

Required Arguments


Due to the design mandatory arguments in argparse library are not allowed to have default values.
There is an interface in argparse called Action which is associated with the argument being processed. The interface is intended to customize the way how an argument is processed/stored. Providing own implementation of the interface will allow you to look the desired value of the argument in system environment:

class FindValueInEnvironmentAction(argparse.Action):
    def __init__(self, varName, **kwargs):
        assert kwargs.get("required")
         
        valueFromEnv = os.environ.get(varName)
        requiredValue = True
        
        if valueFromEnv:
            kwargs["required"] = False
            kwargs["default"] = valueFromEnv
            
        argparse.Action.__init__(self, **kwargs)

    def __call__(self, parser, namespace, values, option_string):
        setattr(namespace, self.dest, values)
...
parser.add_argument("-c", "--crt", type = str, action = FindValueInEnvironmentAction, varName = "X509_CRT", required = True, help = "Path to X509 Certificate")

When the argument's value is found in the system environment variable scope, the built-in options in **kwargs are patched:
  • required attribute is removed;
  • default value is set to the read one.
These steps allow to pretend that an optional argument with a predefined default value is being processed. Here a value of the argument passed through the command line has a priority over a value set through "X509_CRT" environment variable.

I would also inject to our implementation a dictionary where to look up; it will allow us to cover the class with unit tests. And if you a user of Python3 feel free to try an alternative way.

Wednesday, September 4, 2013

Run Unit Test automatically for Python code

Have you ever thought how often you write the tests for the newly developed code? I believe that more likely -- every time. Even a tiny tool which will never leave the developer's sandbox, which is supposed to show that the implemented logic is correct might be called a "test"; it is not perfect, not maintainable, but it is intended to do at least one thing -- to check the code. I did so. Currently I prefer to write unit tests rather than such tiny tools; for the new as well as for the legacy code. If there is a file with the implementation -- there should be a corresponding file with the tests.

I am still Emacs user but sometimes for reasons unknown I use Sublime Text to create something using Python. So currently it is become boring a bit to:
  1. Introduce the change in a file with a SUT;
  2. Switch to the next tab in Sublime Text with a source code of the test;
  3. Press a hot key to run the test to check the fixes.
I would prefer to replace these steps with the one. Here goes a simple straightforward solution to run unit tests for Python code in Sublime Text automatically; these tips are also applicable to other editors.

The test we have implemented for our code might look like:

import unittest
import hello as SUT

class TestHello(unittest.TestCase):
 def test_returnsHelloGretting(self):
  self.assertEqual("hello", SUT.greet())

The code we work on:
def greet():
 return "hello"

Way #0: Unit Tests inside


The most obvious way is to keep the SUT and the tests in the same file. In short I do not like this because of possible problems with the further maintainability.

Way #1: Native/Python


If we want to reduce the amount of switches between the tabs in the editor we need to make the SUT run the associated unit tests when the SUT is run as main python source. This could be done by introducing an intermediate helper layer to run the tests. Call it test.py:

import unittest
import inspect
import os

def main(**kwargs):
 ### stack[0] contains the frame information about the current function, since it was called first
 ### stack[1] contains the frame information about the caller
 callerFullPath = inspect.stack()[1][1]
 callerFileName = os.path.basename(callerFullPath)
 associatedTestFileName = "test_{0}".format(callerFileName)
 associatedTest = os.path.splitext(associatedTestFileName)[0]

 return unittest.main(associatedTest, verbosity = 2, **kwargs)

The function main() simply does the following:
  1. Gets the file name of the SUT it was called from;
  2. Composes a name of the file where the unit test for SUT is located in;
  3. Redirects the call to the real test runner -- unittest.main()

... and slightly update contents of the file with the SUT:

def greet():
 return "hello"

if "__main__" == __name__:
 import test
 test.main()

Since now the each press of Ctrl-B (default hot key to run the build in Sublime Text) will lead to the run of unit tests.

Way #2: Custom build script


Sublime Text provides with a good ability to set up a custom build system for the projects a developer works on.

The final configuration might look like:

where python-wrapper.sh is a shell script to launch Python unit tests directly from Sublime Text on Ctrl-B. The script supposes that:

  1. The tests are located in the same directory where the SUT is;
  2. The file with a test has a prefix 'test_'.
... and it is compatible with bash:

#!/bin/bash

RUN_DIRECTORY="${1}"
SOURCE_NAME="${2}"
TEST_OF_SOURCE_NAME="test_${SOURCE_NAME}"

pushd "${RUN_DIRECTORY}"

if [ -e "${TEST_OF_SOURCE_NAME}.py" ]; then
 python -m unittest "${TEST_OF_SOURCE_NAME}"
else
 echo "[Warning] Associated test '${TEST_OF_SOURCE_NAME}' not found"
 python "${SOURCE_NAME}.py"
fi

RC=$?

popd

exit ${RC}

The simplicity of the solution might make you write and launch unit tests more often! But the solution should not be a reason not to run all unit tests periodically in the project.

Wednesday, May 29, 2013

Pass arguments to BaseHTTPRequestHandler

Each time when I face with the Python's built-in web-server (BaseHTTPServer) I feel a pain. The pain is caused by a strange architectural decision to pass a class as a request handler not an instance. At first glance it does not matter what kind of entity to pass. Since you can extend the default implementation with your logic. It still does not matter until a some moment. This moment happens when you want to have an externally configurable handler and/or you have to inject a bunch of settings. Currently there is no way to do it easily.

There is no easy way since the developer who created such design more likely was fell in love with Template Method pattern or was affected by some forbidden stuff :). Let's take a brief look how current Python's BaseHTTPRequestHandler's implementation works. Then let's try to answer the question how to pass arguments to BaseHTTPRequestHandler?

When a request comes to the server, the server creates an instance of BaseHTTPRequestHandler class. The newly created instance is initialized with a received request in raw format (say, as a plain not yet parsed text; when it comes finally to our handler, it is already split to the headers, body etc.). BaseHTTPRequestHandler's constructor dispatches an inner method (call it process_request()) responsible for an initial request handling; e.g. to determine a kind of the request (GET/POST/etc). After the request is recognized, a corresponding method do_[GET/POST/DELETE/HEAD/PUT]() is called from the self.

How BaseHTTPServer interacts with the handler


Seems very straightforward. But the following approach at least breaks the rule that one function should do one thing only. Constructor is responsible for object construction but not for serving business logic.

Let's see the following code. The handler is supposed to output current time and date in some format. With the current implementation the task could be implemented as:

def tellTheDate():
  import time
  return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())

class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
  def do_GET(self):
    self.__process()

  def do_POST(self):
    self.__process()

  def __process(self):
    self.__setupLayout()
    self.wfile.write(tellTheDate())

  def __setupLayout(self):
    self.send_response(200)
    self.send_header("Content/Type", "text/plain")
    self.end_headers()

def main():
  host = ("localhost", 8080)
  server = BaseHTTPServer.HTTPServer(host, RequestHandler)
  server.handle_request()

The problem in the code above that it depends on the global scope. We need to avoid such dependency by injecting a required logic inside the handler. This is achieved by extending the handler's class.

class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
  def do_GET(self):
    self.__process()

  def do_POST(self):
    self.__process()

  def __process(self):
    self.__setupLayout()
    self.logic()

  def __setupLayout(self):
    self.send_response(200)
    self.send_header("Content/Type", "text/plain")
    self.end_headers()

def tellTheDate(handler):
  import time
  currentDate = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
  handler.wfile.write(currentDate)

def main():
  host = ("localhost", 8080)
  handlerCls = RequestHandler

  handlerCls.logic = tellTheDate

  server = BaseHTTPServer.HTTPServer(host, handlerCls)
  server.handle_request()

After such modification the logic could be easily interchanged and could pretend as handler's built-in method. But since now it starts to break general encapsulation of extended BaseHTTPRequestHandler: the outer function has to know inner details of the class; e.g. to know how response is sent to the client. Also the implementation of handler is located across multiple locations: in the function with the logic and in the handler itself.

class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
  def __init__(self, tellTheDate, *args):
    self.tellTheDate = tellTheDate
    BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args)
  
  def do_GET(self):
    self.__process()

  def do_POST(self):
    self.__process()

  def __process(self):
    self.__setupLayout()
    self.tellTheDate(self.wfile)

  def __setupLayout(self):
    self.send_response(200)
    self.send_header("Content/Type", "text/plain")
    self.end_headers()

def tellTheDate(output):
  import time
  currentDate = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
  output.write(currentDate)

def handleRequestsUsing(tellTheDateLogic):
  return lambda *args: RequestHandler(tellTheDateLogic, *args)

def main():
  host = ("localhost", 8080)

  handler = handleRequestsUsing(tellTheDate)
  server = BaseHTTPServer.HTTPServer(host, handler)
  server.handle_request()

The code above shows that a postponed (aka lazy) initialization mixed with Python's ability to setup a context where the function runs, makes the desired possible.

Friday, November 11, 2011

Python, make ConfigParser aware of spaces

There is a wonderful Python's module called ConfigParser which allows to process .ini-style configuration files easily. I prefer to use it everywhere rather than spend the time to implement my own solution. Recently there was a bug received that values with leading and trailing spaces are read incorrectly: spaces are lost. This might be important for cases when an application is sensitive for such values; e.g.: for passwords.

It was discovered that current Python's ConfigParser implementation cannot be tuned up not to strip values while reading a configuration. Also there was a corresponding issue found with an attached patch. Unfortunately the patch has not been applied to public available Python builds yet. Definitely it is absolute not convenient to patch Python everywhere where yours application is run.

The solution is not to lose leading and trailing spaces by wrapping them for quotes. Here is a helping code snippet to solve this issue:

class SpaceAwareConfigParser(ConfigParser.ConfigParser):
    def __init__(self, **args):
        KEEP_SPACES_KEYWORD = "keep_spaces"
        
        self.__keep_spaces = args.get(KEEP_SPACES_KEYWORD, True)
        args.pop(KEEP_SPACES_KEYWORD)

        ConfigParser.ConfigParser.__init__(self, **args)

    def get(self, section, option):
        value = ConfigParser.ConfigParser.get(self, section, option)
        if self.__keep_spaces:
            value = self._unwrap_quotes(value)

        return value

    def set(self, section, option, value):
        if self.__keep_spaces:
            value = self._wrap_to_quotes(value)

        ConfigParser.ConfigParser.set(self, section, option, value)        

    @staticmethod
    def _unwrap_quotes(src):
        QUOTE_SYMBOLS = ('"', "'")
        for quote in QUOTE_SYMBOLS:
            if src.startswith(quote) and src.endswith(quote):
                return src.strip(quote)

        return src

    @staticmethod
    def _wrap_to_quotes(src):
        if src and src[0].isspace():
            return '"%s"' % src

        return src

Overridden get() method removes quotes if any. So for ConfigParser's client that is transparent if the option has a value with quoted spaces or not; double and single quotes are supported. set() does vice versa.

So it is enough to replace instantination of ConfigParser in your Python's code just with SpaceAwareConfigParser one and it should work like expected.

Sunday, May 22, 2011

Python, imp.load_source() trap

While I have been writing a hook for WebApy lightweight RESTful Python webserver -- the recent a project of mine, I got ran into the funny (actually it wasn't; since it was hard enough to debug) issue related to loading of Python-app addons. As far as you know (or not; if you already have taken a look at sources), there is Python's standard library's 'imp' module is used to load hook files. "imp.load_source()" if to be more precised.

Here is an example of the problem you may get into in Python while loading modules with load_source() function of imp module.

Lets see a simplified example of what where I ran into. Suppose our Python application supports external addons (aka plugins/extensions). Each addon must provide a class named "Addon" with implemented "runLogic()" method. Addons have priorities -- from 1 to N (N > 1); priority set to "1" is a highest one. Suppose several of addons also implement internal Helper class for low-level dirty work.

Addon #1 (file: addon-1.addon.py)
class Helper(object):
    """ Internal, addon-specific helper class """
    def __init__(self, msg):
        self.__msg = msg

    def help(self):
        print self.__msg
        
class Addon(object):
    """ Addon entry point class """
    PRIORITY = 1
    
    def runLogic(self):
        addonHelper = Helper("module 1")
        addonHelper.help()

and Addon #2 (file: addon-2.addon.py)
class Helper(object):
    """ Internal, addon-specific helper class """
    def help(self):
        print "module 2"

class Addon(object):
    """ Addon entry point class """
    PRIORITY = 2
    
    def runLogic(self):
        addonHelper = Helper()
        addonHelper.help()

Addons are handled inside the main application through the class named "AddonLoader". "AddonLoader" is initialized with the only argument -- filename pattern of modules; since our modules are called addon-1.addon.py and addon-2.addon.py respectively the aforementioned pattern could be '*.addon.py'. Also "AddonLoader" provides with the only public method "runMoreImportantAddon()" which executes an addon with the highest priority. Addons handling is implemented through Python's 'imp' module:

import glob  ### To find addons on local filesystem
import imp   ### To load addons

class AddonLoader(object):
    def __init__(self, addonPattern):
        self.__addonPattern = addonPattern

    def runMoreImportantAddon(self):
        """ Runs an addon with the highest priority (determined by PRIORITY property)  """
        moreImportantAddon = self.__getMoreImportantAddonInstance()
        moreImportantAddon.runLogic()

    def __getMoreImportantAddonInstance(self):
        """ Returns an addon instance with the highest (1 -- high, 10 -- low) priority """
        
        availableAddons = self.__enumerateAddonsOnFileSystem()
        sortedAvailableAddons = sorted(availableAddons,
                                       key = lambda addon: addon.PRIORITY)

        return sortedAvailableAddons[0]()

    def __enumerateAddonsOnFileSystem(self):
        """ Load addons by specified pattern and returns a list with them """
        
        addons = list()
        
        for addonPath in glob.glob(self.__addonPattern):
            addon = imp.load_source("addon", addonPath)
            addons.append(addon.Addon)

        return addons

def main():
    addonLoader = AddonLoader("*.addon.py")
    addonLoader.runMoreImportantAddon()

if "__main__" == __name__:
    main()

So could you predict what the output will be when main app is ran? I bet that probably not. There will be an exception that Helper class could not be instantiated because of invalid passed parameters amount. Was it expected?

No, it was not; at least for me. The problem of this code is hidden in "name" argument of imp.load_source()'s function. For each enumerated addon it is still the same (set explicitly to "addon"); on the each iteration all already loaded classes are overwritten. On first iteration we extract and keep a reference to Addon #1 (do not forget that it uses Helper #1 class). On the second (final) iteration a reference to Addon #2 (it depends on Helper #2 class) is taken. Since loaded addons are set to have the same name ("addon"), on the final iteration we see that Helper #1 is overwritten with Helper #2 in the namespace of "addon". And when Addon #1 is being instantiated it is calls for Helper class, but as you remember it was overwritten and does not take any parameters in the constructor. Here we get an exception.

The solution of the problem is not to pass constant name to load_source() function. Just replace "addon" there with the call of the function which returns an unique name of module to load. Names must be unique!

Since the official documentation does say nothing about uniqueness of "name" argument, I wonder why there is no hint about the trap you could get into while using it?

Wednesday, May 11, 2011

WebApy -- webserver for easy and rapid REST API imitation

Have been developing an application which depends on a remote REST (stands for Representational State Transfer) API of one of popular services I ran into the need to use it [API] more intensively while testing/debugging the code. Not all remote services provide developers with SandBox`ed environments to play in. And not all services may tolerate frequently repeated requests to their REST API; they may just ban your access.

The best way to avoid such problems is to imitate remote API locally.

WebApy -- is a simple lightweight python Webserver built on the top on BaseHTTPServer. WebApy allows easily to create REST API which pretends like original one.

When it might required
  1. To develop Unit tests for a library/application which interacts with remote REST API;
  2. To debug such library/application more intensively and not being dependent on remote service availability;
  3. To make a fast dirty prototype of REST API for your service.
How it works
  1. Create a file (or several ones) called "hook" -- a regular Python file with pre-defined structure (the sample of such hook is available in hooks/ dir; "hooks" must have "hooks.py" filename extension);
  2. Implement canHandleRequest() static method which tells WebApy that the hook can handle this request;
  3. Implement code(), headers() and data() methods to return corresponding response values on the passed request;
  4. Run WebApy server instance to serve your application/library with imitated REST API.
Example
Last.Fm is world's largest and well known online music catalogue. It has a remote XML API to access to theirs music information database. Before start working with the API a client application should perform several authentication steps:
  1. Retrieve an Auth token;
  2. Retrieve an Auth session (providing user's credentials and obtained Auth token).
Lets see how to make WebApy hook which imitate the first step -- to provide a client with an Auth token.

Accordingly to Last.Fm developer's documentation an Auth token retrieving is performed via auth.getToken request. It has only one mandatory parameter -- "api_key" (a Last.Fm API key; it can be received upon request to Last.Fm).

Step 1
First we should check inside the hook if it can handle received API request. The hook can handle request if the following conditions are met:
  • The request is GET [type];
  • There is "method" argument passed;
  • The value of "method" argument is "auth.gettoken".

Lets implement canHandleRequest():

    @staticmethod
    def canHandleRequest(request):
        if "GET" != request.method:
            return False

        return "auth.gettoken" == request.simpleQuery.get("method", [None])[0]

Step 2
Second and final step: implement a logic to return a corresponding response to REST API client. Suppose only a client with Last.fm API key b25b959554ed76058ac220b7b2e0a026 is able to get Auth token. For other ones an error must be returned:

    def __makeResponse(self):
        apiKey = self.request.simpleQuery.get("api_key", [None])[0]

        if self.isValidApiKey(apiKey):
            self.__response = make_authenhicated_response(authToken = "cf45fe5a3e3cebe168480a086d7fe481")
        else:
            ### Error code "10" stands for invalid API key
            self.__response = make_failed_authenhicated_response(errorCode = 10)

The final code will look like:

import string
RESPONSE_TPL = string.Template("<?xml version=\"1.0\" encoding=\"utf-8\"?><lfm status=\"${code}\">${body}</lfm>")

def make_authenhicated_response(authToken):
    return RESPONSE_TPL.substitute(code = "ok", body = "<token>%s</token>" % authToken)

def make_failed_authenhicated_response(errorCode):
    return RESPONSE_TPL.substitute(code = errorCode, body = str())

class RequestHook:
    @staticmethod
    def canHandleRequest(request):
        if "GET" != request.method:
            return False

        return "auth.gettoken" == request.simpleQuery.get("method", [None])[0]

    def __init__(self):
        self.__response = None
        self.__makeResponse()
          
    def __makeResponse(self):
        apiKey = self.request.simpleQuery.get("api_key", [None])[0]

        if self.isValidApiKey(apiKey):
            self.__response = make_authenhicated_response(authToken = "cf45fe5a3e3cebe168480a086d7fe481")
        else:
            ### Error code "10" stands for invalid API key
            self.__response = make_failed_authenhicated_response(errorCode = 10)

    def code(self):
        return 200

    def headers(self):
        return {}

    def data(self):
        return self.__response

    @staticmethod
    def isValidApiKey(key):
        return "b25b959554ed76058ac220b7b2e0a026" == key

Seems pretty easy.

Test
Lets use "curl" utility to test how "our" API works. A malformed request:
$~ curl 'http://localhost:8080/?method=auth.gettoken'


And the correct one:
$~ curl 'http://localhost:8080/?method=auth.gettoken&api_key=b25b959554ed76058ac220b7b2e0a026'
cf45fe5a3e3cebe168480a086d7fe481

Distribution
WebApy REST API webserver can be directly downloaded from the git repository: http://git.thekondor.net/webapy.git. The software is licensed in terms of GNU GPL v3 and higher.

General notes
Imitated REST API can return JSON as well as XML responses (actually anything; depends on your needs).  WebApy is not intended to serve production environment, for debugging and testing purposes only since it was developed as an accessorial part of another project of mine. Hence it has some limitations and things to improve (especially I want to replace canHandleRequest() with the declarative description). Documentation is coming soon.

Anyway please feel free to submit your bug reports if any.