Tuesday, July 29, 2008

Python Magazine for July 2008



The July issue is available for download now.

Brandon Rhodes delivers another cover story for us, this time on Kinetic Style Sheets. KSS is a JavaScript engine that interprets files full of CSS-like instructions that can launch both client-side and server-side actions in response to the user's typing and clicks. Brandon makes it look easy, as usual.

Speaking of easy, Jonathan LaCour takes all of the mystery out of metaclasses with an explanation clear enough to make me reconsider using them in my own work.

This month also brings us the final installment of Learning Python with PyGame from Terry Hancock. The Cat and Mouse game comes together with sprites and more animation to create something ready to play.

Once you've finished learning PyGame, let Richard Jones inspire you to participate in PyWeek, a semi-annual game programming challenge using Python. Richard's journal walks through the evolution of his own game, and shows how much fun the contest can be.

Threaded Comments for Your Site from Eric Florenzano rounds out the feature articles for July. If you're building a web site using Django and want to support discussions or comments, check out Eric's article and django-threadedcomments.

In his Welcome to Python column, regular contributor Mark Mruss introduces the set data type. Jesse Noller "gets with" context managers in Completely Different, exploring the new feature available in Python 2.5's __future__ module. Finally, Steve Holden considers whether scientists and technologists should be more involved in social and political issues.

Enjoy!

Sunday, July 27, 2008

PyMOTW: uuid

The uuid module implements Universally Unique Identifiers as described in RFC 4122.

Module: uuid
Purpose: Generate unique identifiers for objects.
Python Version: 2.5

Description:

RFC 4122 defines a system for creating universally unique identifiers for resources in a way that does not require a central registrar. UUID values are 128 bits long and "can guarantee uniqueness across space and time". They are useful for ids for documents, hosts, application clients, and other situations where a unique value is necessary. The RFC is specifically geared toward creating a Uniform Resource Name namespace.

Three main algorithms are covered by the spec:

  • Using IEEE 802 MAC addresses as a source of uniqueness
  • Using pseudo-random numbers
  • Using well-known strings combined with cryptographic hashing


In all cases the seed value is combined with the system clock and a clock sequence value (to maintain uniqueness in case the clock was set backwards).

UUID 1 - IEEE 802 MAC Address:

UUID version 1 values are computed using the MAC address of the host. The uuid module uses getnode() to retrieve the MAC value on a given system:

import uuid

print hex(uuid.getnode())



$ python uuid_getnode.py
0x1ec200d9e0L


If a system has more than one network card, and so more than one MAC, any one of the values may be returned.

To generate a UUID for a given host, identified by its MAC address, use the uuid1() function. You can pass a node identifier, or leave the field blank to use the value returned by getnode().

import uuid

u = uuid.uuid1()

print u
print type(u)
print 'bytes :', repr(u.bytes)
print 'hex :', u.hex
print 'int :', u.int
print 'urn :', u.urn
print 'variant :', u.variant
print 'version :', u.version
print 'fields :', u.fields
print '\ttime_low : ', u.time_low
print '\ttime_mid : ', u.time_mid
print '\ttime_hi_version : ', u.time_hi_version
print '\tclock_seq_hi_variant: ', u.clock_seq_hi_variant
print '\tclock_seq_low : ', u.clock_seq_low
print '\tnode : ', u.node
print '\ttime : ', u.time
print '\tclock_seq : ', u.clock_seq


The components of the UUID object returned can be accessed through read-only instance attributes. Some attributes, such as hex, int, and urn, are different representations of the UUID value.


$ python uuid_uuid1.py
c8425fd2-5bec-11dd-b385-001ec200d9e0
<class 'uuid.UUID'>
bytes : '\xc8B_\xd2[\xec\x11\xdd\xb3\x85\x00\x1e\xc2\x00\xd9\xe0'
hex : c8425fd25bec11ddb385001ec200d9e0
int : 266190234244921474865513442896659929568
urn : urn:uuid:c8425fd2-5bec-11dd-b385-001ec200d9e0
variant : specified in RFC 4122
version : 1
fields : (3359793106L, 23532L, 4573L, 179L, 133L, 132103854560L)
time_low : 3359793106
time_mid : 23532
time_hi_version : 4573
clock_seq_hi_variant: 179
clock_seq_low : 133
node : 132103854560
time : 134364636421185490
clock_seq : 13189


Because of the time component, each time uuid1() is called a new value is returned.

import uuid

for i in xrange(3):
print uuid.uuid1()


Notice in this output that only the time component (at the beginning of the string) changes.


$ python uuid_uuid1_repeat.py
f48683ca-5bec-11dd-9168-001ec200d9e0
f4868a8c-5bec-11dd-9168-001ec200d9e0
f4868c3a-5bec-11dd-9168-001ec200d9e0


Of course, since your computer has a different MAC address than mine, you will see entirely different values if you run the examples, because the node identifier at the end of the UUID will change, too.

import uuid

node1 = uuid.getnode()
print hex(node1), uuid.uuid1(node1)

node2 = 0x1e5274040e
print hex(node2), uuid.uuid1(node2)



$ python uuid_uuid1_othermac.py
0x1ec200d9e0L 79e13e17-5bed-11dd-83e5-001ec200d9e0
0x1e5274040eL 79e1c1b0-5bed-11dd-9e3c-001e5274040e


UUID 3 and 5 - Name-Based Values:

It is also useful in some contexts to create UUID values from names instead of random or time-based values. Versions 3 and 5 of the UUID specification use cryptographic hash values (MD5 or SHA-1) to combine namespace-specific seed values with "names" (DNS hostnames, URLs, object ids, etc.). There are several well-known namespaces, identified by pre-defined UUID values, for working with DNS, URLs, ISO OIDs, and X.500 Distinguished Names. You can also define your own application-specific namespaces by generating and saving UUID values.

To create a UUID from a DNS name, pass uuid.NAMESPACE_DNS as the namespace argument to uuid3() or uuid5():

import uuid

hostnames = ['www.doughellmann.com', 'blog.doughellmann.com']

for name in hostnames:
print name
print '\tMD5 :', uuid.uuid3(uuid.NAMESPACE_DNS, name)
print '\tSHA-1 :', uuid.uuid5(uuid.NAMESPACE_DNS, name)



$ python uuid_uuid3_uuid5.py
www.doughellmann.com
MD5 : bcd02e22-68f0-3046-a512-327cca9def8f
SHA-1 : e3329b12-30b7-57c4-8117-c2cd34a87ce9
blog.doughellmann.com
MD5 : 9bdabfce-dfd6-37ab-8a3f-7f7293bcf111
SHA-1 : fa829736-7ef8-5239-9906-b4775a5abacb


The UUID value for the same name in a namespace is always the same, no matter when or where it is calculated. Values for the same name in different namespaces are different, of course.

import uuid

for i in xrange(3):
print uuid.uuid3(uuid.NAMESPACE_DNS, 'www.doughellmann.com')



$ python uuid_uuid3_repeat.py
bcd02e22-68f0-3046-a512-327cca9def8f
bcd02e22-68f0-3046-a512-327cca9def8f
bcd02e22-68f0-3046-a512-327cca9def8f


UUID 4 - Random Values:

Sometimes host-based and namespace-based UUID values are not "different enough". In cases where you want to use the UUID as a lookup key, a more random sequence of values with more differentiation is desirable. In these situations, use uuid4() to generate UUIDs from random values.

import uuid

for i in xrange(3):
print uuid.uuid4()



$ python uuid_uuid4.py
1b53263a-d6a7-4bb9-8930-c3acf48ba354
a224a8ee-de50-4aa1-8808-76a1b1b1a227
2fb446fa-afe8-4911-aebb-511ec06ad0ef


Working with UUID Objects:

In addition to generating new UUID values, you can parse strings in various formats to create UUID objects. This makes it easier to compare them, sort them, etc.

import uuid

def show(msg, l):
print msg
for v in l:
print '\t', v
print

input_values = [
'urn:uuid:f2f84497-b3bf-493a-bba9-7c68e6def80b',
'{417a5ebb-01f7-4ed5-aeac-3d56cd5037b0}',
'2115773a-5bf1-11dd-ab48-001ec200d9e0',
]

show('input_values', input_values)

uuids = [ uuid.UUID(s) for s in input_values ]
show('converted to uuids', uuids)

uuids.sort()
show('sorted', uuids)



$ python uuid_uuid_objects.py
input_values
urn:uuid:f2f84497-b3bf-493a-bba9-7c68e6def80b
{417a5ebb-01f7-4ed5-aeac-3d56cd5037b0}
2115773a-5bf1-11dd-ab48-001ec200d9e0

converted to uuids
f2f84497-b3bf-493a-bba9-7c68e6def80b
417a5ebb-01f7-4ed5-aeac-3d56cd5037b0
2115773a-5bf1-11dd-ab48-001ec200d9e0

sorted
2115773a-5bf1-11dd-ab48-001ec200d9e0
417a5ebb-01f7-4ed5-aeac-3d56cd5037b0
f2f84497-b3bf-493a-bba9-7c68e6def80b



References:

RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace
Python Module of the Week Home
Download Sample Code


Technorati Tags:
,


Saturday, July 26, 2008

virtualenvwrapper | And Now For Something Completely Different

Not content to leave well enough alone, I offer up some extensions to Ian Bicking's virtualenv script that make it even more useful.

This article was originally published by Python Magazine in May of 2008.

Read More

Sunday, July 20, 2008

svnbackup 1.6

Adam Crews provided a patch to svnbackup.sh that adds an option to let the user control the base name of dump files. I've rolled up the changes in release 1.6, available for download from the project home page.

Thanks, Adam!

PyMOTW: base64

The base64 module contains functions for translating binary data into a subset of ASCII suitable for transmission using plaintext protocols.

Module: base64
Purpose: Encode binary data into ASCII characters.
Python Version: 1.4 and later

Description:

The base64, base32, and base16 encodings convert 8 bit bytes to values with 6, 5, or 4 bits of useful data per byte, allowing non-ASCII bytes to be encoded as ASCII characters for transmission over protocols that require plain ASCII, such as SMTP. The "base" values correspond to the length of the alphabet used in each encoding. There are also URL-safe variations of the original encodings that use slightly different results.

Base 64 Encoding:

A basic example of encoding some text looks like this:

import base64

initial_data = open(__file__, 'rt').read()

encoded_data = base64.b64encode(initial_data)

num_initial = len(initial_data)
padding = { 0:0, 1:2, 2:1 }[num_initial % 3]

print '%d bytes before encoding' % num_initial
print 'Expect %d padding bytes' % padding
print '%d bytes after encoding' % len(encoded_data)
print
print encoded_data


The output shows the 529 bytes of the original source expand to 708 bytes after being encoded. The encoding process looks at each sequence of 24 bits in the input (3 bytes) and encodes those same 24 bits spread over 4 bytes in the output. The last two characters, the "==", are padding because the number of bits in the original string was not evenly divisible by 24.

There are no carriage returns in the output produced by the library, but I have inserted them in the output here to make this page render more cleanly in HTML.


$ python base64_b64encode.py
529 bytes before encoding
Expect 2 padding bytes
708 bytes after encoding

IyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgZW5jb2Rpbmc
6IHV0Zi04CiMKIyBDb3B5cmlnaHQgKGMpIDIwMDggRG
91ZyBIZWxsbWFubiBBbGwgcmlnaHRzIHJlc2VydmVkL
gojCiIiIgoiIiIKCl9fdmVyc2lvbl9fID0gIiRJZDog
cHltb3R3LnB5IDEyMzkgMjAwOC0wMS0xNiAxMDo1NTo
xOVogZGhlbGxtYW5uICQiCgppbXBvcnQgYmFzZTY0Cg
ppbml0aWFsX2RhdGEgPSBvcGVuKF9fZmlsZV9fLCAnc
nQnKS5yZWFkKCkKCmVuY29kZWRfZGF0YSA9IGJhc2U2
NC5iNjRlbmNvZGUoaW5pdGlhbF9kYXRhKQoKbnVtX2l
uaXRpYWwgPSBsZW4oaW5pdGlhbF9kYXRhKQpwYWRkaW
5nID0geyAwOjAsIDE6MiwgMjoxIH1bbnVtX2luaXRpY
WwgJSAzXQoKcHJpbnQgJyVkIGJ5dGVzIGJlZm9yZSBl
bmNvZGluZycgJSBudW1faW5pdGlhbApwcmludCAnRXh
wZWN0ICVkIHBhZGRpbmcgYnl0ZXMnICUgcGFkZGluZw
pwcmludCAnJWQgYnl0ZXMgYWZ0ZXIgZW5jb2RpbmcnI
CUgbGVuKGVuY29kZWRfZGF0YSkKcHJpbnQKcHJpbnQg
ZW5jb2RlZF9kYXRhCg==


Base 64 Decoding:

The encoded string can be converted back to the original form by taking 4 bytes and converting them to the original 3, using a reverse lookup. The b64decode() function does that for you.

import base64

original_string = 'This is the data, in the clear.'
print 'Original:', original_string

encoded_string = base64.b64encode(original_string)
print 'Encoded :', encoded_string

decoded_string = base64.b64decode(encoded_string)
print 'Decoded :', decoded_string



$ python base64_b64decode.py
Original: This is the data, in the clear.
Encoded : VGhpcyBpcyB0aGUgZGF0YSwgaW4gdGhlIGNsZWFyLg==
Decoded : This is the data, in the clear.


URL-safe Variations:

Because the default base64 alphabet may use + and /, and those two characters are used in URLs, it became necessary to specify an alternate encoding with substitutes for those characters. The + is replaced with a -, and / is replaced with underscore (_). Otherwise, the alphabet is the same.

import base64

for original in [ '\xfb\xef', '\xff\xff' ]:
print 'Original :', repr(original)
print 'Standard encoding:', base64.standard_b64encode(original)
print 'URL-safe encoding:', base64.urlsafe_b64encode(original)
print



$ python base64_urlsafe.py
Original : '\xfb\xef'
Standard encoding: ++8=
URL-safe encoding: --8=

Original : '\xff\xff'
Standard encoding: //8=
URL-safe encoding: __8=


Other Encodings:

Besides base 64, the module provides functions for working with base 32 and base 16 (hex) encoded data.

import base64

original_string = 'This is the data, in the clear.'
print 'Original:', original_string

encoded_string = base64.b32encode(original_string)
print 'Encoded :', encoded_string

decoded_string = base64.b32decode(encoded_string)
print 'Decoded :', decoded_string



$ python base64_base32.py
Original: This is the data, in the clear.
Encoded : KRUGS4ZANFZSA5DIMUQGIYLUMEWCA2LOEB2GQZJAMNWGKYLSFY======
Decoded : This is the data, in the clear.


The base 16 functions work with the hexadecimal alphabet.

import base64

original_string = 'This is the data, in the clear.'
print 'Original:', original_string

encoded_string = base64.b16encode(original_string)
print 'Encoded :', encoded_string

decoded_string = base64.b16decode(encoded_string)
print 'Decoded :', decoded_string



$ python base64_base16.py
Original: This is the data, in the clear.
Encoded : 546869732069732074686520646174612C20696E2074686520636C6561722E
Decoded : This is the data, in the clear.


References:

RFC 3548 - The Base16, Base32, and Base64 Data Encodings
Python Module of the Week Home
Download Sample Code


Technorati Tags:
,


Thursday, July 17, 2008

Attendee Pricing for PyWorks 2008

Pricing has been set for PyWorks 2008.

All packages include both the php|works and PyWorks conferences and/or tutorial day, lunch and snacks, evening events, and a free one-year subscription to either php|architect or Python Magazine (Print + PDF). See the signup page for more details.

Also remember that the speaker package includes free access to the conference, a travel and housing allowance, and a stipend for each presentation. Refer to our call for papers page if you are interested in presenting a talk or teaching a tutorial.

Tuesday, July 15, 2008

PyCon 2008 | And Now For Something Completely Different, April 2008

PyCon 2008 was held March 12-20 in Chicago. The explosive growth in attendance of the conference translated to an increase in energy and enthusiasm, from both attendees and organizers.

This column was originally published by Python Magazine in April of 2008.

Read More

Sunday, July 13, 2008

need more books

We just had some new book shelves installed in the living room to replace the somewhat aging free-standing units we had before. Now we have empty shelves. I don't expect that situation to last long.

New Shelves

Saturday, July 12, 2008

PyWorks conference blog



As I've previously mentioned MTA, publishers of Python Magazine, has announced the PyWorks conference to be held in Atlanta, GA on November 12-14, 2008.

We've started a blog so anyone interested in the conference can keep up with announcements. If you think you might want to attend or present at the conference, check it out!

Thursday, July 10, 2008

PyMOTW in Spanish

I am very excited to announce that Ernesto Rico Schmidt has begun a Spanish translation of my Python Module of the Week series. Ernesto is in Bolivia, and is starting this project as a way to contribute to the members of the Bolivian Free Software community who use Python.

So far, he has translated the prose and output messages in the articles covering the warnings and SimpleXMLRPCServer module. He will be translating new articles after they are posted to the main feed, and working through the previously published articles as well.

The full list of articles is available at http://denklab.org/articles/category/pymotw/, and of course there is an RSS feed.

I'm just floored that someone would take the time to do this. Thank you, Ernesto!

Monday, July 7, 2008

svnbackup 1.5

Matthew McDonald contributed a patch to let svnbackup work on repositories with spaces in the name. I also added a --compress option so users can choose between gzip and bzip2 (the default is still bzip2).

The new release is available from the download page at the Google Code project site.

Thanks, Matthew!

Sunday, July 6, 2008

PyMOTW: xmlrpclib

As a follow-up to last week's article on SimpleXMLRPCServer, this week covers the client-side library xmlrpclib.

Module: xmlrpclib
Purpose: Client-side library for XML-RPC communication.
Python Version: 2.2 and later

Description:

Last week we looked at the library for creating an XML-RPC server. The xmlrpclib module lets you communicate from Python with any XML-RPC server written in any language. All of the examples below use the server defined in xmlrpclib_server.py, available in the source distribution and repeated here for reference:



Connecting to a Server:

The simplest way to connect a client to a server is to instantiate a ServerProxy object, giving it the URI of the server. For example, the demo server runs on port 9000 of localhost:

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000')
print 'Ping:', server.ping()


In this case, the ping() method of the service takes no arguments and returns a single boolean value.


$ python xmlrpclib_ServerProxy.py
Ping: True


Other options are available to support alternate transport. Both HTTP and HTTPS are supported out of the box, as are basic authentication. You would only need to provide a transport class if your communication channel was not one of the supported types. It would be an interesting exercise, for example, to implement XML-RPC over SMTP. Not terribly useful, but interesting.

The verbose option gives you debugging information useful for working out where communication errors might be happening.

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000', verbose=True)
print 'Ping:', server.ping()



$ python xmlrpclib_ServerProxy_verbose.py
Ping: connect: (localhost, 9000)
connect fail: ('localhost', 9000)
connect: (localhost, 9000)
connect fail: ('localhost', 9000)
connect: (localhost, 9000)
send: 'POST /RPC2 HTTP/1.0\r\nHost: localhost:9000\r\nUser-Agent: xmlrpclib.py/1.0.1 (by www.pythonware.com)\r\nContent-Type: text/xml\r\nContent-Length: 98\r\n\r\n'
send: "<?xml version='1.0'?>\n<methodCall>\n<methodName>ping</methodName>\n<params>\n</params>\n</methodCall>\n"
reply: 'HTTP/1.0 200 OK\r\n'
header: Server: BaseHTTP/0.3 Python/2.5.1
header: Date: Sun, 06 Jul 2008 19:56:13 GMT
header: Content-type: text/xml
header: Content-length: 129
body: "<?xml version='1.0'?>\n<methodResponse>\n<params>\n<param>\n<value><boolean>1</boolean></value>\n</param>\n</params>\n</methodResponse>\n"
True


You can change the default encoding from UTF-8 if you need to use an alternate system.

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000', encoding='ISO-8859-1')
print 'Ping:', server.ping()


The server should automatically detect the correct encoding.


$ python xmlrpclib_ServerProxy_encoding.py
Ping: True


The allow_none option controls whether Python's None value is automatically translated to a nil value or if it causes an error.

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000', allow_none=True)
print 'Allowed:', server.show_type(None)

server = xmlrpclib.ServerProxy('http://localhost:9000', allow_none=False)
print 'Not allowed:', server.show_type(None)


Note that the error is raised locally if the client does not allow None, but can also be raised from within the server if it is not configured to allow None.


$ python xmlrpclib_ServerProxy_allow_none.py
Allowed: ['None', "<type 'NoneType'>", None]
Not allowed:
Traceback (most recent call last):
File "/Users/dhellmann/Documents/PyMOTW/in_progress/xmlrpclib/xmlrpclib_ServerProxy_allow_none.py", line 17, in <module>
print 'Not allowed:', server.show_type(None)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1147, in __call__
return self.__send(self.__name, args)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1431, in __request
allow_none=self.__allow_none)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1080, in dumps
data = m.dumps(params)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 623, in dumps
dump(v, write)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 635, in __dump
f(self, value, write)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 639, in dump_nil
raise TypeError, "cannot marshal None unless allow_none is enabled"
TypeError: cannot marshal None unless allow_none is enabled


The use_datetime option lets you pass datetime.datetime and related objects in to the proxy or receive them from the server. If use_datetime is False, the internal DateTime class is used to represent dates instead.

Data Types:

The XML-RPC protocol recognizes a limited set of common data types. The types can be passed as arguments or return values and combined to create more complex data structures.

import xmlrpclib
import datetime

server = xmlrpclib.ServerProxy('http://localhost:9000')

for t, v in [ ('boolean', True),
('integer', 1),
('floating-point number', 2.5),
('string', 'some text'),
('datetime', datetime.datetime.now()),
('array', ['a', 'list']),
('array', ('a', 'tuple')),
('structure', {'a':'dictionary'}),
]:
print '%-22s:' % t, server.show_type(v)


The simple types:


$ python xmlrpclib_types.py
boolean : ['True', "<type 'bool'>", True]
integer : ['1', "<type 'int'>", 1]
floating-point number : ['2.5', "<type 'float'>", 2.5]
string : ['some text', "<type 'str'>", 'some text']
datetime : ['20080706T16:22:49', "<type 'instance'>", <DateTime '20080706T16:22:49' at a5d030>]
array : ["['a', 'list']", "<type 'list'>", ['a', 'list']]
array : ["['a', 'tuple']", "<type 'list'>", ['a', 'tuple']]
structure : ["{'a': 'dictionary'}", "<type 'dict'>", {'a': 'dictionary'}]


And of course, they can be nested to create values of arbitrary complexity:

import xmlrpclib
import datetime
import pprint

server = xmlrpclib.ServerProxy('http://localhost:9000')

data = { 'boolean':True,
'integer': 1,
'floating-point number': 2.5,
'string': 'some text',
'datetime': datetime.datetime.now(),
'array': ['a', 'list'],
'array': ('a', 'tuple'),
'structure': {'a':'dictionary'},
}
arg = []
for i in range(3):
d = {}
d.update(data)
d['integer'] = i
arg.append(d)

print 'Before:'
pprint.pprint(arg)

print
print 'After:'
pprint.pprint(server.show_type(arg)[-1])



$ python xmlrpclib_types_nested.py
Before:
[{'array': ('a', 'tuple'),
'boolean': True,
'datetime': datetime.datetime(2008, 7, 6, 16, 24, 52, 348849),
'floating-point number': 2.5,
'integer': 0,
'string': 'some text',
'structure': {'a': 'dictionary'}},
{'array': ('a', 'tuple'),
'boolean': True,
'datetime': datetime.datetime(2008, 7, 6, 16, 24, 52, 348849),
'floating-point number': 2.5,
'integer': 1,
'string': 'some text',
'structure': {'a': 'dictionary'}},
{'array': ('a', 'tuple'),
'boolean': True,
'datetime': datetime.datetime(2008, 7, 6, 16, 24, 52, 348849),
'floating-point number': 2.5,
'integer': 2,
'string': 'some text',
'structure': {'a': 'dictionary'}}]

After:
[{'array': ['a', 'tuple'],
'boolean': True,
'datetime': <DateTime '20080706T16:24:52' at a5be18>,
'floating-point number': 2.5,
'integer': 0,
'string': 'some text',
'structure': {'a': 'dictionary'}},
{'array': ['a', 'tuple'],
'boolean': True,
'datetime': <DateTime '20080706T16:24:52' at a5bf30>,
'floating-point number': 2.5,
'integer': 1,
'string': 'some text',
'structure': {'a': 'dictionary'}},
{'array': ['a', 'tuple'],
'boolean': True,
'datetime': <DateTime '20080706T16:24:52' at a5bf80>,
'floating-point number': 2.5,
'integer': 2,
'string': 'some text',
'structure': {'a': 'dictionary'}}]


Passing Objects:

Instances of Python classes are treated as structures and passed as a dictionary, with the attributes of the object as values in the dictionary.

import xmlrpclib

class MyObj:
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return 'MyObj(%s, %s)' % (repr(self.a), repr(self.b))

server = xmlrpclib.ServerProxy('http://localhost:9000')

o = MyObj(1, 'b goes here')
print 'o=', o
print server.show_type(o)

o2 = MyObj(2, o)
print 'o2=', o2
print server.show_type(o2)


Round-tripping the value gives a dictionary on the client, since there is nothing encoded in the values to tell the server (or client) that it should be instantiated as part of a class.


$ python xmlrpclib_types_object.py
o= MyObj(1, 'b goes here')
["{'a': 1, 'b': 'b goes here'}", "<type 'dict'>", {'a': 1, 'b': 'b goes here'}]
o2= MyObj(2, MyObj(1, 'b goes here'))
["{'a': 2, 'b': {'a': 1, 'b': 'b goes here'}}", "<type 'dict'>", {'a': 2, 'b': {'a': 1, 'b': 'b goes here'}}]


Binary Data:

All values passed to the server are encoded and escaped automatically. However, some data types may contain characters that are not valid XML. For example, binary image data may include byte values in the ASCII control range 0 to 31. If you need to pass binary data, it is best to use the Binary class to encode it for transport.

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000')

s = 'This is a string with control characters' + '\0'
print 'Local string:', s

data = xmlrpclib.Binary(s)
print 'As binary:', server.send_back_binary(data)

print 'As string:', server.show_type(s)


If we pass the string containing a NULL byte to show_type(), an exception is raised in the XML parser:


$ python xmlrpclib_Binary.py
Local string: This is a string with control characters
As binary: This is a string with control characters
As string:
Traceback (most recent call last):
File "/Users/dhellmann/Documents/PyMOTW/in_progress/xmlrpclib/xmlrpclib_Binary.py", line 21, in <module>
print 'As string:', server.show_type(s)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1147, in __call__
return self.__send(self.__name, args)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1437, in __request
verbose=self.__verbose
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1201, in request
return self._parse_response(h.getfile(), sock)
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1340, in _parse_response
return u.close()
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 787, in close
raise Fault(**self._stack[0])
xmlrpclib.Fault: <Fault 1: "<class 'xml.parsers.expat.ExpatError'>:not well-formed (invalid token): line 6, column 55">


Binary data can also be used to send objects using pickles. The normal security issues related to sending what amounts to executable code over the wire apply here (i.e., don't do this unless you're sure your communication channel is secure).

import xmlrpclib
import cPickle as pickle

class MyObj:
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return 'MyObj(%s, %s)' % (repr(self.a), repr(self.b))

server = xmlrpclib.ServerProxy('http://localhost:9000')

o = MyObj(1, 'b goes here')
print 'Local:', o, id(o)

print 'As object:', server.show_type(o)

p = pickle.dumps(o)
b = xmlrpclib.Binary(p)
r = server.send_back_binary(b)

o2 = pickle.loads(r.data)
print 'From pickle:', o2, id(o2)


Remember, the data attribute of the Binary instance contains the pickled version of the object, so it has to be unpickled before it can be used. That results in a different object (with a new id value).


$ python xmlrpclib_Binary_pickle.py
Local: MyObj(1, 'b goes here') 9620936
As object: ["{'a': 1, 'b': 'b goes here'}", "<type 'dict'>", {'a': 1, 'b': 'b goes here'}]
From pickle: MyObj(1, 'b goes here') 11049200


Exception Handling:

Since the XML-RPC server might be written in any language, exception classes cannot be transmitted directly. Instead, exceptions raised in the server are converted to Fault objects and raised as exceptions locally.

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000')
try:
server.raises_exception('A message')
except Exception, err:
print 'Fault code:', err.faultCode
print 'Message :', err.faultString



$ python xmlrpclib_exception.py
Fault code: 1
Message : <type 'exceptions.RuntimeError'>:A message


MultiCall:

Multicall is an extension to the XML-RPC protocol to allow more than one call to be sent at the same time, with the responses collected and returned to the caller. The MultiCall class was added to xmlrpclib in Python 2.4. To use a MultiCall instance, invoke the methods on it as with a ServerProxy, then call the object with no arguments. The result is an iterator with the results.

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000')

multicall = xmlrpclib.MultiCall(server)
multicall.ping()
multicall.show_type(1)
multicall.show_type('string')

for i, r in enumerate(multicall()):
print i, r



$ python xmlrpclib_MultiCall.py
0 True
1 ['1', "<type 'int'>", 1]
2 ['string', "<type 'str'>", 'string']


If one of the calls causes a Fault or otherwise raises an exception, the exception is raised when the result is produced from the iterator and no more results are available.

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000')

multicall = xmlrpclib.MultiCall(server)
multicall.ping()
multicall.show_type(1)
multicall.raises_exception('Next to last call stops execution')
multicall.show_type('string')

for i, r in enumerate(multicall()):
print i, r



$ python xmlrpclib_MultiCall_exception.py
0 True
1 ['1', "<type 'int'>", 1]
Traceback (most recent call last):
File "/Users/dhellmann/Documents/PyMOTW/in_progress/xmlrpclib/xmlrpclib_MultiCall_exception.py", line 21, in <module>
for i, r in enumerate(multicall()):
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 949, in __getitem__
raise Fault(item['faultCode'], item['faultString'])
xmlrpclib.Fault: <Fault 1: "<type 'exceptions.RuntimeError'>:Next to last call stops execution">


References:

PyMOTW: SimpleXMLRPCServer
XML-RPC How To
XML-RPC Extensions
Python Module of the Week Home
Download Sample Code


Technorati Tags:
,


Tuesday, July 1, 2008

Automated Testing with unittest and Proctor

Automated testing is an important part of Agile development methodologies, and the practice is seeing increasing adoption even in environments where other Agile tools are not used. This article discusses testing techniques for you to use with the open source tool Proctor. By using Proctor, you will not only manage your automated test suite more effectively, but you will also obtain better results in the process.

This article was originally published in Python Magazine in March of 2008.

Read more