pyatspi

WillieWalker and PeterParente met at CSUN07 and discussed the creation of an "official" Python wrapper for AT-SPI. This wrapper is now part of the GNOME at-spi module for any assistive technology or automated testing tool to use.

Goals

See the original specification for more details.

Projects using pyatspi

Current code

The code is feature complete according to the spec below. The code will ship with at-spi in GNOME 2.20. The code will work with previous versions of AT-SPI, however. You can get it and install it on any system by doing the following:

svn co svn://svn.gnome.org/svn/at-spi/trunk/pyatspi pyatspi
cd pyatspi
sudo python setup.py install

API documentation

See http://www.gnome.org/~parente/pyatspi/doc for epydoc HTML.

Problems

Bugs and enhancements

To report a bug or request an enhancement, please use at-spi and select python-bindings as the component.

Example usage

Basic calls

   1 import pyatspi
   2 reg = pyatspi.Registry
   3 print reg.getDesktopCount()
   4 desktop = reg.getDesktop(0)
   5 # check if the desktop object is the same as its desktop interface
   6 print desktop == desktop.queryDesktop()
   7 try:
   8   # check if the desktop has the Table interface (definitely not)
   9   desktop.queryTable()
  10 except NotImplementedError:
  11   pass
  12 # get the number of running apps
  13 print len(desktop)
  14 # show all the running apps
  15 apps = [app.name for app in desktop]
  16 print apps

Event listening

   1 import pyatspi
   2 def callback(event):
   3   print event
   4 reg = pyatspi.Registry
   5 # register for focus and caret movement events
   6 reg.registerEventListener(callback, 'focus', 'object:text-caret-moved')
   7 # register for presses and releases of all keys with all possible modifiers
   8 reg.registerKeystrokeListener(callback, mask=pyatspi.allModifiers())
   9 reg.start()

Event generation

   1 import pyatspi
   2 reg = pyatspi.Registry
   3 # generate a button 1 click
   4 reg.generateMouseEvent(0, 0, pyatspi.MOUSE_B1C)
   5 # generate a press and release of Enter
   6 reg.generateKeyboardEvent(36, '', pyatspi.KEY_PRESSRELEASE)

Searching

   1 import pyatspi
   2 reg = pyatspi.Registry
   3 desktop = reg.getDesktop(0)
   4 # find the gedit application, breadth first
   5 gedit = pyatspi.findDescendant(desktop, lambda x: x.name == 'gedit', breadth_first=True)
   6 print gedit
   7 # find the first text area
   8 text = pyatspi.findDescendant(gedit, lambda x: x.getRoleName() == 'text')
   9 print text

Cache profiling

   1 import pyatspi
   2 import timeit
   3 import pprint
   4 import gobject
   5 def query(acc, n, *ints):
   6   '''Query to the given interfaces n times. Return the times taken to query.'''
   7   t_ints = []
   8   for i in ints:
   9     for j in xrange(n):
  10       t1 = timeit.default_timer()
  11       try:
  12         a = i()
  13       except NotImplementedError:
  14         pass
  15       t_ints.append(timeit.default_timer()-t1)
  16   return t_ints
  17 def props(acc, n):
  18   '''Fetch the name, description, and role name properties n times. Return the
  19   times taken to fetch.'''
  20   t_ints = []
  21   for j in xrange(n):
  22     t1 = timeit.default_timer()
  23     a = acc.name
  24     b = acc.description
  25     c = acc.getRoleName()
  26     t_ints.append(timeit.default_timer()-t1)
  27   return t_ints
  28 reg = pyatspi.Registry
  29 d = reg.getDesktop(0)
  30 gedit = pyatspi.findDescendant(d, lambda x: x.name == 'gedit', True)
  31 if gedit is None:
  32   raise RuntimeError('run gedit before starting the test')
  33 def testInterfaces():
  34   print 'testing interface speedup'
  35   print '========================='
  36   avg_i = []
  37   for caching in (None, pyatspi.CACHE_INTERFACES):
  38     text = pyatspi.findDescendant(gedit, lambda x: x.getRoleName() == 'text')
  39     pyatspi.setCacheLevel(caching)
  40     print 'cache level: ', caching, text
  41     results = query(text, 100, text.queryText, text.queryComponent,
  42                     text.queryStreamableContent, text.queryDesktop)
  43     avg_i.append(sum(results) / float(len(results)))
  44   print 'interface averages', avg_i
  45   print 'interface speedup', avg_i[0] / avg_i[1]
  46   print
  47 def testProps():
  48   print 'testing properties speedup'
  49   print '=========================='
  50   avg_p = []
  51   for caching in (None, pyatspi.CACHE_PROPERTIES):
  52     text = pyatspi.findDescendant(gedit, lambda x: x.getRoleName() == 'text')
  53     pyatspi.setCacheLevel(caching)
  54     print 'cache level: ', caching, text
  55     results = props(text, 100)
  56     avg_p.append(sum(results) / float(len(results)))
  57   print 'cache contents: ', pyatspi.printCache()
  58   print 'properties averages', avg_p
  59   print 'properties speedup', avg_p[0] / avg_p[1]
  60 def main():
  61   testInterfaces()
  62   testProps()
  63   print 'exiting...'
  64   print 'cache contents: ',
  65   pyatspi.printCache()
  66   reg.stop()
  67   return False
  68 gobject.timeout_add(0, main)
  69 reg.start()

GAP/PythonATSPI (last edited 2008-07-08 19:34:35 by BrianMerrell)