Last week I met Stephan Diehl, Michael Hierweck, Veit Schiele, and Jens Vagelpohl in Berlin for a sprint. Our chosen topic was “Python web application deployment”. In this post I’d like to recap our discussions, gocept’s perspective on those, and the deployment tool “batou” that we have been incubating in the last months.
zope.formlib has the ability to customize the used widget like this:
class KeywordsManagementForm(five.formlib.formbase.SubPageForm): form_fields = zope.formlib.form.Fields(IKeywords) form_fields['keywords'].custom_widget = KWSelectWidgetFactory
I do not like this approach for two reasons:
- the widget has to be set manually every time the specific field is used
- there is no easy way to get a display widget if the form or field is not editable for the user
Defining a new schema field and registering the widget for this field seems a bit heavy, so I came up with providing a marker interface on the field:
class IHaveSelectableKeywords(zope.interface.Interface): """Marker interface to get a special keywords widget.""" class IKeywords(zope.interface.Interface): keywords = zope.schema.List( title = _("Edit Keywords"), value_type = zope.schema.Choice( vocabulary=u"uc.keywords.Keywords")) zope.interface.alsoProvides(keywords, IHaveSelectableKeywords)
I registered the edit widget and display widget for the
IHaveSelectableKeywords interface, so the custom widget does not have to be set in the form like this (edit widget):
<adapter for=".IHaveSelectableKeywords zope.publisher.interfaces.browser.IBrowserRequest" provides="zope.app.form.browser.interfaces.ISimpleInputWidget" factory=".KWSelectWidgetFactory" permission="zope.Public" />
The whole company spent three days in Kloster Drübeck sprinting on internal tools and topics.
We overhauled our workflow for generating invoices and identified steps that we could automate.
require 'my_app.js' describe 'MyApp', -> it 'has read Douglas Adams', -> expect(new MyApp().calculate_the_answer()).toEqual(42)
and include them into your Python test suite with a single command:
import gocept.exttest def test_suite(): return gocept.exttest.makeSuite( os.environ.get('jasmine-bin'), '--coffee', '--json', pkg_resources.resource_filename('your.package', 'tests'))
The third area of our efforts was documentation, we designed a Sphinx skeleton to make it easy to get started writing docs, and created a template for eggs that contains the necessary boilerplate and codifies our packaging and documentation conventions. While the concrete details are probably a bit specific to our tastes, some of the general mechanincs might be interesting to others, so we’ll release gocept.package once we’ve got the missing integration tests sorted out.
For integration tests it can be helpful to have a fake HTTP server whose behaviour the tests can control. All necessary building blocks are even included in Python standard library. However, the BaseHTTPServer is surprisingly hard to shut down properly, so that it gives up the socket and everything.
While working on gocept.selenium, we came up with some code that does the trick (together with Jan-Wijbrand Kolman and Jan-Jaap Driessen).
class HTTPServer(BaseHTTPServer.HTTPServer): _continue = True def serve_until_shutdown(self): while self._continue: self.handle_request() def shutdown(self): self._continue = False # We fire a last request at the server in order to take it out of the # while loop in `self.serve_until_shutdown`. try: urllib2.urlopen( 'http://%s:%s/' % (self.server_name, self.server_port)) except urllib2.URLError: # If the server is already shut down, we receive a socket error, # which we ignore. pass self.server_close()
You might use this in a zope.testrunner layer like this:
class SilentRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): def log_message(self, format, *args): pass class HTTPServerLayer(object): host = 'localhost' def setUp(self): self.server = None self.port = random.randint(30000, 40000) self.start_server() def start_server(self): self.server = HTTPServer((self.host, self.port), SilentRequestHandler) self.server_thread = threading.Thread( target=self.server.serve_until_shutdown) self.server_thread.daemon = True self.server_thread.start() # Kludge: Wait a little as it sometimes takes a while to get the server # started. time.sleep(0.25) def stop_server(self): if self.server is None: return self.server.shutdown() self.server_thread.join() def tearDown(self): self.stop_server()
zc.sourcefactory is very handy to easily create a source (zope.schema.interfaces.IIterableSource to be precise) with corresponding titles and tokens for its contents. Every now and then a source requires an explicit interface. For zc.sourcefactory the following code snippet helps:
class IMySource(zope.schema.interfaces.IIterableSource): """my source""" class MySource( zc.sourcefactory.contextual.BasicContextualSourceFactory): """The source factory.""" class source_class( zc.sourcefactory.source.FactoredContextualSource): """This class is being instanciated by the factory. It *must* be called source_class. """ zope.interface.implements(IMySource) def getValues(self, context): …
Of course it is also possible to declare the source_class separately from the source factory and reference it. But since its sole purpose is to hold an implements declaration, I’m fine with defining it inline.