10/04/2008

Automatic list of actions from a controller

A little recipe to list all the actions off a controller automatically:

class SomethingController(BaseController):

    def index(self):
        # magic to return a list of actions this controller supports 
        html = [h.link_to(f.replace('_', ' '), h.url_for(controller='something', action=f)) + "<br>"
                for f in dir(self) 
                if (not f.startswith('_') and 
                    callable(getattr(self, f)) and 
                    f not in ('index', 'start_response')
                )]
        return "\n".join(html)

09/04/2008

Google Web Engine

Can everyone just stop talking about it please?

/me bored...

pyglons in pyglons in pyglons in...

pyglons application class now also subclasses from the state class... what does this mean? With a few more tweaks to the base application, applications can now run inside other applications ( inside other applications ( inside other applications (... enough!

Why? Well its kind of cool for one! Two: it allows you to write loaders for other games ( with a little entry point magic you could create nice loaders for your other apps ). And three, because thats what you can do with pylons and wsgi in general!

Im thinking it could also be used for generic settings screens or something.. somehow..

pyglet application entry point specification anyone...?

asyncore, asynchat and pickle

A little demo I did for spike using asycore and asynchat that sends pickles around.

(yes the delimiter sucks, and i could really not of used asynchat at all... :)

easymsg.py ------------------------------------------------------------------
import asyncore, asynchat
import os, socket, string
import pickle

PORT = 8000

class EasyMsgRequest(asyncore.dispatcher):

    def __init__(self, host, obj, port=PORT):
        asyncore.dispatcher.__init__(self)
        self.obj = obj
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((host, port))

    def handle_connect(self):
        self.send(pickle.dumps(self.obj)+'\r\n')
        self.close()

    def handle_expt(self):
        self.close()

    def handle_close(self):
        self.close()


class EasyMsgChannel(asynchat.async_chat):

    def __init__(self, server, sock, addr):
        asynchat.async_chat.__init__(self, sock)
        self.set_terminator("\r\n")
        self.data = ""
        self.server = server

    def collect_incoming_data(self, data):
        self.data = self.data + data

    def found_terminator(self):
        obj = pickle.loads(self.data)
        self.route(obj)

    def route(self, obj):
        print "dont know how to route %s" % str(obj)


class EasyMsgServer(asyncore.dispatcher):

    channel_class = EasyMsgChannel

    def __init__(self, port=PORT):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.bind(("", port))
        self.listen(5)

    def handle_accept(self):
        con, addr = self.accept()
        self.create_channel(con, addr)
    
    def create_channel(self, con, addr):
        self.channel_class(self, con, addr)


client.py ------------------------------------------------------------------
import asyncore
from easymsg import EasyMsgRequest, PORT
from code import InteractiveConsole

def sender(host, port=PORT):
    def send(obj):
        req = EasyMsgRequest(host, obj, port=port)
        asyncore.loop()
    return send

if __name__ == '__main__':
    import sys
    import readline

    if len(sys.argv) < 2:
        print "usage: client.py  []"
        sys.exit(1)

    try:
        port = int(sys.argv[2])
    except (IndexError, ValueError):
        port = PORT

    v = globals().copy()
    v['s'] = v['send'] = sender(sys.argv[1], port)
    console = InteractiveConsole(v)
    console.interact()

server.py ------------------------------------------------------------------
import asyncore
from easymsg import EasyMsgServer, PORT

if __name__ == '__main__':
    import sys
    try:
        port = int(sys.argv[1])
    except (IndexError, ValueError):
        port = PORT
    s = EasyMsgServer(port)
    print "serving..."
    asyncore.loop()

forwarder.py ------------------------------------------------------------------
from easymsg import EasyMsgServer, EasyMsgChannel, EasyMsgRequest, PORT

class ForwarderChannel(EasyMsgChannel):
    def route(self, obj):
        server = self.server
        print "forwarding %s" % str(obj)
        EasyMsgRequest(server.dest_host, obj, port=server.dest_port)


class ForwarderServer(EasyMsgServer):
    
    channel_class = ForwarderChannel

    def __init__(self, dest_host, dest_port=PORT, port=PORT):
        self.dest_host = dest_host
        self.dest_port = dest_port
        EasyMsgServer.__init__(self, port)


if __name__ == '__main__':
    import sys
    import asyncore

    try:
        dest_host = sys.argv[1]
        dest_port = int(sys.argv[2])
    except (IndexError, ValueError):
        print "usage: forwarder.py   []"
        sys.exit(1)
    try:
        port = int(sys.argv[3])
    except (IndexError, ValueError):
        port = PORT
    s = ForwarderServer(dest_host, dest_port, port)
    print "serving..."
    asyncore.loop()

Graphing Pylons SQLAlchemy model paster command

Lovely title i know...

I already posted how to make pretty graphs from your sqlalchemy models, the following is a complete paster command using that code.

import os
import sys

from paste.script.command import Command, BadCommand
from paste.script.filemaker import FileOp
from paste.deploy import loadapp, appconfig
from paste.script.pluginlib import find_egg_info_dir

import pydot

def graph_meta(meta, filename="dbgraph.jpeg"):
    d = pydot.Dot()
    nodes = {}
    
    for table in meta.tables.itervalues():
        n = nodes[table.name] = pydot.Node(table.name)
        d.add_node(n)

    for table in meta.tables.itervalues():
        for c in table.c:
            for fk in c.foreign_keys:
                e = pydot.Edge(nodes[table.name], nodes[fk.column.table.name], 
                    label=fk.column.name)
                d.add_edge(e)

    d.write_jpeg(filename)

 
def can_import(name):
    """Attempt to __import__ the specified package/module, returning True when
    succeeding, otherwise False"""
    try:
        __import__(name)
        return True
    except ImportError:
        return False


class DBGraph(Command):
    summary = 'create a graph of the model'
    parser = Command.standard_parser(simulate=True)
    parser.add_option('--output', '-o',
                      default="dbraph.jpg",
                      dest='output',
                      help="output graph to file (default: dbgraph.jpg)")

    group_name = 'pylons'

    def command(self):
        if len(self.args) == 0:
            # Assume the .ini file is ./development.ini
            config_file = 'development.ini'
            if not os.path.isfile(config_file):
                raise BadCommand('%sError: CONFIG_FILE not found at: .%s%s\n'
                                 'Please specify a CONFIG_FILE' % \
                                 (self.parser.get_usage(), os.path.sep,
                                  config_file))
        else:
            config_file = self.args.pop()

        config_name = 'config:%s' % config_file
        here_dir = os.getcwd()
        locs = dict(__name__="pylons-admin")

        wsgiapp = loadapp(config_name, relative_to=here_dir)

        # Determine the package name from the .egg-info top_level.txt.
        egg_info = find_egg_info_dir(here_dir)
        f = open(os.path.join(egg_info, 'top_level.txt'))
        packages = [l.strip() for l in f.readlines()
                    if l.strip() and not l.strip().startswith('#')]
        f.close()

        # Start the rest of our imports now that the app is loaded
        found_base = False
        for pkg_name in packages:
            # Import all objects from the base module
            base_module = pkg_name + '.lib.base'
            found_base = can_import(base_module)
            if not found_base:
                # Minimal template
                base_module = pkg_name + '.controllers'
                found_base = can_import(base_module)

            if found_base:
                break

        if not found_base:
            raise ImportError("Could not import base module. Are you sure "
                              "this is a Pylons app?")

        base = sys.modules[base_module]
        base_public = [__name for __name in dir(base) if not \
                       __name.startswith('_') or __name == '_']
        for name in base_public:
            locs[name] = getattr(base, name)
        locs.update(dict(wsgiapp=wsgiapp))

        graph_meta(locs['model'].meta, self.options.output)

If you want to use this drop it in a file called "commands.py" inside you pylons app, add the following to your setup.py:

    [paste.paster_command]
    dbgraph = .commands:DBGraph

Then run "python setup.py egg_info" and youll be able to run the command.

If your all very good ill move this to a package and post the egg here... ;)

07/04/2008

pyglons and pymunk brick game

In the pyglons repository is now a complete brick game based on pyglons! It uses pymunk (using chipmunk) for physics. The game features splash screens, menu, and fully working game with lives, scoring, paused state, etc etc ... everything a "real" game has :) The only change from the templates pyglons created has been the game state class so far ( as it should be! ) altho I think a little customisation might be on the way...

I think Ive got a few ideas to improve pyglons a bit now...

A little Pylons/Routes helper

If you have trouble remembering route names in Pylons using Routes you might like to try adding these few lines to the end of the make_map function in config/routing.py before "return map":

    if config['debug']:
        route_names = map._routenames.keys()
        route_names.sort()
        for name in route_names:
            log.debug("%s = %s" % (name, map._routenames[name].routepath))

Now when pylons starts up you will get a list of all named routes printed to the log.

06/04/2008

more pyglons

pyglons development has been going full steam ahead and now includes config file support ( including loading logging config ), much nicer event handling in the base state, the possibility to play videos in the splash screen and, as they say, much much more :)

Actually pyglons should hopefully be finished now except for bugs so I can go back to what I was making in the first place..

05/04/2008

pyglons!

I had a crazy idea yesterday evening and Ive just finished the first version. pyglons takes ideas from pylons and utilises pastes paster command to make creating pyglet games a snap.

At the moment pyglons will generate you a small working application with splash screen, menu, help and vary basic game state skeleton ready for you to add your code. I plan to use the pylons idea of not forcing the developers to use anything they dont want to whilst still maintaining some sane defaults

Example and code can be got from http://code.google.com/p/pyglons/

04/04/2008

Pylons Paster Shell Logging

For anyone else looking to see the SQL SQLAlchemy generates while using the paster shell command you need to have the following in your development.ini:

sqlalchemy.default.echo = true

and then in the shell type the following:

>>> g.sa_engine.logger.disabled = 0

Browsing the pylons source quickly I cant see why this is needed, the logging looks to be setup properly...

I found the solution here for anyone interested http://groups.google.hu/group/pylons-discuss/msg/057e70d62dead733