The next PyWeek will be in May from the 6th to 13th. Not April.
Richard Jones' Log
The 14th Python Game Programming Challenge (PyWeek) is coming. It'll run from the 6th to the 13th of May.
The PyWeek challenge:
- Invites entrants to write a game in one week from scratch either as an individual or in a team,
- Is intended to be challenging and fun,
- Will increase the public body of game tools, code and expertise,
- Will let a lot of people actually finish a game, and
- May inspire new projects (with ready made teams!)
If you're in the US and can make it I'm co-presenting a 3 hour pygame tutorial at PyCon in March.
PyPI is now an OpenID provider.
To use this OpenID provider, enter pypi.python.org into any form that expects an OpenID*. Should the service not support OpenID 2, you will have to enter pypi.python.org/id/username instead (using your PyPI username.) Log into PyPI and visit your details page if you'd like to cut-n-paste the URL.
We follow the emerging approach that you have to sign into PyPI before signing into the actual services. This is intended to prevent phishing, as otherwise the relying party may fake PyPI's login page and collect your PyPI password (which they can still do if you fall for it.) It also avoids "nested" logins (i.e. where you need to log into PyPI with an OpenID while trying to login elsewhere with the PyPI id.)
If you find any problems with this service, please report them to the PyPI bug tracker.
*: of course for sites that extend PyPI this can be simplified to a simple button saying "link to my PyPI account".
This post contains some tips for testing Twisted applications. Note that I'm not using trial, the official Twisted test runner, for a variety of reasons.
Twisted Deferreds in Testing
You could use trial, but if you need to use another testing tool (nose, or behave) then you'll need to do a little more work.
To let your deferreds run (and make sure they do finish) wrap your test functions in the "@deferred" decorator from nose.twistedtools:
@deferred(1)
This will run the reactor for 1 second before declaring it hung.
If you want to see deferreds that are not behaving you will need to enable logging of Twisted events if they're not already enabled:
defer.setDebugging(1) observer = log.PythonLoggingObserver() observer.start()
and then patch nose.twistedtools as follows:
--- original/nose/twistedtools.py
+++ installed/nose/twistedtools.py
@@ -39,12 +39,19 @@
_twisted_thread = None
+_deferreds = []
+
def threaded_reactor():
"""
Start the Twisted reactor in a separate thread, if not already done.
Returns the reactor.
The thread will automatically be destroyed when all the tests are done.
"""
+ from twisted.internet import defer
+ def __init__(self):
+ _deferreds.append(self)
+ defer.DebugInfo.__init__ = __init__
+
global _twisted_thread
try:
from twisted.internet import reactor
@@ -135,6 +142,8 @@
def errback(failure):
# Retrieve and save full exception info
try:
+ if issubclass(failure.type, StandardError):
+ print failure
failure.raiseException()
except:
q.put(sys.exc_info())
@@ -156,6 +165,9 @@
try:
error = q.get(timeout=timeout)
except Empty:
+ for d in _deferreds:
+ if hasattr(d, 'creator') and not hasattr(d, 'invoker'):
+ print d._getDebugTracebacks()
raise TimeExpired("timeout expired before end of test (%f s.)"
% timeout)
# Re-raise all exceptions
(I tried a couple of times to figure out how trial does the above but failed miserably)
This will result in a display of all the deferreds still pending when the deferred decorator timeout fires. Note that the deferred decorator introduces a deferred that will not have terminated.
One outstanding problem I've not solved is that inlineCallbacks aren't displayed usefully in the failure debug output - you get told that it is some inline callback that is still hanging, but not where it was created.
Twisted Web and Selenium
Testing Twisted web using Selenium is made more complicated by the need to allow the Twisted reactor to run at the same time as the selenium webdriver is trying to poke at it.
In other applications I've tested like this I just run up the server being tested in a separate thread but I can't do that in the case of Twisted.
To make this work any selenium operation that will cause a Twisted reactor event must be deferred to a thread, hence the liberal sprinkling of deferToThread throughout this code. Some sections do not touch the reactor (for example, filling out a form once the form page has been loaded doesn't), so it can be straight selenium calls.
Note also that the pages are generated in a server dynamically using deferreds so I use a "done" tag marker at the end to indicate to the tests that the page has been fully generated. Selenium's timeout on element access allows me to cause the test to fail if the done marker does not appear.
For example this step function directly from a behave feature implementation:
@then('the message should be sent')
@deferred(1)
@inlineCallbacks
def step(context):
# wait until we're all done
context.browser.implicitly_wait(1)
yield threads.deferToThread(lambda: get_element(context.browser, id="done"))
# there should be a result container
results = yield threads.deferToThread(lambda: find_elements(context.browser, id="result"))
result = results[-1]
status = yield threads.deferToThread(lambda: get_element(result, id="status"))
# the status could be a number of values depending on synchronisiation of
# the tests / runner
assert status.text in 'created new accepted done'.split()
message = context.db.messages.values()[0]
if message.status != message.STATUS_DONE:
yield esme._wait_until_done(context)
More tips as I happen to think of them or discover them :-)
Thanks, Tarek, for this fun idea.
1. What's the coolest Python application, framework or library you have discovered in 2011?
I was pretty happy I discovered the awesomeness of bottle when researching my web micro-framework battle (video.)
I've started using Python 2.7 and 3.2 which is pretty cool (having been stuck in 2.3, gasp!)
I've been working with Twisted again after a couple of years' break and have discovered txpostgres and Twisted's own inlineCallbacks. Both are pretty cool. inlineCallbacks make Twisted programming quite bearable to think about :-)
2. What new programming technique did you learn in 2011?
I've been honing my testing skills and learned about Behaviour Driven Development. I've also learned to use both Mercurial and Git (and the latter still drives me insane sometimes) and their related websites bitbucket and github.
I also started using virtualenv and Fabric way more this year.
3. What's the name of the open source project you contributed the most in 2011? What did you do?
This year I created parse, overload and ooch and contributed to behave. I also helped run PyCon Australia.
4. What was the Python blog or website you read the most in 2011?
Planet Python and the New Packages feed from PyPI.
5. What are the three top things you want to learn in 2012?
I can never predict what I'll end up learning. Or what software I'll be writing.
I know I'll be learning a lot more about Mercuruial (and Git, I suppose) and the vagaries of modern software deployment.
I plan on working more on my test- and behaviour-driven development practices.
I also want to learn how to bake more things thanks to the Great British Bake Off :-)
6. What are the top software, app or lib you wish someone would write in 2012?
I'm hoping that someone will do something cool with the new PyPI OpenID provider ;-)
Want to do your own list? here's how:
- copy-paste the questions and answer to them in your blog
- tweet it with the #2012pythonmeme hashtag
We've had a fun couple of days sprinting on the Cheese Shop at PyCon AU where a number of contributors have fixed bugs and improved or added features (though always with the goal of keeping the service simple of course.)
In particular:
- Andy Todd helped clean up some aspects of the underlying database and fix up some of the sql.
- Capel Brunker added some more XML-RPC functionality, performed some tracker triage and also addressed some bugs and security issues.
- Kaleb Ufton, in his first contribution to Open Source development, added a bug tracker URL field to packages (which persists across releases and you must enter by editing through the website.) He also helped me sort out some twisted Apache configuration issues.
- I finally got around to writing the "newest packages" RSS feed.
There's another secret project we kicked off that will hopefully appear in the next couple of days, and some additional work that will hopefully come to fruition within a week or so. Stay tuned :-)
Thanks to everyone who contributed!
I needed support for "priv" instead of Fabric's built-in "sudo" support. I went through a number of (sometimes quite horrific) iterations before I settled on this relatively simple solution:
import contextlib
@contextlib.contextmanager
def priv(user):
'''Context manager to cause all run()'ed operations to be
priv('user')'ed.
Replaces env.shell with the priv command for the duration of the
context.
'''
save_shell = env.shell
env.shell = 'priv su - %s -c' % user
yield
env.shell = save_shell
This is then used in a fabfile like so:
with priv('remote_user'):
run('do some remote command as remote_user')
run('another remote command as remote_user')
The 13th Python Game Programming Challenge (PyWeek) is coming. It'll run from the 11th to the 18th of September.
The PyWeek challenge:
- Invites entrants to write a game in one week from scratch either as an individual or in a team,
- Is intended to be challenging and fun,
- Will hopefully increase the public body of game tools, code and expertise,
- Will let a lot of people actually finish a game, and
- May inspire new projects (with ready made teams!)
If you've never written a game before and would like to try things out then perhaps you could try either:
- The tutorial I presented at LCA 2010, Introduction to Game Programming, or
- The book Invent Your Own Computer Games With Python
A reminder that the Call for Proposals for PyCon Australia 2011 will be closing soon. We've had some great proposals so far, but there is still time left and program to fill.
PyCon Australia is Australia's only conference dedicated exclusively to the Python programming language, and will be held at the Sydney Masonic Center over the weekend of August 20 and 21. See below for more information and updates on:
- Call For Proposals
- Registration is Open
- More Sponsors Announced
Please share this message on to those you feel may be interested.
Call For Proposals
The deadline for proposal submission is the 2nd of May. That's only a few days away!
We are looking for proposals for talks on all aspects of Python programming from novice to advanced levels; applications and frameworks, or how you have been involved in introducing Python into your organisation. We're especially interested in short presentations that will teach conference-goers something new and useful. Can you show attendees how to use a module? Explore a Python language feature? Package an application?
We welcome first-time speakers; we are a community conference and we are eager to hear about your experience. If you have friends or colleagues who have something valuable to contribute, twist their arms to tell us about it! Please also forward this Call for Proposals to anyone that you feel may be interested.
The earlier you submit your proposal, the more time we will have to review and give you feedback before the program is finalised.
Speakers receive free registration for the conference, including a seat at the conference dinner. Don't miss out, submit your proposal today!
Registration is Open
We offer three levels of registration for PyCon Australia 2011:
- Corporate - $440
- If your company is paying for you to attend PyCon, please register at the corporate rate. You'll be helping to keep the conference affordable for all, especially for students and those needing financial aid. Government employees should also register at the corporate rate.
- Full (Early Bird) - $165
- This is the registration rate for regular attendees. We are offering a limited Early Bird rate for the first 50 registrations until the end of May. Once the Early Bird period ends, or when all Early Bird slots are filled, registration will increase to $198. Full registration includes one seat at the conference dinner on Saturday night.
- Student - $44
- For students able to present a valid student card we're offering this reduced rate. Student registrations do not include a seat at the conference dinner.
Additional seats at the conference dinner may be purchased for $77 each.
All prices include GST.
Information about the registration process is on the PyCon Australia website.
More Sponsors Announced
We are delighted to announce that ComOps has joined as a Gold Sponsor. Thank you to the following companies for their continuing support of Python and for helping to make PyCon Australia 2011 a reality:
Gold: Google
Gold: Microsoft
Gold: ComOps
Silver: Anchor
Silver: Enthought
Silver: Python Software Foundation
Thanks also to Linux Australia, who provide the overarching legal and organisational structure for PyCon Australia.
Lennart Regebro has provided me with a free copy of his self-published book "Porting to Python 3" so that I may review it. He has considerable experience in porting code from Python 2 to Python 3, and it shows in much of his advice and examples. Regebro has assembled an excellent cast of helpers: the technical reviewer Martin von Löwis also has much expertise with Python 3 (he implemented the first port of the popular Django web framework). Brett Canon's introduction also provides a good starting point for someone new to the story of Python 3.
The book's structure is well thought-out. The first chapter immediately invites the reader to make sure they really need to port their code, and what considerations might be taken into account when deciding to do so. The book even has advice, backed up with useful information, for those who are currently unable to port their code but may start preparing for doing so in the future.
The second chapter discusses strategies for moving to Python 3 (view it online). These are presented clearly with one section per strategy. Within each section there are clear references to the other, more detailed chapters of the book which may be used to implement each strategy.
Once you've decided which strategy to apply you can focus on the pertinent remaining chapters. These follow a pretty logical progression:
- firstly preparing the way by making your code more modern,
- introducing the 2to3 tool for automatically converting code to Python 3 and the various options for how to use it,
- talking through some common migration problems and some simple solutions to them,
- presenting some modern Python (2.6+) idioms that your code may use in Python 2 and 3, and
- supporting Python 2 and 3 without using the 2to3 tool.
The final two chapters are for more limited audiences and cover migrating C extensions and writing custom 2to3 tool fixers. Even though these are topics which most readers won't need to worry about they are still covered in good detail with minimum fuss. It is in this final chapter that Regebro's experience with porting gives weight to the advice and examples he presents.
Finally there's a couple of pretty comprehensive appendices covering over the main language incompatibilities and library changes which makes it a good reference.
As previously mentioned, the book is very well-organised giving the reader an easy path through the material depending on their situation. Throughout the book there are many pertinent and clear references to software written by others. The book suffers from some typesetting and grammatical errors, though these are minor and none could cause any confusion regarding the content.
CONCLUSION
There's a lot of information out there for porting from Python 2 to 3, but Regebro has produced a concise, well-organised and complete reference for doing so. It's not skimped on any detail though; the book covers all areas related to moving to Python 3. It's not just about porting code; it's also a handy book for any programmer who's grown up with Python 2 (or 1!) and is looking to move to Python 3.
The 12th Python Game Programming Challenge (PyWeek) is almost upon us. It'll run from the 3rd to the 10th of April. Registration for teams and individuals is now open on the website.
The PyWeek challenge:
- Invites entrants to write a game in one week from scratch either as an individual or in a team,
- Is intended to be challenging and fun,
- Will hopefully increase the public body of game tools, code and expertise,
- Will let a lot of people actually finish a game, and
- May inspire new projects (with ready made teams!)
If you've never written a game before and would like to try things out then perhaps you could try either:
- The tutorial I presented at LCA 2010, Introduction to Game Programming, or
- The book Invent Your Own Computer Games With Python
The second PyCon AU will be held in Sydney on the weekend of the 20th and 21st of August at the Sydney Masonic Center.
We are looking for proposals for talks on all aspects of Python programming from novice to advanced levels; applications and frameworks, or how you have been involved in introducing Python into your organisation. We're especially interested in short presentations that will teach conference-goers something new and useful. Can you show attendees how to use a module? Explore a Python language feature? Package an application?
We welcome first-time speakers; we are a community conference and we are eager to hear about your experience. If you have friends or colleagues who have something valuable to contribute, twist their arms to tell us about it! Please also forward this Call for Proposals to anyone that you feel may be interested.
To find out more go to the official Call for Proposals page.
The deadline for proposal submission is the 2nd of May.
See you in Sydney in August!
Richard Jones
http://pycon-au.org/
PyCon AU Program Chair
The second PyCon Australia will be held in Sydney on the weekend of the 20th and 21st of August at the Sydney Masonic Center.
The first PyCon Australia was held in June 2010 and attracted over 200 Python programming enthusiasts. The second event is expected to host over 250 attendees.
The weekend will see dozens of presentations introducing;
- Python programming and techniques,
- web programming,
- business applications,
- game development,
- education, science and mathematics,
- social issues,
- testing, databases, documentation and more!
We are hoping to organise sprints on the days following the conference proper.
International guests should note that Kiwi PyCon is to run on the following weekend, making it a great opportunity to attend a couple of awesome Down Under conferences and hopefully do some sprinting with the locals.
Richard Jones
http://pycon-au.org/
PyCon AU Committee
The 12th Python Game Programming Challenge (PyWeek) is coming. It'll run from the 3rd to the 10th of April.
The PyWeek challenge:
- Invites entrants to write a game in one week from scratch either as an individual or in a team,
- Is intended to be challenging and fun,
- Will hopefully increase the public body of game tools, code and expertise,
- Will let a lot of people actually finish a game, and
- May inspire new projects (with ready made teams!)
If you've never written a game before and would like to try things out then perhaps you could try either:
- The tutorial I presented at LCA 2010, Introduction to Game Programming, or
- The book Invent Your Own Computer Games With Python
These days I have a snazzy phone with an awesome IMAP client. A problem I have though is the large volume of email my work address gets. I have had my laptop Mail.app rules sorting those messages to sensible folders, but that approach is flawed when my laptop isn't active. I've moved to using sieve on the IMAP server and to do that I wrote the following simple script to convert my Mail.app rules to sieve commands. I didn't even realise the standard library had a plist module until I googled it :-)
import plistlib
p = plistlib.readPlist('/Users/sekrit/Library/Mail/MessageRules.plist')
print '''
require ["fileinto", "reject"];
'''
stmt = 'if'
def handle(header, expression, mbox):
global stmt
if header in ('To', 'Cc', 'From'):
print stmt, 'address :contains ["%s"] "%s" { fileinto "INBOX.%s"; }' % (header, expression, mbox)
elif header == 'Subject':
print stmt, 'header :matches "Subject" ["*%s*"] { fileinto "INBOX.%s"; }' % (expression, mbox)
else:
raise ValueError(header)
stmt = 'elsif'
for rule in p['rules']:
if 'Mailbox' not in rule: continue
mbox = rule['Mailbox'].split('/')[-1].split('.')[0]
print '#', rule['RuleName'], '->', mbox
for criteria in rule['Criteria']:
if criteria['Header'] == 'AnyRecipient':
handle('To', criteria['Expression'], mbox)
handle('Cc', criteria['Expression'], mbox)
else:
handle(criteria['Header'], criteria['Expression'], mbox)
print '''
else {
keep;
}
'''
ps. I realise my sieve-fu is probably really quite poor :-)
This post describes how to generate and solve a navigation mesh - a structure used to figure out how to move an entity around a game playing field in a sensible manner (ie. not running into things).
We start with a playing field with some rectangles in it that we'll call "blockers". These block movement in some way:
Having blockers with other shapes is quite possible - you'd just need to implement the intersects() method as used below.
We generate a quad tree structure by recursively:
- dividing the play field in quarter-sized nodes,
- look to see whether there are any blockers present in the new nodes,
- if there are:
- if the node size is larger than some minimum size we subdivide into quarters again as per step 1, else
- we discard it from the tree.
Assuming a square play field, the code for this is (using the Rect base class from cocos2d for convenience):
class QuadTreeNode(Rect):
def __init__(self, x, y, size, parent, minimum_size=None):
super(QuadTreeNode, self).__init__(x, y, size, size)
self.parent = parent
self.minimum_size = minimum_size or parent.minimum_size
def check_node(self, blockers):
for blocker in blockers:
if blocker.intersects(self):
if self.width <= self.minimum_size:
return False
self.subdivide(blockers)
if not self.nodes:
# my subdivision came up empty; remove me entirely
return False
break
return True
def subdivide(self, blockers):
size = self.width // 2
# bottom left
node = QuadTreeNode(self.x, self.y, size, self)
if node.check_node(blockers):
self.nodes.append(node)
# bottom right
node = QuadTreeNode(self.x + size, self.y, size, self)
if node.check_node(blockers):
self.nodes.append(node)
# top right
node = QuadTreeNode(self.x + size, self.y + size, size, self)
if node.check_node(blockers):
self.nodes.append(node)
# top left
node = QuadTreeNode(self.x, self.y + size, size, self)
if node.check_node(blockers):
self.nodes.append(node)
class QuadTree(QuadTreeNode):
def __init__(self, x, y, size, blockers, minimum_size):
super(QuadTree, self).__init__(x, y, size, None, minimum_size)
self.check_node(blockers)
This gives us a quad tree:
To reduce the number of nodes in the tree we can collapse adjacent nodes which share the same dimension on their shared side. To keep subsequent path generation sensible (as implemented here) we restrict the aspect ratio of the nodes so they're tend to be more square. The following fairly inelegant code achieves this:
def optimise(self, limit_aspect=True):
all = self.list_nodes()
all.sort(key=lambda x:x[1].y)
all.sort(key=lambda x:x[1].x)
changed = True
for i in range(2):
while changed:
changed = False
for i, (parent_one, one) in enumerate(all):
try:
parent_two, two = all[i+1]
except IndexError:
break
if one.bottomleft == two.bottomright and one.height == two.height:
if limit_aspect and two.width > 2 * two.height:
continue
parent_one.remove(one)
del all[i]
two.width += one.width
elif two.topleft == one.bottomleft and one.width == two.width:
if limit_aspect and two.height > 2 * two.width:
continue
parent_one.remove(one)
del all[i]
two.height += one.height
elif two.bottomleft == one.bottomright and one.height == two.height:
if limit_aspect and one.width > 2 * one.height:
continue
parent_two.remove(two)
del all[i+1]
one.width += two.width
elif one.topleft == two.bottomleft and one.width == two.width:
if limit_aspect and one.height > 2 * one.width:
continue
parent_two.remove(two)
del all[i+1]
one.height += two.height
else:
continue
changed = True
break
all.sort(key=lambda x:x[1].x)
all.sort(key=lambda x:x[1].y)
changed = True
This reduces the quad tree to:
Now that we have a quad tree of nodes we can analyse it to determine the neighbor lists for the various nodes, given the addition of the various n_ variables on the QuadTreeNode class.
def find_neighbors(self):
all = self.list_nodes()
for op, one in all:
for tp, two in all:
if one is two: continue
if one.left == two.right and (one.bottom < two.top and one.top > two.bottom):
one.n_left.append(two)
elif one.right == two.left and (one.bottom < two.top and one.top > two.bottom):
one.n_right.append(two)
elif one.top == two.bottom and (one.left < two.right and one.right > two.left):
one.n_top.append(two)
elif one.bottom == two.top and (one.left < two.right and one.right > two.left):
one.n_bottom.append(two)
Finally we may now solve a path to take us from one node to another. This uses the A* algorithm (the Python code below is almost identical to the pseudocode from Wikipedia.) as a method on the QuadTree class:
def astar(self, start, goal):
# The set of nodes already evaluated.
closed = set()
# Distance from start along optimal path.
g_score = {start: 0}
# Heuristic estimate of distance to goal
h_score = {start: distance(start.center, goal.center)}
# Open nodes; estimated total distance from start to goal through y
f_score = {start: h_score[start]}
came_from = {}
def reconstruct_path(node):
if node in came_from:
return reconstruct_path(came_from[node]) + [node]
else:
return [node]
while f_score:
l = [(v, k) for k, v in f_score.items()]
l.sort()
x = l[0][1]
if x is goal:
return reconstruct_path(came_from[goal])
del f_score[x]
closed.add(x)
for y in x.neighbors():
if y in closed:
continue
tentative_g_score = g_score[x] + distance(x.center, y.center)
if y not in f_score:
tentative_is_better = True
elif tentative_g_score < g_score[y]:
tentative_is_better = True
else:
tentative_is_better = False
if tentative_is_better:
came_from[y] = x
g_score[y] = tentative_g_score
h_score[y] = distance(y.center, goal.center)
f_score[y] = g_score[y] + h_score[y]
raise ValueError('unable to solve map')
And finally we can create a nice wrapper method which takes an arbitrary source and destination, uses the A* code to determine the nodes to travel along and extracts the relevant edge information to create a nice path:
def find_path(self, source, destination):
# solve the path from source -> destination using A*
source = self.quadtree.cell_at(*source)
destination = self.quadtree.cell_at(*destination)
path = self.quadtree.astar(source, destination) + [destination]
# now find the best points along the path rects to use
points = [source.center]
for i, cell in enumerate(path):
try:
next = path[i+1]
except IndexError:
points.append(cell.center)
break
if cell.top == next.bottom:
if cell.width < next.width:
points.append(cell.midtop)
else:
points.append(next.midbottom)
elif cell.bottom == next.top:
if cell.width < next.width:
points.append(cell.midbottom)
else:
points.append(next.midtop)
elif cell.right == next.left:
if cell.height < next.height:
points.append(cell.midright)
else:
points.append(next.midleft)
elif cell.left == next.right:
if cell.height < next.height:
points.append(cell.midleft)
else:
points.append(next.midright)
return points
The result is a path like this:
Note that it might be simpler to just have the A* solver work using the various major points around the QuadTreeNode rectangles; that's just not how I implemented it here :-)
There is an optimisation of the path left to peform that I ran out of time to implement:
- for each pair of lines in the path:
- join their ultimate endpoints to create a new line,
- determine whether the new line crosses out of the quad tree at any point,
- if it doesn't then replace the original lines from #1 with the new line at #2, and
- repeat until no lines are replaced.
If you're interested in the full working code, including the visualisation above, feel free to grab it from my PyWeek entry "Catch the Cootie" - the interesting file is in gamelib/navigation.py.
Georg has created a plugin for Sphinx that handles all that dynamic JSON Cheese Shop stuff, and it's called sphinxcontrib-cheeseshop. It's pretty darned cool :-)
Also I made a couple of other cosmetic changes to the Cheese Shop which you may have already noticed but should be apparent from this screenshot fragment:

I don't get to work on the Cheese Shop very often these days. I've been wanting to add this feature for a while though: being able to dynamically include the latest information about a package on some other website. Now I can!
On the Roundup project website I have a "download" box. People have asked that I include the version information in that box, but I'm lazy and don't want to have to update it manually when I do a release. Now the page includes this HTML:
<span id="release_info" class="note">Download:
<a href="http://pypi.python.org/pypi/roundup">latest</a></span>
<script type="text/javascript">
$.getJSON('http://pypi.python.org/pypi/roundup/json?callback=?', function(data) {
h = 'Download: ' + data.info.version;
for (var i=0, url; url=data.urls[i]; ++i) {
h += '<br><a href="' + url.url + '">' + url.filename + '</a>';
}
$('#release_info').html(h);
});
</script>
This was actually generated in Sphinx, and the magic there is:
.. raw:: html
<span id="release_info" class="note">Download:
<a href="http://pypi.python.org/pypi/roundup">latest</a></span>
<script type="text/javascript">
$.getJSON('http://pypi.python.org/pypi/roundup/json?callback=?', function(data) {
h = 'Download: ' + data.info.version;
for (var i=0, url; url=data.urls[i]; ++i) {
h += '<br><a href="' + url.url + '">' + url.filename + '</a>';
}
$('#release_info').html(h);
});
</script>
That code uses jQuery which is already included in a Sphinx build so there's nothing more to do.
On the Cheese Shop side you have the option to request the JSON for the latest release or a specific release. The following are (currently, until I do a new Roundup release) equivalent:
http://pypi.python.org/pypi/roundup/1.4.15/json
http://pypi.python.org/pypi/roundup/json
Note in the jQuery the callback guff - that's part of some JSONP thing I don't care to understand but took me the longest of all of the implementation to work around. Boo for very average documentation, guys...
A reminder: registration for PyCon Australia 2010 will close on the 22nd of June - several days before the event. Register now!
We've got a great line-up of talks and keynotes. We've got two social events organised - a CodeWar the night before and the conference dinner on Saturday night.
It's only a couple of weeks away now and I'm getting fairly excited ... also having some fun working on my presentation :-)
The next meeting of the Melbourne Python Users Group (MPUG) will be Monday the 10th of May at 6:30PM at Horse Bazaar.
This meeting will see a number of presentations on development and deployment including tools like fabric, pip, virtualenv and coverage during testing. We'll also hear about load-balancing xmlrpclib/jsonrpclib for robust distributed applications.
Full info on the meeting including directions and talks at the MPUG wiki page.
See you there!





