11.30.07
Posted in plone, python, quality, zope tagged plone zope python quality at 11:05 am by Tarek Ziadé
At Ingeniweb, we have started to define standards for our Plone projects using ZopeSkel. IngeniSkel is a thin layer on the top of ZopeSkel, that was:
- injecting Archetype content within existing products
- defining standard tests skeletons for all elements contained in products.
- providing other templates like the recipe one, that creates a skeleton for zc.buildout recipes
The injection idea was previously proposed by Martin, and Mustapha started to implement it in a branch. Erik F. has added the archetype content injection yesterday.
It means you can now inject new archetype based content with the Paster directly with ZopeSkel:
$ paster create -t archetype my.package $ cd my.package $ paster --help
usage: paster [paster_options] COMMAND [command_options]
options:
…
Commands:
create Create the file layout for a Python distribution
…
ZopeSkel local commands:
addcontent Adds plone content types to your project $ paster addcontent –list
Available templates:
contenttype: A content type skeleton
portlet: A Plone 3 portlet
view: A browser view skeleton
zcmlmeta: A ZCML meta directive skeleton $ paster addcontent
This is great, now the only thing it misses so we can drop IngeniSkel in favor of this enhanced version of ZopeSkel is generating tests modules on all templates and local commands, and the same way everytime. I started such a work in another branch to backport what we have it and I will propose it.
Why ? Because having tests that are written the same way on all layers of a Plone project is important to:
- automate some QA and documentation tasks
- make sure a newcomer won’t get lost on how to startup a new piece of code, with the right test fixture. If he takes too much time to prepare the test fixture, he’ll probably drop the TDD approach…
Edit: I have merged the the recipe template into ZopeSkel trunk already, as I’ve been asked to
Permalink
11.11.07
Posted in plone, python, zope tagged plone zope python cache at 10:26 am by Tarek Ziadé
On a Plone website, setting the cache headers with CacheFu or other tools is quite easy, but when you need to check on a given page which headers are inserted, it’s not always easy to know what will be called in the stack of rules you have set.
You can look at each page properties in you web browser to check this, but it is a pain and you won’t be able to make sure nothing is broken when you change, for example, your CacheFu settings.
So, having a dedicated functional test to control your website cache settings is a good way to avoid regressions. zope.testbrowser allows you to write this on a doctest, so it’s easy to gather all your cache headers control in one text file. To install it, you can use Easy Install:
$ easy_install zope.testbrowser
Here’s an example of such doctest (cache.txt):
=============
Testing cache
=============
>>> from zope.testbrowser.browser import Browser
This method is used to print a page headers::
>>> def headers(url, login=None, password=None):
... b = Browser()
... if login is not None and password is not None:
... b.addHeader('Authorization',
... 'Basic %s:%s' % (login, password))
... b.open(url)
... print b.headers
Let's try on the python front page::
>>> headers('http://python.org')
Date: ...
Server: Apache/2.2.3 (Debian) DAV/2 SVN/1.4.2 mod_ssl/2.2.3 OpenSSL/0.9.8c
Last-Modified: ...
ETag: "60193-3fd1-b811ca00"
Accept-Ranges: bytes
Content-Length: 16337
Connection: close
content-type: text/html; charset=utf-8
The page returns an ETag header, which tells the browser if the page has changed.
Let's try Plone's one::
>>> headers('http://plone.org')
Server: nginx/0.5.26
Date: ...
Connection: close
Content-Language: en
X-Cache-Headers-Set-By: CachingPolicyManager: /plone.org/caching_policy_manager
Expires: ...
Vary: Accept-Encoding
Last-Modified: Sun, 04 Dec 2005 12:13:31 GMT
X-Cache-Rules-Applied: yes
X-Caching-Rule-Id: frontpage
Cache-Control: max-age=0, s-maxage=3600, must-revalidate
X-Header-Set-Id: cache-in-proxy-1-hour
Content-Length: 44947
X-Varnish: 783893351 783878475
Age: 3364
Via: 1.1 varnish
Content-Type: text/html;charset=utf-8
imagetoolbar: no
It has more complex cache information (CacheFu) used by Varnish cache software.
Notice the use of Ellipsis (…) that replaces changing data like Dates. It can be called using a python module that can look like this:
import os
import unittest
import doctest
OPTIONFLAGS = (doctest.REPORT_ONLY_FIRST_FAILURE |
doctest.ELLIPSIS |
doctest.NORMALIZE_WHITESPACE)
def test_suite():
return doctest.DocFileSuite(os.path.basename('cache.txt'),
optionflags=OPTIONFLAGS)
if __name__ == '__main__':
test_suite()
This is not specific to Plone, and can be used on any website. Added to the stack of tests, this helps a lot on making sure the cache settings are doing what they are supposed to.
Permalink
11.01.07
Posted in plone, python, windows, zope tagged windows python service task win32 at 12:23 pm by Tarek Ziadé
I am posting this small entry because it took me quite a time to compute all informations to programmatically add tasks in Windows using pywin32, so this post should be an helpfull example.
The process to schedule a task is as follows:
- create an instance of the COM TaskScheduler interface
- adding a task within the task scheduler
- adding a trigger within the task
Here’s how it can look in Python, to create daily tasks:
import pythoncom, win32api
import time
from win32com.taskscheduler import taskscheduler
def create_daily_task(name, cmd, hour=None, minute=None):
"""creates a daily task"""
cmd = cmd.split()
ts = pythoncom.CoCreateInstance(taskscheduler.CLSID_CTaskScheduler,None,
pythoncom.CLSCTX_INPROC_SERVER,
taskscheduler.IID_ITaskScheduler)
if '%s.job' % name not in ts.Enum():
task = ts.NewWorkItem(name)
task.SetApplicationName(cmd[0])
task.SetParameters(’ ‘.join(cmd[1:]))
task.SetPriority(taskscheduler.REALTIME_PRIORITY_CLASS)
task.SetFlags(taskscheduler.TASK_FLAG_RUN_ONLY_IF_LOGGED_ON)
task.SetAccountInformation(”, None)
ts.AddWorkItem(name, task)
run_time = time.localtime(time.time() + 300)
tr_ind, tr = task.CreateTrigger()
tt = tr.GetTrigger()
tt.Flags = 0
tt.BeginYear = int(time.strftime(’%Y’, run_time))
tt.BeginMonth = int(time.strftime(’%m’, run_time))
tt.BeginDay = int(time.strftime(’%d’, run_time))
if minute is None:
tt.StartMinute = int(time.strftime(’%M’, run_time))
else:
tt.StartMinute = minute
if hour is None:
tt.StartHour = int(time.strftime(’%H’, run_time))
else:
tt.StartHour = hour
tt.TriggerType = int(taskscheduler.TASK_TIME_TRIGGER_DAILY)
tr.SetTrigger(tt)
pf = task.QueryInterface(pythoncom.IID_IPersistFile)
pf.Save(None,1)
task.Run()
else:
raise KeyError(”%s already exists” % name)
task = ts.Activate(name)
exit_code, startup_error_code = task.GetExitCode()
return win32api.FormatMessage(startup_error_code)
You can see an example of usage here, in the iw.win32 package we have started to build, to gather all win32 specific Python things.
Permalink