Python
2010-05-12
Some issues with zc.recipe.egg's python option
I've recently had to integrate a script/package into a Plone 2.5 buildout that runs on top of Python 2.4. Due to that package's dependence of a sane imaplib (and the one in Python 2.4 is buggy), I had to run the script with python2.6. To make a script run on a different python, you need to do:
[myscript] recipe = zc.regipe.egg eggs = myegg IMAPClient python = python26
The python26 option is actually the name of a buildout part that configures the python executable path
[python26] python = /usr/bin/python26
Now the problems. I've had various buildouts fail with a message "Cannot find egg myegg". After a bit of effort, we managed to trace the cause to this problem:
First, the python path in the [python26] part was incorect. Second, even if it pointed to the proper binary, the -devel packages for that python needed to be installed.
Well, now I know. Hopefully I'll remember it for the next time when I'll encounter the problem.
2010-01-28
Dear PyPi uploaders: don't use a download URL, upload your package instead!
Pypi's biggest mistake: allowing package entries without any upload
I think this is the Python Index biggest mistake, the one which makes it unreliable for serious development environments: exposing package entries with no real package files and just a download URL. To see what I'm talking about, just examine the PyPI records for BeautifulSoup or IPython, packages that are very common in buildouts. As soon as the author and publisher of that package has a hosting problem, the developer that uses that package also has a problem. Buildouts will completely fail and this will cause dead times and frustration for the developers.
Yes, there are a couple of PyPi mirrors, but they only mirror files hosted by PyPi. The central PyPi site will probably have better performance and availability then what individual groups and developers can provide and it's always easier to mirror one single website than many, so there's no shame or loss of pride in using the PyPi to host your files. Please do so!
2009-08-05
ReportLab is one frustrating piece of software...
I'm starting to grow a strong dislike to it, enough to steer me off Python to JVM, with Jython or Scala. Case in point: its authors considered that it's appropriate to overload the Paragraph class from the platypus module, to make it accept a form of "xml". There's no switch to disable this behavior and its xml handling is something straight out of the 90's: no namespaces, no validation. What happens for example, when you're trying to make a paragraph with the text:
"Some html documents contain <img> tags"
That's right, you'll encounter an error. The "<img>" fragment is interpreted as an img tag for the Paragraph, which will then complain that it's missing a src attribute. I wonder how can ReportLab be promoted as an enterprise solution and still display this behavior. The easiest solution, in my opinion, would be to create a new class that accepts properly formated xml, and RL's special tags need to be isolated in a new namespace. Fortunately, for the time being, I can do a replace for the "<" character with the < entity.
2009-02-15
Using mechanize to process protected Plone pages
One of my long-running projects involves a workflow where content is produced in a Plone site, with the data later extracted and processed in various ways (including scripting Scribus to layout this data in a book). Initially the site where the content was produced wasn't protected, so I could run a simple urllib script to download the content and process it using lxml. A recent change in the workflow security settings meant this script didn't work anymore and I had to remember how to login into a Plone site using urllib2. Some google searches found me nothing, but I remembered that the zope.testbrowser can be easily used to run a programatical browsing session, complete with cookies support. But trying to install zope.testbrowser standalone in a buildout didn't lend to too much success, due to some dependency problems (and even after I covered for those dependencies, it still broke somewhere in zope.app.testing).
The solution was to use just the mechanize package, on top of which zope.testbrowser is built. mechanize has a slightly different API (more modern) and doesn't do so much handholding as zope.testbrowser, but I only need to process one form. In the end my script looks something like this (the asxmllist page is just an xml page that returns a list of urls to the entities that I want to process):
import lxml.etree
import os
import os.path
import urllib
import mechanize
loginurl = "http://example.com/login_form"
listurl = "http://example.com/asxmllist"
def run():
curdir = os.getcwd()
datadir = os.path.join(curdir, 'data')
if not os.path.exists(datadir):
os.makedirs(datadir)
b = mechanize.Browser()
b.open(loginurl)
b.select_form(nr=1)
b['__ac_name'] = "username"
b['__ac_password'] = "password"
b.submit()
b.open(listurl)
etree = lxml.etree.parse(b.response())
for entry in etree.xpath('//entry'):
url = entry.get('url')
print "Processing " + url
e = lxml.etree.parse(b.open(url + '/asxml'))
id = e.find('id').text
print "Got entry " + id
fpath = os.path.join(datadir, id + '.xml')
f = open(fpath, 'w')
xml = lxml.etree.tostring(e)
f.write(xml)
f.close()
print "Saved " + fpath
if __name__ == "__main__":
run()
2008-12-03
Specific imports versus module imports in Python
I've always been a fan of explicit, separate imports in Python, vs generic module imports. Maybe it's because I like things to be explicit, or I've been spoiled by the way Eclipse Pydev deals with auto-importing, but I have an aversion towards generic module imports. To keep the code style consistent, I even rewrite to this style any foreign code that ends up in my code.
Specifically, I'm talking about:
from foo import Bar Bar()
vs
import foo foo.Bar()
Needless to say, I like the first style better.
There are advantages and disadvantages for each of the above methods:
- general module imports demand less from the editor (the programmer needs to type less "import" lines and the editor doesn't need to be very much aware of Python - emacs and vim users will probably favor this style)
- but it makes it hard sometimes to figure out where a module comes from. Example: you're deep down reading a module and you encounter a line referencing the component module. Now, where does that component module comes from? Could be both of the following imports:
from zope.app import component from zope import component
Of course, some people sensed this problem and write code like:
class MyView(object):
template = zope.app.pagetemplate.ViewPageTemplateFile('template.pt')
But this code is hard to read, hard to write and is almost at the limit with the self-imposed line length of 80 characters, which means most of the times it needs to be broken in two lines.
Today I became aware of what I consider the biggest advantage of using specific import (from foo import Bar). Heavy refactoring, in the absence of a comprehensive test suite, is a lot easier! When starting the program, the imports will fail and you get an immediate pointer to where you need to make a fix. If I would have used a generic module import, the error will have appeared only when trying to use the piece of code that calls foo.Bar().
TL;DR: use specific imports! If your editor doesn't support it, take some time to look at Eclipse Pydev or Netbeans, two free IDEs with Python support. You'll get:
- easier refactoring
- better code legibility
- I'll be happier when I need to reuse code snippets :-)
2008-11-01
A wxPython based tagcloud renderer
This is a small example app that will render a tag cloud with various font weights/height, based on their weight in the cloud. Not much to say here, hope it is useful to someone. It has actually been easier to design and create then expected, the only difficulty was in figuring out how to resize the buttons based on the size of their label. The algorithm could be improved to generate the cloud in a single pass, but I'm not gonna bother, it works fast enough right now.
import wx
TAGS = [
['animals', 0],
['architecture', 2],
['art', 5],
['august', 1],
['australia', 1],
['autumn', 3],
['baby', 5],
['band', 1],
['barcelona', 3],
['beach', 2],
['berlin', 5],
['bird', 1],
['birthday', 0],
['black', 1],
['blackandwhite', 5],
['blue', 2],
['boston', 3],
['bw', 5],
['california', 1],
['cameraphone', 1],
['camping', 1],
['canada', 4],
['canon', 0],
['car', 5],
['cat', 3],
['chicago', 4],
['china', 5],
['christmas', 0],
['church', 0],
['city', 1],
['clouds', 3],
['color', 5],
['concert', 5],
['cute', 3],
['dance', 0],
['day', 5],
['de', 4],
['dog', 0],
['england', 5],
['europe', 4],
['fall', 1],
['family', 4],
['festival', 1],
['film', 1],
['florida', 2],
['flower', 1]
]
WIDTH = 500
def getwidth(line):
return sum([l.GetSize()[0] for l in line])
def getheight(line):
return max([l.GetSize()[1] for l in line])
class TopFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.SetSize((500,400))
lines = []
for tag in TAGS:
l = wx.Button(self, -1, tag[0], style=wx.NO_BORDER|wx.BU_EXACTFIT)
weight = ((tag[1] % 2) and wx.BOLD) or wx.NORMAL
l.SetFont(wx.Font(10 + tag[1], wx.DEFAULT, wx.NORMAL, weight, 0, ""))
l.SetSize(l.GetBestSize())
w, _h = l.GetSize()
if lines:
line_width = getwidth(lines[-1])
if (line_width + w) > WIDTH: #make a new line
lines.append([l])
else:
lines[-1].append(l)
else: #lines is empty
lines.append([l])
row_pos = 0
for line in lines:
height = getheight(line)
row_w = 0
w = getwidth(line)
spacer = (WIDTH - w) / len(line)
for l in line:
dh = row_pos + height - l.GetSize()[1]
l.MoveXY(row_w, dh)
row_w += l.GetSize()[0] + spacer
row_pos += height
class MyApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
frame = TopFrame(None, - 1, "")
self.SetTopWindow(frame)
frame.Show()
return 1
def start():
app = MyApp(0)
app.MainLoop()
if __name__ == "__main__":
start()
2008-08-22
Bug in PyPi
The zope.app.form PyPi page looks awful, it should be fixed. Who's fault is that? Django, the framework that sits underneath (AFAIK), or the docutils libraries that probably parse the RST pages?
2008-01-28
Monty Python sketches and download script
A friend pointed me to a page with many many links to Monty Python sketches videos.
Being a Monty Python fan, I've countered with this script:
#!/usr/bin/python2.4
downloader = "/home/tibi/Software/youtube-dl.py '%s' -o '%s'"
url = "http://onemansblog.com/2006/12/01/a-compendium-of-150-monty-python-sketches/"
import lxml.html
import urllib
import os
content = urllib.urlopen(url)
etree = lxml.html.parse(content)
links = etree.xpath('//ol/li/a')
for link in links:
print "Downloading ", link.text
cmd = downloader % (link.get('href'), link.text + '.flv')
print os.popen(cmd).read()
It needs the YouTube downloader script and lxml 2.0.
2007-11-14
Trying out eggs and applications with workingenv, without poluting the system python
Ever since I've started working with zc.buildout I've grown to love the whole "environment as project" setup that it provides: eggs and python packages are installed and contained in the buildout and they don't polute the system packages. There is another alternative: workingenv, which, while simpler in scope, it is easier to setup and use. For example, recently I've wanted to see if lxml 2.0 can be installed in my system. I've had problems with the earlier alphas, which made it difficult or impossible to compile the egg. First, I've installed workingenv.py in my system:
sudo easy_install workingenv
Then I made a folder ~/tmp/lx, which I then used as a workingenv environment:
workingenv ~/tmp/lx
Next, I've activated the environment:
source ~/tmp/lx/bin/activate
And finally, I could easy_install my desired egg:
~/tmp/lx/bin/easy_install lxml
It took a while to compile, but in the end I had the egg installed in ~/tmp/lx/lib/python2.5/ and, while the workingenv was activated with "source bin/activate", I could run the system's python and have the eggs installed in the workingenv available to me.
2007-08-15
Scripting Adobe Indesign with Python
I'm working on a larger project, with various components and one of the components is producing content with Plone which is then to be imported into Indesign, where each item from Plone gets to fill in a 2 page template. There are about 500 of these items, so this can't be done manually. InDesign is stupid (or maybe plugin friendly / commercially clever) enough that, even though it can import XML files and even perform XSLT transformations on them and has a "Clone repeating elements" option on import, it can't create new frames or pages. The import procedure, which is a beast in itself, depends on having content placeholders tagged with the proper tags, but it doesn't keep the tags when copy&pasting elements. So the only option left is to script the entire import procedure. Fortunately, InDesign can be scripted through COM and its DOM is the same when accessing it through either its own JavaScript engine or Python, with the help of win32com.
First steps to get started is to install the win32com extensions. Next, start PythonWin and use the Tools menu to start the "COM Makepy utility", select the "Adobe InDesign CS2 Type Library". Next, try this in a Python prompt.
>>> import win32com.client
>>> app = win32com.client.Dispatch('InDesign.Application')
>>> doc = app.Documents.FirstItem()
>>> pages = doc.Pages
>>> newpage = pages.Add()
Unfortunately, doing a dir(app) won't show all elements available, for example the Documents collection. There are some very good resources to get me started, though: several InDesign tutorials and scripting references from Adobe, the ExtendedScriptToolkit which shows the DOM tree for the document once you assign it to a local variable and execute the script, and the Visual Basic Editor, whose Object Browser can be used to look at the available methods and properties for objects (you'll need to add the Indesign Type library as reference first).
2007-05-25
Running Python based software in the GPU?
2007-03-10
Embeding the Gecko engine in Python applications
I've got to deploy a new desktop based application, created, of course, with Zope 3. When you've got a hammer, everything looks like a nail, indeed. This application uses HTML and CSS as its presentation layer (right, it's a web page), with a bit of Ajax thrown in. Due do time constraints, I'm not even trying to get it to work properly on Internet Explorer (although it looks about 90% right, but I have some problems with Javascript), so I won't be embeding the Internet Explorer this time engine in my Python application, I'll just try to get the Gecko rendering engine, the one used in Firefox.
First, I've installed the ActiveX control for the Gecko engine. Following some ActiveX migration details on wxpython.org, I've generated a wxpython "binding" class, using the genaxmodule.py tool (after a quick dig in the Windows registry to find out which is the Mozilla control class name):
C:\Python24\Lib\site-packages\wx-2.6-msw-unicode\wx\tools>genaxmodule.py "Mozilla.Browser" Gecko
Creating module in: .\Gecko.py
ProgID: Mozilla.Browser.1
CLSID: {1339B54C-3453-11D2-93B9-000000000000}
The python code would that would use the ActiveX control would be something like this:
# -*- coding: ISO-8859-1 -*-
# generated by wxGlade 0.4.1cvs on Sat Mar 10 16:40:22 2007 from C:\Work\CourseBuilder.wxg
import wx
import Gecko
def createGeckoControl(parent, id):
gecko = Gecko.Gecko(parent, id)
return gecko
# begin wxGlade: dependencies
# end wxGlade
class MainFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: MainFrame.__init__
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.panel_1 = wx.Panel(self, -1)
self.gecko = createGeckoControl(self.panel_1, -1)
self.__set_properties()
self.__do_layout()
# end wxGlade
self.gecko.SetFocus()
self.gecko.Navigate2('http://slashdot.org', 0)
def __set_properties(self):
# begin wxGlade: MainFrame.__set_properties
self.SetTitle("frame_1")
# end wxGlade
def __do_layout(self):
# begin wxGlade: MainFrame.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.Add(self.gecko, 1, wx.EXPAND, 0)
self.panel_1.SetSizer(sizer_2)
sizer_1.Add(self.panel_1, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.Layout()
# end wxGlade
# end of class MainFrame
And the main app file:
#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-
# generated by wxGlade 0.4.1cvs on Sat Mar 10 16:40:22 2007 from C:\Work\CourseBuilder.wxg
import wx
from MainFrame import MainFrame
class CourseBuilderApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
main_frame = MainFrame(None, -1, "")
self.SetTopWindow(main_frame)
main_frame.Show()
return 1
# end of class CourseBuilderApp
if __name__ == "__main__":
CourseBuilderBrowser = CourseBuilderApp(0)
CourseBuilderBrowser.MainLoop()
Unfortunately I hit a problem that I can't find a solution for: this application crashes when I type something in the Gecko window. Blah :-( If anyone reading this know the answer, help me! :) Please! (tibi@life.org.ro)
My only option left is to go for the XULRunner and the MyBrowser demo. I'm not extremely happy about this, but at this point I don't see other options. Plus, it will be fun learning some about the Mozilla development platform.
Update: I've tested the Mozilla ActiveX Control using a simple Delphi 7 form and, while I don't get a crash, I still can't get it to accept keyboard input.
2007-02-10
Zope buildout quickstart
One of the bigger players in the latest move to automate Plone and Zope development and deployment has been buildout, so I figured it's about time to start learning it and see how it can help me. I'm writing this short recipe as I progress through learning buildout to help me remember this stuff later on.
Installing buildout
The easiest way to install buildout is to get easy_install (a manager for python packages) on your system. In order to get it installed, I had to do: (based on a custom python 2.4.3 installation in /opt/python):
$mkdir ~/buildout_play
$cd ~/buildout_play
$wget http://peak.telecommunity.com/dist/ez_setup.py
$sudo /opt/python/bin/python ez_setup.py
Next, install the zc.buildout package, using easy_install:
/opt/python/bin/easy_install zc.buildout
This will install the buildout egg in the python site-packages folder and create a 'buildout' script in the scripts folder, in my case /opt/python/bin/buildout.
Next, transform the buildout_play folder in buildout folder, by running:
/opt/python/bin/buildout -v
This will "bootstrap" that folder and prepare it as a buildout environment, also installing the setuptools and zc.buildout eggs. The buildout script will check every time it's being ran if those eggs are at their latest version, run it with the -N option if you want to skip that.
As a simple test for buildout, I've modified my buildout.cfg to contain the following lines:
[buildout]
parts = checkout
[checkout]
recipe = zc.recipe.zope3checkout
url = svn://svn.zope.org/repos/main/Zope3/trunk
This tells buildout to include a part named checkout, which is defined to use the "zc.recipe.zope3checkout" recipe, that is configured with the "url" option. Running
/opt/python/bin/buildout -v -N
will automatically grab the zc.recipe.zope3checkout egg, do a svn checkout in the parts/checkout folder and then compile in place the zope 3 checkout.
When developing new projects, to make this process easier, it is possible to put a bootstrap.py script in the folder where you're developing, which will automatically install setuptools (easy_install) and zc.buildout, transform that folder in a buildout folder and put a bin/bootstrap script that can be ran to do the build.
These are the basics to get started, see below for further details.
Reference
2006-11-06
One liner to get the common elements of several lists
While doing an exercise with a mockup catalog and indexes, I ran into the problem of filtering several lists and returning the common elements from the list. The following example demonstrate the usage of reduce(), one of the functional programming constructs that are less common and obvious in their usage.
>>> a = [1,2,3,4]
>>> b = [2,3,4,5]
>>> c = [1,2,4,6]
>>> d = [0,2,3]
>>> z = [a,b,c,d]
>>> z = reduce(lambda i,j:list(set(i).intersection(j)), z)
>>> z
[2]
2006-10-27
Catching and printing python exceptions
>>> import traceback, sys
>>> try:
... 1/0
... except:
... traceback.print_exc(sys.exc_info())
...
Traceback (most recent call last):
File "<stdin>", line 2, in ?
ZeroDivisionError: integer division or modulo by zero
>>>
2006-09-10
Adaptation explained
It seems that there's a proposal to introduce adaptation in Python 3000, which sparked a discussion and a nice introduction to adaptation on the python-dev list. Another explanation here.
2006-09-06
Printing frameworks and wxPython
As hard as I have tried to find, there's no good printing framework for wxPython. wxEasyPrinting sucks so much for anything more then simple text (for example, the table cells don't support specifying a height). Generating PDF files with a toolkit such as ReportLab (even with Platypus) is harder then it should be, especially when there's no ready made higher level framework. This page explores some of the common printing solutions on Windows.
My own solution to all these is a single-platform hack. Based on my previous experience of creating a "zope based desktop application" using an embeded Internet Explorer, I'm using Internet Explorer through ActiveX as a rendering and print preview engine. Building a new "form" is easy with any HTML GUI builder such as NVU or Dreamweaver and SimpleTAL bridges the gap between the application data and the presentation for printing. The trick is to display only the Print Preview dialog, which can't be done without going through Internet Explorer's back door.
In my generated HTML files I have the following snippet, which automatically calls the Print Preview dialog from Internet Explorer when the file is loaded.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
function printpr()
{
var OLECMDID = 7;
/* OLECMDID values:
* 6 - print
* 7 - print preview
* 1 - open window
* 4 - Save As
*/
var PROMPT = 1; // 2 DONTPROMPTUSER
var WebBrowser = "<OBJECT ID='WebBrowser1' WIDTH=0 HEIGHT=0 CLASSID='CLSID:8856F961-340A-11D0-A96B-00C04FD705A2' />";
document.body.insertAdjacentHTML('beforeEnd', WebBrowser);
WebBrowser1.ExecWB(OLECMDID, PROMPT);
WebBrowser1.outerHTML = "";
}
</script>
</head>
<body onload="printpr(); return false;">
</body>
</html>
Next, I have a frame with the IE ActiveX control embeded.
# -*- coding: ISO-8859-1 -*-To display the printing dialog I'm just dumping the generated HTML file to a temporary file and loading it into the IE control.
# generated by wxGlade 0.4.1 on Sun Apr 30 20:43:52 2006
import wx
import wx.lib.iewin as iewin
# begin wxGlade: dependencies
# end wxGlade
def ieWidget(parent, id):
ie = iewin.IEHtmlWindow(parent, -1)
return ie
class dlgIEFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: dlgIEFrame.__init__
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.window_4 = ieWidget(self, -1)
self.__set_properties()
self.__do_layout()
# end wxGlade
self.ie = self.window_4
self.ie.SetClientSizeWH(300, 400)
def __set_properties(self):
# begin wxGlade: dlgIEFrame.__set_properties
self.SetTitle("frame_1")
self.SetSize((792, 755))
# end wxGlade
def __do_layout(self):
# begin wxGlade: dlgIEFrame.__do_layout
sizer_59 = wx.BoxSizer(wx.VERTICAL)
sizer_59.Add(self.window_4, 1, wx.EXPAND, 0)
self.SetAutoLayout(True)
self.SetSizer(sizer_59)
self.Layout()
self.Centre()
# end wxGlade
# end of class dlgIEFrame
2006-08-23
Creating and managing a Windows service (part 4)
The final step is to "compile" everything into Windows executables using py2exe. For this I have the following setup.py file:
from distutils.core import setup
import py2exe
import sys
# If run without args, build executables, in quiet mode.
if len(sys.argv) == 1:
sys.argv.append("py2exe")
sys.argv.append("-q")
class Target:
def __init__(self, **kw):
self.__dict__.update(kw)
# for the versioninfo resources
self.version = "0.1.0"
self.company_name = "Pixelblaster Romania"
self.copyright = "Copyright 2006 Pixelblaster Romania"
self.name = "MyService service"
################################################################
# THE SERVICE
myservice_service = Target(
description = "MyService daemon",
version = "0.1.0",
modules = ["myservice"]
)
################################################################
# THE CONFIGURATOR
manifest_template = '''
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="5.0.0.0"
processorArchitecture="x86"
name="%(prog)s"
type="win32"
/>
<description>%(prog)s Program</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="X86"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
'''
RT_MANIFEST = 24
service_configurator = Target(
description = "The Service configurator",
script = "service_configurator.py",
other_resources = [(RT_MANIFEST, 1, manifest_template % dict(prog="configurator"))],
## icon_resources = [(1, "icon.ico")],
dest_base = "service_configurator")
#==============================
#CREATE SERVICE
create_service = Target(
description = "Creates the service",
script = "create_service.py",
dest_base = "create_service")
#==============================
#REMOVE SERVICE
remove_service = Target(
description = "Removes the service",
script = "remove_service.py",
dest_base = "remove_service")
################################################################
# THE SERVICE CONTROLER
################################################################
service_controler = Target(
description = "The configurator",
script = "service_controler.py",
other_resources = [(RT_MANIFEST, 1, manifest_template % dict(prog="configurator"))],
## icon_resources = [(1, "icon.ico")],
dest_base = "service_controler")
excludes = ["pywin", "pywin.debugger", "pywin.debugger.dbgcon",
"pywin.dialogs", "pywin.dialogs.list"]
setup(
options = {"py2exe": {
"compressed": 1,
"optimize": 2,
"excludes": excludes}},
zipfile = "lib/shared.zip",
service = [service],
windows = [configurator, service_controler],
console = [create_service, remove_service]
)
I think that's it. All that is left is to wrap everything to a nice
installer using InnoTools and just deploy it to the customer. :-)
Creating and managing a Windows service (part 3)
This third part shows the code needed to start/stop and set the startup type for the service. Actually, most of the necessary code sits in a ActiveState python-cookbook recipe, found here.
So, the code, using the recipe is simple (well, except that it's presented as a wxwidgets windows):
#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-
# generated by wxGlade 0.4.1cvs on Mon Aug 21 19:12:22 2006
import wx
from dlgControlerMainFrame import dlgControlerMainFrame
from service_utils import WService
from traceback_format import hook
import sys
class Alert:
def __init__ (self, parent, msg, style=wx.OK | wx.ICON_EXCLAMATION):
self.msgDlg = wx.MessageDialog(parent, msg, style=style)
def __call__(self):
res = self.msgDlg.ShowModal()
return res
STARTUP_TYPES = [ "boot", "system", "automatic", "manual", "disabled" ]
class MyApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
MainFrame = dlgControlerMainFrame(None, -1, "")
self.SetTopWindow(MainFrame)
MainFrame.Show()
self.dlg = MainFrame
def conf_hook(tb_type, tb_value, tb):
msg = hook(tb_type, tb_value, tb)
Alert(self.dlg, msg)()
sys.excepthook = conf_hook
self.extra_init()
return 1
def extra_init(self):
self.service = WService('Epaca')
self.dlg.button_3.Bind(wx.EVT_BUTTON, self.clickStart)
self.dlg.button_4.Bind(wx.EVT_BUTTON, self.clickStop)
self.dlg.button_2.Bind(wx.EVT_BUTTON, self.clickStatus)
self.dlg.combo_box_1.Bind(wx.EVT_COMBOBOX, self.comboClick)
self.reload()
def reload(self):
status = self.service.status(0)
self.dlg.MainFrame_statusbar.SetStatusText("The Epaca service is " + status)
start_type = self.service.infostartup()
ix = STARTUP_TYPES.index(start_type)
self.dlg.combo_box_1.SetSelection(ix)
def clickStart(self, event):
self.service.start()
self.service.fetchstatus('RUNNING')
self.reload()
def clickStop(self, event):
self.service.stop()
self.service.fetchstatus('STOPPED')
self.reload()
def clickStatus(self, event):
self.reload()
def comboClick(self, event):
val = self.dlg.combo_box_1.GetValue().strip().lower()
self.service.setstartup(val)
self.dlg.MainFrame_statusbar.SetStatusText('Service startup was changed')
# end of class MyApp
if __name__ == "__main__":
service_controler = MyApp(0)
service_controler.MainLoop()
The dialog window is generated with wxGlade in dlgControlerMainFrame.py , and has the following source code:
# -*- coding: ISO-8859-1 -*-Next part, the setup.py script that will create the exe files.
# generated by wxGlade 0.4.1cvs on Mon Aug 21 19:10:50 2006
import wx
# begin wxGlade: dependencies
# end wxGlade
class dlgControlerMainFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: dlgControlerMainFrame.__init__
kwds["style"] = wx.CAPTION|wx.CLOSE_BOX|wx.SYSTEM_MENU
wx.Frame.__init__(self, *args, **kwds)
self.panel_2 = wx.Panel(self, -1)
self.panel_1 = wx.Panel(self, -1)
self.MainFrame_statusbar = self.CreateStatusBar(1, 0)
self.button_3 = wx.Button(self.panel_1, -1, "Start")
self.button_4 = wx.Button(self.panel_1, -1, "Stop")
self.button_2 = wx.Button(self.panel_1, -1, "Refresh")
self.label_1 = wx.StaticText(self.panel_1, -1, "Set startup type:")
self.combo_box_1 = wx.ComboBox(self.panel_1, -1, choices=["Boot", "System", "Automatic", "Manual", "Disabled"], style=wx.CB_DROPDOWN|wx.CB_READONLY)
self.label_2 = wx.StaticText(self.panel_2, -1, "STARTUP TYPES:\n\nAutomatic: start at boot time\n\nManual: use the Epaca Service Controler \nto start the service\n\nDisabled: the service will not start", style=wx.ALIGN_RIGHT)
self.__set_properties()
self.__do_layout()
# end wxGlade
def __set_properties(self):
# begin wxGlade: dlgControlerMainFrame.__set_properties
self.SetTitle("Configure the Epaca Service")
self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))
self.MainFrame_statusbar.SetStatusWidths([-1])
# statusbar fields
MainFrame_statusbar_fields = ["MainFrame_statusbar"]
for i in range(len(MainFrame_statusbar_fields)):
self.MainFrame_statusbar.SetStatusText(MainFrame_statusbar_fields[i], i)
self.combo_box_1.SetSelection(-1)
# end wxGlade
def __do_layout(self):
# begin wxGlade: dlgControlerMainFrame.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.Add(self.button_3, 0, wx.ALL|wx.ADJUST_MINSIZE, 6)
sizer_2.Add(self.button_4, 0, wx.ALL|wx.ADJUST_MINSIZE, 6)
sizer_2.Add(self.button_2, 0, wx.ALL|wx.ADJUST_MINSIZE, 6)
sizer_2.Add(self.label_1, 0, wx.TOP|wx.ADJUST_MINSIZE, 10)
sizer_2.Add(self.combo_box_1, 0, wx.ALL|wx.ADJUST_MINSIZE, 6)
self.panel_1.SetAutoLayout(True)
self.panel_1.SetSizer(sizer_2)
sizer_2.Fit(self.panel_1)
sizer_2.SetSizeHints(self.panel_1)
sizer_1.Add(self.panel_1, 0, wx.ALL|wx.EXPAND, 6)
sizer_3.Add(self.label_2, 1, wx.ALL|wx.EXPAND|wx.ADJUST_MINSIZE, 6)
self.panel_2.SetAutoLayout(True)
self.panel_2.SetSizer(sizer_3)
sizer_3.Fit(self.panel_2)
sizer_3.SetSizeHints(self.panel_2)
sizer_1.Add(self.panel_2, 1, wx.EXPAND, 0)
self.SetAutoLayout(True)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
sizer_1.SetSizeHints(self)
self.Layout()
self.Centre()
# end wxGlade
# end of class dlgControlerMainFrame
Creating and managing a Windows service (part 2)
import sys
import win32service, win32serviceutil
from config import SERVICE_NAME, SERVICE_DISPLAY_NAME, SOFTWAREPATH
service_path = "%s\myservice.exe" % SOFTWAREPATH
def debug(msg):
print msg
def removeSvc():
debug('called removeSvc()')
win32serviceutil.RemoveService(SERVICE_NAME)
debug('...service was removed')
def installSvc():
debug('installSvc()')
hscm = win32service.OpenSCManager(None,None,win32service.SC_MANAGER_ALL_ACCESS)
debug('...opened svc manager')
try:
try:
hs = win32service.CreateService(hscm,
SERVICE_NAME,
SERVICE_DISPLAY_NAME,
win32service.SERVICE_ALL_ACCESS, # desired access
win32service.SERVICE_WIN32_OWN_PROCESS, # service type
win32service.SERVICE_DEMAND_START,
win32service.SERVICE_ERROR_NORMAL, # error control type
service_path,
None,
0,
None,
None,
None)
debug('...installed service')
win32service.CloseServiceHandle(hs)
except:
debug('...failed to install service')
debug('...%s' % sys.exc_info()[0])
debug('...%s' % sys.exc_info()[1])
finally:
debug('...closing service handle')
win32service.CloseServiceHandle(hscm)
if __name__ == "__main__":
installSvc()
raw_input('Press <ENTER> to continue...')
To delete the service, use this code:
import sys
import win32service, win32serviceutil
from config import SERVICE_NAME, SERVICE_DISPLAY_NAME
def debug(msg):
print msg
def removeSvc():
debug('called removeSvc()')
win32serviceutil.RemoveService(SERVICE_NAME)
debug('...service was removed')
if __name__ == "__main__":
removeSvc()
raw_input('Press <ENTER> to continue...')
Next, how to start/stop/set startup for this service.