Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save tamizhgeek/640248 to your computer and use it in GitHub Desktop.
Save tamizhgeek/640248 to your computer and use it in GitHub Desktop.
\documentclass {beamer}
\usepackage{graphics}
\usepackage{listings}
\usetheme{Berkeley}
\title {Plugin frameworks}
\subtitle {3 approaches to designing plugin APIs}
\author[noufal] {Noufal Ibrahim \\ \texttt{[email protected]}}
\date {25 September 2010}
\institute {PyCon India 2010, MSRIT, Bangalore}
\begin{document}
\lstset{language=Python,
basicstyle=\tiny,
numbers=left,
numberstyle=\tiny,
numbersep=5pt,
commentstyle=\color{blue},
showstringspaces=false
}
\begin{frame}
\titlepage
\end{frame}
\section {Introduction}
\subsection {About me}
\begin{frame}
\frametitle {About me}
\begin{itemize}
\item Freelance programmer/trainer based in Bangalore
\item Organiser of PyCon India 2009, 2010
\end{itemize}
\end{frame}
\subsection {About this talk}
\begin{frame}
\frametitle {About this talk}
\begin{itemize}
\item This talk about approaches to writing plugin frameworks using examples
\begin {enumerate}
\item TRAC - The project planning/tracking tool from edgewall software.
\item py.test - The test harness from the PyPy project.
\item Emacs - The extensible text editor from the GNU project.
\end {enumerate}
\item Informed by my lifelong(?) experiences with Emacs and my work
with \texttt{py.test} and \texttt{trac} over the past two years
\item Inspired by a Stack Overflow discussion I had at \texttt{http://bit.ly/bznQ2g}
\end{itemize}
\end{frame}
\subsection {Plugin frameworks}
\begin {frame}
\frametitle {Plugin frameworks}
\begin{itemize}
\item Controlled ways to run user code inside your application
\pause
\item Used to shift the burden of writing your program to your user
\pause
\item Used to defer important decisions for later so that they can
be made when you know enough
\pause
\item Used to write \emph{frameworks} that can be used for multiple
tasks
\pause
\item To reduce the size of the application
\pause
\item Generally fun to do
\end{itemize}
\end {frame}
\section {Case \#1 : Trac}
\subsection {Trac Component architecture}
\begin{frame}
\frametitle {Trac : Component architecture}
\begin{itemize}
\item Designed using a \emph{Component architecture}.
\item Each component exposes ``extension points'' that can plugged
into by other components.
\includegraphics[height=3cm]{xtnpt.png}
\item Components are singletons
\item Simplified version of the \texttt{Zope Component Architecture}
\item Details at \texttt{http://trac.edgewall.org/wiki/TracDev/ComponentArchitecture}
\item Very popular architecture. Used in other applications like
Eclipse.
\end{itemize}
\end{frame}
\subsection {Writing a plugin}
\begin{frame}
\frametitle {Trac : Writing a plugin}
\begin{itemize}
\item Write a new component (subclass \texttt{component}).
\item Interfaces are created by subclassing \texttt{Interface} and
then wrapping them in an \texttt{ExtensionPoint}.
\item The \texttt{Interface} will have methods defined in them that
can be overridden by user created \texttt{Components} that
\emph{implement} the interface.
\end{itemize}
\end{frame}
\subsection {Example}
\begin{frame}[fragile]
\frametitle {Trac : Example plugin}
\begin{lstlisting}
# Following is the code in the application
# The interface and it's specification
class ITodoObserver(Interface):
def todo_added(name, description):
"Called when a to-do item is added."
class TodoList(Component):
# Declaration of an extension point
observers = ExtensionPoint(ITodoObserver)
def __init__(self):
self.todos = {}
def add(self, name, description):
assert not name in self.todos, 'To-do already in list'
self.todos[name] = description
for observer in self.observers: # Using the extension point
observer.todo_added(name, description)
# A plugin that prints out details when something is added
class TodoPrinter(Component):
# Stating which interface is implemented
implements(ITodoObserver)
def todo_added(self, name, description):
prinyt 'TODO:', name
print ' ', description
\end{lstlisting}
\end{frame}
\subsection {Pros/Cons}
\begin {frame}
\frametitle {Trac : Pros/Cons}
\begin{itemize}
\item Pros
\begin{itemize}
\item Very ``Object Oriented''
\end{itemize}
\item Cons
\begin{itemize}
\item High entry barrier (need to learn ``plugin API'')
\item Lots of boilerplate code
\end{itemize}
\end{itemize}
\end {frame}
\section {Case \#2 : py.test}
\subsection {Metaprogramming}
\begin{frame}
\frametitle {py.test : Metaprogramming}
\begin{itemize}
\item Takes the ``best api is no api'' to the extreme.
\item Relies on naming conventions to insert code into the framework.
\item Searches multiple sources for ``plugin'' files to load
(setuptools entry points, \texttt{-p} option,
\texttt{conftest.py} etc.)
\item Inside the plugin file, hooks can be defined using the
\texttt{pytest\_} prefix (e.g. \texttt{pytest\_addoption} to add a
command line option).
\end{itemize}
\end{frame}
\subsection{Writing a plugin}
\begin{frame}
\frametitle{py.test : Writing a plugin}
\begin{itemize}
\item Implement the hooks that are necessary for the plugin to work
(e.g. add a few command line options, initialise some stuff before
the tests start, print out some extra information when tests are done).
\item Inform \texttt{py.test} that this plugin file needs to be
picked up at startup time.
\item Similar in some sense to the first approach but using modules
instead of singleton classes.
\end{itemize}
\end{frame}
\subsection {Example}
\begin{frame}[fragile]
\frametitle{py.test : Example}
\begin{lstlisting}
class BugZillaInteg(object):
def pytest_report_teststatus(self,report):
"Hook called when test status is created"
if report.failed:
if self.analysed(report.item.function.__doc__):
return "analysed", "A", "ANALYSED"
def __init__(self, config, bugzilla):
self.config = config
self.bugzilla = bugzilla
def pytest_configure(config):
# Logging in into bugzilla
bzilla = pyzilla.BugZilla(config.getvalue("bugzilla_url"),
config.getvalue("bugzilla_verbose"))
bzilla.login (username = config.getvalue("bugzilla_username"),
password = config.getvalue("bugzilla_pw"))
# Register the plugin
config.pluginmanager.register(BugZillaInteg(config, bzilla), "bugzilla")
\end{lstlisting}
\end{frame}
\subsection {Pros/Cons}
\begin{frame}
\frametitle {py.test : Pros/Cons}
\begin{itemize}
\item Pros
\begin{itemize}
\item Very easy to write a plugin
\item Very easy to visualise what's going on.
\item High level of flexibility.
\end{itemize}
\item Cons
\begin{itemize}
\item The internals of py.test can get complex.
\end{itemize}
\end{itemize}
\end{frame}
\section {Case \#3 : Emacs}
\subsection {Embedded scripting language}
\begin{frame}
\frametitle{Emacs : Embedded scripting language}
\begin{itemize}
\item Build your application as a ``library'' for a scripting
language.
\item The primitives will be hardcoded and the rest built on top in
the scripting language.
\item No real ``API''. The boundaries between plugin and core get
blurred.
\item Emacs is a successful example. It's an ``editor'' that works
as an email client, IRC client, PIM, web browser, twitter client,
video editor, mp3 player and other things.
\end{itemize}
\end{frame}
\subsection{Writing a plugin}
\begin{frame}
\frametitle {Emacs : Writing a plugin}
\begin{itemize}
\item Write a script just like one would do for any language
\item Evaluate the script (or drop it into the init file)
\end{itemize}
\end{frame}
\lstset{language=lisp}
\subsection {Example}
\begin{frame}[fragile]
\frametitle{Emacs : Example}
\begin{lstlisting}
(defun list-issues ()
"Shows all TBDs in current tree in all files of the same type as current"
(interactive)
(let ((dir (file-name-directory (buffer-file-name)))
(ext (file-name-extension (buffer-file-name))))
(rgrep "TBD" (concat "*." ext) dir))
(other-window 1))
(global-set-key (kbd "<f11>") 'list-issues)
\end{lstlisting}
\end{frame}
\section {Thoughts}
\begin{frame}
\frametitle {Thoughts}
General feelings on how these things should be done.
\pause
\begin{itemize}
\item Try to keep the non extensible core as small as possible.
\item Implement the smallest subset of what you need and make the
rest plugins.
\item Move all non essential parts to plugin land.
\item Reduce boilerplate as much as possible. Make it \emph{easy}
(and not just possible) for users to extend your software. Rely on
conventions rather than writing lots of code.
\item If you're going the Emacs route, embed a \emph{real} scripting
language and not just a toy or ad-hoc one.
\end{itemize}
\end{frame}
\end{document}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment